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
@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;
}
}
Related videos on Youtube
Comments
-
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:
- do authentication against a 3rd party service when doing a user + pas logi n
replace cookie session token with my custom implementation upon successful auth
upon every request parse the JWT from the cookie ( using a filter )
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 almost 8 yearsThank 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 over 6 yearsThis 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 over 6 yearsdo you have github link?
-
Mario about 6 yearswish i could upvote this more than once. very helpful, thanks @AssafMoldavsky.
-
Dibyendu almost 4 yearsCookieAuthenticationFilter.attemptAuthentication is getting invoked twice, any idea why this might happen. I implemented the above solution as is.
-
San over 3 years@AssafMoldavsky - do you working example of this in github? Can you provide complete code?
-
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.