Spring Security Cookie + JWT authentication

28,215

To get this to work the way described in the original post, this is what needs to happen...

Custom Filter

public class CookieAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

        public CookieAuthenticationFilter( RequestMatcher requestMatcher ) {

            super( requestMatcher );
            setAuthenticationManager( super.getAuthenticationManager() );

        }

        @Override
        public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response )
            throws AuthenticationException, IOException, ServletException {

            String token = "";

            // get token from a Cookie
            Cookie[] cookies = request.getCookies();

            if( cookies == null || cookies.length < 1 ) {
                throw new AuthenticationServiceException( "Invalid Token" );
            }

            Cookie sessionCookie = null;
            for( Cookie cookie : cookies ) {
                if( ( "someSessionId" ).equals( cookie.getName() ) ) {
                sessionCookie = cookie;
                break;
                }
            }

            // TODO: move the cookie validation into a private method
            if( sessionCookie == null || StringUtils.isEmpty( sessionCookie.getValue() ) ) {
                throw new AuthenticationServiceException( "Invalid Token" );
            }

            JWTAuthenticationToken jwtAuthentication = new JWTAuthenticationToken( sessionCookie.getValue(), null, null );

            return jwtAuthentication;

        }


        @Override
        public void doFilter(ServletRequest req, ServletResponse res,
                 FilterChain chain) throws IOException, ServletException {
            super.doFilter(req, res, chain);
        }

}

Authentication Provider

attach the provider to the UsernamePasswordAuthenticationToken which is generated by UsernamePasswordAuthenticationFilter, which attaches itself to "/login" POST. For formlogin with POST to "/login" will generate UsernamePasswordAuthenticationToken and your provider will be triggered

@Component
public class ApiAuthenticationProvider implements AuthenticationProvider {

        @Autowired
        TokenAuthenticationService tokenAuthService;

        @Override
        public Authentication authenticate( Authentication authentication ) throws AuthenticationException {

            String login = authentication.getName();
            String password = authentication.getCredentials().toString();

            // perform API call to auth against a 3rd party

            // get User data
            User user = new User();

            // create a JWT token
            String jwtToken = "some-token-123"

            return new JWTAuthenticationToken( jwtToken, user, new ArrayList<>() );

        }

        @Override
        public boolean supports( Class<?> authentication ) {
                return authentication.equals( UsernamePasswordAuthenticationToken.class );
        }
}

Custom Authentication object

For the JWT we want to have our own Authentication token object to carry the data that we want along the stack.

public class JWTAuthenticationToken extends AbstractAuthenticationToken {

        User principal;
        String token;

        public JWTAuthenticationToken( String token, User principal, Collection<? extends GrantedAuthority> authorities ) {
            super( authorities );
            this.token = token;
            this.principal = principal;
        }

        @Override
        public Object getCredentials() {
            return null;
        }

        @Override
        public Object getPrincipal() {
            return principal;
        }

        public void setToken( String token ) {
            this.token = token;
        }

        public String getToken() {
            return token;
        }
}

Authentication Success Handler

This is getting called when our custom provider did it's job by authenticating the user against a 3rd party and generating a JWT token, This is the place where the Cookie gets into the Response.

@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(
                HttpServletRequest request, 
                HttpServletResponse response, 
                Authentication authentication) throws IOException, ServletException {

        if( !(authentication instanceof JWTAuthenticationToken) ) {
            return;
        }

        JWTAuthenticationToken jwtAuthenticaton =    (JWTAuthenticationToken) authentication;

        // Add a session cookie
        Cookie sessionCookie = new Cookie( "someSessionId", jwtAuthenticaton.getToken() );
        response.addCookie( sessionCookie );

        //clearAuthenticationAttributes(request);

        // call the original impl
        super.onAuthenticationSuccess( request, response, authentication );
}

}

Hooking This All Together

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired @Required
    ApiAuthenticationProvider apiAuthProvider;

    @Autowired @Required
    AuthenticationSuccessHandler authSuccessHandler;

    @Autowired @Required
    SimpleUrlAuthenticationFailureHandler authFailureHandler;

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

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

            httpSecurity

            // don't create session
            .sessionManagement()
                .sessionCreationPolicy( SessionCreationPolicy.STATELESS )
                .and()

            .authorizeRequests()
                .antMatchers( "/", "/login", "/register" ).permitAll()
                .antMatchers( "/js/**", "/css/**", "/img/**" ).permitAll()
                .anyRequest().authenticated()
                .and()

            // login
            .formLogin()
                .failureHandler( authFailureHandler )
                //.failureUrl( "/login" )
                .loginPage("/login")
                .successHandler( authSuccessHandler )
                        .and()

            // JWT cookie filter
            .addFilterAfter( getCookieAuthenticationFilter(
                    new AndRequestMatcher( new AntPathRequestMatcher( "/account" ) )
            ) , UsernamePasswordAuthenticationFilter.class );
    }


    @Bean
    SimpleUrlAuthenticationFailureHandler getAuthFailureHandler() {

            SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler( "/login" );
            handler.setDefaultFailureUrl( "/login" );
            //handler.setUseForward( true );

            return handler;

    }

    CookieAuthenticationFilter getCookieAuthenticationFilter( RequestMatcher requestMatcher ) {

            CookieAuthenticationFilter filter = new CookieAuthenticationFilter( requestMatcher );
            filter.setAuthenticationFailureHandler( authFailureHandler );
            return filter;
    }
}
Share:
28,215

