9

I have a SpringBoot 2.0.2.RELEASE web application, with this config file:

@Override
protected void configure(HttpSecurity http) throws Exception {

    final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
    if (activeProfiles.contains("dev")) {
        http.csrf().disable();
        http.headers().frameOptions().disable();
    }

    http
        .authorizeRequests()
        .antMatchers(publicMatchers()).permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
        .failureUrl("/login?error").permitAll()
        .and()
        .logout().permitAll();
    }

I want to add a Custom JWT based security filter ONLY for the Rest Controllers that will be under the match /rest/** , so I modified the config to this file, but now I can't log into the app, because I have a HTTP Status 401 – Unauthorized

@Override
protected void configure(HttpSecurity http) throws Exception {

    final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
    if (activeProfiles.contains("dev")) {
        http.csrf().disable();
        http.headers().frameOptions().disable();
    }

    http
       .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
       // don't create session
       .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
       .authorizeRequests()
       .antMatchers(publicMatchers()).permitAll()
       .anyRequest().authenticated()
       .and()
       .formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
                   .failureUrl("/login?error").permitAll()
       .and()
       .logout().permitAll();


       // Custom JWT based security filter
       JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
       http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

and the filter (that extends from OncePerRequestFilter )

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

    logger.info("processing authentication for '{}'", request.getRequestURL());


    if (request.getRequestURI().indexOf("/rest/")==-1) {
        chain.doFilter(request, response);
        return;
    }


    final String requestHeader = request.getHeader(this.tokenHeader);

    String username = null;
    String authToken = null;
    if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
        authToken = requestHeader.substring(7);
        try {
            username = jwtTokenUtil.getUsernameFromToken(authToken);
        } catch (IllegalArgumentException e) {
            logger.info("an error occured during getting username from token", e);
        } catch (ExpiredJwtException e) {
            logger.info("the token is expired and not valid anymore", e);
        }
    } else {
        logger.info("couldn't find bearer string, will ignore the header");
    }

    logger.info("checking authentication for user '{}'", username);
    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
       logger.info("security context was null, so authorizating user");

        // It is not compelling necessary to load the use details from the database. You could also store the information
        // in the token and read it from it. It's up to you ;)
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

        // For simple validation it is completely sufficient to just check the token integrity. You don't have to call
        // the database compellingly. Again it's up to you ;)
        if (jwtTokenUtil.validateToken(authToken, userDetails)) {
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            logger.info("authorizated user '{}', setting security context", username);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        chain.doFilter(request, response);
    }

....

@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
    return request.getRequestURI().indexOf("/rest/")==-1;
}

in the logger I see

("couldn't find bearer string, will ignore the header"

Because I only want to apply the JWT filter in the RestContollers not in all of them, like LoginController

With this config class I can access to the /rest/ URL only being logged in the app.

@Profile("web")
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final Logger LOG = LoggerFactory.getLogger(WebSecurityConfig.class);

    @Autowired
    private UserSecurityService userSecurityService;


    @Value("${server.servlet.context-path}")
    private String serverContextPath;

    /** The encryption SALT. */
    private static final String SALT = "fd&lkj§sfs23#$1*(_)nof";

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Configuration
    @Order(1)
    public static class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Autowired
        private JwtAuthenticationEntryPoint unauthorizedHandler;

        @Autowired
        private JwtTokenUtil jwtTokenUtil;

        @Value("${jwt.header}")
        private String tokenHeader;

        @Value("${jwt.route.authentication.path}")
        private String authenticationPath;

        @Override
        protected void configure(HttpSecurity http) throws Exception {

            http
                    // we don't need CSRF because our token is invulnerable
                    .csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()

                    // don't create session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .authorizeRequests()
                    .antMatchers(“/rest/**”).permitAll().anyRequest().authenticated()
                    .antMatchers(“**/rest/**”).permitAll().anyRequest().authenticated();

            // Custom JWT based security filter
            JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
            http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

            // disable page caching
            http.headers().frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
                    .cacheControl();

        }

    }

    @Configuration
    @Order(0)
    public static class OtherSecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Value("${server.servlet.context-path}")
        private String serverContextPath;

        @Autowired
        private Environment env;

        @Override
        protected void configure(HttpSecurity http) throws Exception {

            final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
            if (activeProfiles.contains("dev")) {
                http.csrf().disable();
                http.headers().frameOptions().disable();
            }

            http.authorizeRequests()
                .antMatchers(publicMatchers())
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
                .failureUrl("/login?error").permitAll()
                .and()
                .logout()
                .permitAll();
        }

         private String[] publicMatchers() {

             /** Public URLs. */
            final String[] PUBLIC_MATCHERS = {
                    "/webjars/**",
                    serverContextPath + "/css/**",
                    serverContextPath + "/js/**",
                    serverContextPath + "/fonts/**",
                    serverContextPath + "/images/**",                
                    serverContextPath ,
                    "/",
                    "/error/**/*",
                    "/console/**",
                    ForgotMyPasswordController.FORGOT_PASSWORD_URL_MAPPING,
                    ForgotMyPasswordController.CHANGE_PASSWORD_PATH,
                    SignupController.SIGNUP_URL_MAPPING
            };

            return PUBLIC_MATCHERS;

        }

    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
    }

}
3
  • Why don’t you use antMatcher(“rest/**”).authenticated() Commented May 19, 2018 at 20:55
  • I have to be authenticated in all cases, rest/ with JWT, and the others with the typical springsecurity Commented May 20, 2018 at 4:54
  • 2
    assuming your filter is a OncePerRequestFilter, it has a method, shouldNotFilter(), can you use that to ignore everything other than your desired requests? Commented May 21, 2018 at 18:05

1 Answer 1

2
+50

In a nutshell, you have two subpaths (namely /rest/** and others) in the same application, and you want to apply different login schemes for each. Spring-security allows you to have multiple configurations, allowing for such a scenario.

I would do something like this:

@EnableWebSecurity
public class SecurityConfig {

    @Configuration
    @Order(1)
    public static class ApiSecurityConfiguration 
                  extends WebSecurityConfigurerAdapter {

        private final JwtAuthorizationTokenFilter jwtFilter = new ...
        private final AuthenticationEntryPoint unauthorizedHandler = new ...

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/rest/**").authorizeRequests()
                .and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
                .and()
                .addFilter(jwtFilter);
        }
    }

    @Configuration
    public static class OtherSecurityConfiguration 
                  extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests().anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/login").defaultSuccessUrl("/bonanza/list")
                        .failureUrl("/login?error").permitAll()
                    .and()
                    .logout().permitAll();
        }
    }
}

With such configuration, the JwtAuthorizationTokenFilter should only be activated for the matching paths. Thus I think you won't need to check for the paths in JwtAuthorizationTokenFilter.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.