Spring Security Cookie + JWT authentication Spring Security Cookie + JWT authentication spring spring

Spring Security Cookie + JWT authentication


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

@Componentpublic 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.

@Componentpublic 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@EnableWebSecuritypublic 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;    }}


The easiest way would be to add Spring Session into your project and extend HttpSessionStrategy that provides convenient hooks for session created/destroyed events and has method to extract session from HttpServletRequest.