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:
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:
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:
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...
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;
}
}
最简单的方法是将Spring Session添加到项目中并扩展HttpSessionStrategy ,它为会话创建/销毁事件提供方便的钩子,并具有从HttpServletRequest中提取会话的方法。
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.