Sagar Kadu
Sagar Kadu

Reputation: 261

spring security permitAll() not working for JWT Authentication filter

The issue is with the app uses custom JWT authentication filter which extends UsernamePasswordAuthenticationFilter which accepts user credentials and generates a long-lived JWT in return.

The issue seems to be with permitAll() which should bypass custom Authorization filter.However in debug mode I could see call to custom JwtAuthorizationFilter first instead of custom JwtAuthenticationFilter Filter which eventually results with 403 forbidden Access denied response.

Note the .antMatchers(HttpMethod.POST, "/login").permitAll() line. /login endpoint should be accessible without JWT since the JWT has not yet been generated when the user has not yet logged in.

Below is my code

JwtAuthenticationFilter.java

public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    //
    private AuthenticationManager authenticationManager;

    private final static UrlPathHelper urlPathHelper = new UrlPathHelper();

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        setFilterProcessesUrl("/login");
    }

    /**
     * Trigger when we issue POST request to login / we also need to pass in
     * {"username: " username, "password": password} in the request body
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        // Grab credentials and map them to login viewmodel

        LoginViewModel credentials = null;

        try {
            credentials = new ObjectMapper().readValue(request.getInputStream(), LoginViewModel.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Create login token
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                credentials.getUsername(), credentials.getPassword(), new ArrayList<>());

        // Authenciate user
        Authentication auth = authenticationManager.authenticate(authenticationToken);

        return auth;
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
            Authentication authResult) throws IOException, ServletException {
        // Grab principal
        UserPrincipal principal = (UserPrincipal) authResult.getPrincipal();

        // Create JWT Token
        String token = JWT.create().withSubject(principal.getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
                .sign(HMAC512(JwtProperties.SECRET.getBytes()));

        // add token in response
        response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + token);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException failed) throws IOException, ServletException {
        logger.debug("failed authentication while attempting to access "
                + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));

        // Add more descriptive message
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
    }

}

JwtAuthorizationFilter.java

public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

    private UserRepository userRepository;

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
        super(authenticationManager);
        this.userRepository = userRepository;
    }

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

        //Read the Authorization header, where the JWT token should be
        String header = request.getHeader(JwtProperties.HEADER_STRING);

        //If header does not contain BEARER or is null delegate to Spring impl and exit
        if (header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }


        // If header is present, try grab user principal from db and perform authorization
        Authentication authentication = getUsernamePasswordAuthentication(request);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        // Continue filter execution
        chain.doFilter(request, response);
    }

    private Authentication getUsernamePasswordAuthentication(HttpServletRequest request){
        String token = request.getHeader(JwtProperties.HEADER_STRING)
                .replace(JwtProperties.TOKEN_PREFIX, "");

        if(token !=null){
            //parse the token validate it
            String userName = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET.getBytes()))
                    .build()
                    .verify(token)
                    .getSubject();
            // Search in the DB if we find the user by token subject(username)
            // If so, then grab user details and create auth token using username, pass, authorities/roles

            if(userName != null){
                User user = userRepository.findByUsername(userName);
                UserPrincipal principal = new UserPrincipal(user);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, null, principal.getAuthorities());
                return authenticationToken;
            }
            return null;
        }
        return null;
    }
}

SecurityConfiguration.java

Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private UserPrincipalDetailsService userPrincipalDetailsService;
    private UserRepository userRepository;

    public SecurityConfiguration(UserPrincipalDetailsService userPrincipalDetailsService,
            UserRepository userRepository) {
        this.userPrincipalDetailsService = userPrincipalDetailsService;
        this.userRepository = userRepository;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // remove csrf state in session because in jwt do not need them
                .csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests().antMatchers(HttpMethod.POST, "/login").permitAll()
                .antMatchers("/api/public/management/*").hasRole("MANAGER").antMatchers("/api/public/admin/*")
                .hasRole("ADMIN").anyRequest().authenticated().and()
                // add jwt filters (1. authentication, 2. authorization_)
                .addFilter(new JwtAuthenticationFilter(authenticationManager()))
                .addFilter(new JwtAuthorizationFilter(authenticationManager(), this.userRepository));
        // configure access rules

    }

    @Bean
    DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService((UserDetailsService) this.userPrincipalDetailsService);

        return daoAuthenticationProvider;
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

Request,Response

enter image description here

Can someone suggest whats wrong here..Appreciate your help..Thanks in advance..!!!

Upvotes: 2

Views: 3363

Answers (2)

Ufuk Chavushoglu
Ufuk Chavushoglu

Reputation: 21

please consider to use shouldNotFilter method from BasicAuthenticationFilter. It extends OncePerRequestFilter so you can use it in filtering class as below:

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
       // code here
}

Upvotes: 2

Danilo Jakob
Danilo Jakob

Reputation: 81

It seems that your path is wrong. When you look at your body you can see that the path shows following: /login%0A. This seems that you have an extra character at the end of your URL. Just try to rewrite the URL in Postman.

Upvotes: 2

Related Questions