Related videos on Youtube

Assaf Moldavsky
Author by

Assaf Moldavsky

I am a web developer and a Javascript maniac :)

Updated on July 09, 2022

Comments

  • Assaf Moldavsky
    Assaf Moldavsky almost 2 years

    I must say I am very confused about the entire model and I need help gluing all the floating pieces together.

    I am not doing Spring REST, just plain WebMVC controllers.

    My mission: I want a form login with a username + pass authentication. I want to authenticate against a 3rd party service. Upon success I want to return a cookie but NOT use the default cookie token mechanism. I want the cookie to have a JWT token instead. By leveraging the cookie mechanism every request will be sent with the JWT.

    So to break it down I have the following modules to take care of:

    1. do authentication against a 3rd party service when doing a user + pas logi n
    2. replace cookie session token with my custom implementation upon successful auth

    3. upon every request parse the JWT from the cookie ( using a filter )

    4. extract user details / data from the JWT to be accessible to the controllers

    What's confusing? ( please correct me where I am wrong )

    3rd party authentication

    to authenticate against a 3rd party I will need to have a custom provider by extending AuthenticationProvider

    public class JWTTokenAuthenticationProvider implements AuthenticationProvider { 
    
          @Override
          public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
    
              // auth against 3rd party
    
              // return Authentication
              return new UsernamePasswordAuthenticationToken( name, password, new ArrayList<>() );
    
          }
    
          @Override
          public boolean supports(Class<?> authentication) {
              return authentication.equals( UsernamePasswordAuthenticationToken.class );
          }
    
    }
    

    Questions:

    • is this provider executed upon successful authentication / login when user submits a form user + pass? if so that how is that related to AbstractAuthenticationProcessingFilter#successfulAuthentication?
    • do I have to return an instance of UsernamePasswordAuthenticationToken?
    • do I have to support UsernamePasswordAuthenticationToken to get user + pass here?

    replace cookie token with a JWT

    No idea how to do this gracefully, I can think of a number of ways but they not Spring Security ways and I don't want to break out of the flow. Would be thankful for any suggestions here!

    parse the JWT with every request from a cookie

    From what I understand I need to extend AbstractAuthenticationProcessingFilter like so

    public class CookieAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
        @Override
        public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response )
                throws AuthenticationException, IOException, ServletException {
    
            String token = "";
    
            // get token from a Cookie
    
            // create an instance to Authentication
            TokenAuthentication authentication = new TokenAuthentication(null, null);
    
            return getAuthenticationManager().authenticate(tokenAuthentication);
    
        }
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {
            super.doFilter(req, res, chain);
        }
    
    }
    

    Questions:

    • when is AbstractAuthenticationProcessingFilter#successfulAuthentication called? is it called with the user logs in or when the JWT token been validated successfully?
    • is there any relation between this filter and the custom provider I posted previously? The manager will supposedly call the custom provider based on the token instance which is matched with what the provider supports via the support method?

    It seems as if I have all the pieces I need, except the cookie session replacement, but I cannot put them into a single coherent model and I need from somebody who understand the mechanics well enough so I can glue all of this into a single module.

    UPDATE 1

    OK, I think I am getting where this is starting... https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.java

    This Filter registers itself to POST -> "/login" and than creates an instance of UsernamePasswordAuthenticationToken and passes the control to the next filter.

    Question is where the cookie session is set....

    UPDATE 2

    This section of the dos gives the top level flow that I was missing, for whoever is going through this take a look here... http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#tech-intro-authentication

    This section regarding the AuthenticationProvider... http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#core-services-authentication-manager

    UPDATE 3 - working case, is this the best way??

    So after digging through the Spring Security docs and their sources I got the initial model to work. Now, doing this, I realized there is more than one way to do it. Any advice of why picking this way VS what Denys proposed below?

    Working example below...

  • Assaf Moldavsky
    Assaf Moldavsky almost 8 years
    Thank you for the tip Denys! So this is where I am getting confused, there are a number of ways to achieve the same thing, I can do this in three different ways now including what you have proposed. Can somebody explain why I would go the way I proposed im my question VS what you have proposed?
  • mohdajami
    mohdajami over 6 years
    This example doesn't look complete. It throws an error on "no AuthenticationProvider for JWTAuthenticationToken". I'm trying to do the same flow like yours and couldn't find any helpful resources.
  • Assaf Moldavsky
    Assaf Moldavsky over 6 years
    do you have github link?
  • Mario
    Mario about 6 years
    wish i could upvote this more than once. very helpful, thanks @AssafMoldavsky.
  • Dibyendu
    Dibyendu almost 4 years
    CookieAuthenticationFilter.attemptAuthentication is getting invoked twice, any idea why this might happen. I implemented the above solution as is.
  • San
    San over 3 years
    @AssafMoldavsky - do you working example of this in github? Can you provide complete code?
  • checketts
    checketts almost 2 years
    @Dibyendu If the filter is wired in using addFilterAfter you want to ensure it isn't also a bean or you'll see it being created and called twice.