Refresh token endpoint.

This commit is contained in:
svlada 2016-08-19 16:58:25 +02:00
parent a3e9b93382
commit f1a5be7412
8 changed files with 81 additions and 26 deletions

View File

@ -1,6 +1,8 @@
package com.svlada.security.auth.ajax; package com.svlada.security.auth.ajax;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -43,12 +45,16 @@ public class AjaxAwareAuthenticationSuccessHandler implements AuthenticationSucc
Authentication authentication) throws IOException, ServletException { Authentication authentication) throws IOException, ServletException {
UserContext userContext = (UserContext) authentication.getPrincipal(); UserContext userContext = (UserContext) authentication.getPrincipal();
JwtToken token = tokenFactory.createAccessJwtToken(userContext); JwtToken accessToken = tokenFactory.createAccessJwtToken(userContext);
JwtToken refreshToken = tokenFactory.createRefreshToken(userContext); JwtToken refreshToken = tokenFactory.createRefreshToken(userContext);
Map<String, String> tokenMap = new HashMap<String, String>();
tokenMap.put("token", accessToken.getToken());
tokenMap.put("refreshToken", refreshToken.getToken());
response.setStatus(HttpStatus.OK.value()); response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(response.getWriter(), token); mapper.writeValue(response.getWriter(), tokenMap);
clearAuthenticationAttributes(request); clearAuthenticationAttributes(request);
} }

View File

@ -39,11 +39,9 @@ public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingF
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public AjaxLoginProcessingFilter(String defaultFilterProcessesUrl, public AjaxLoginProcessingFilter(String defaultProcessUrl, AuthenticationSuccessHandler successHandler,
AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler, ObjectMapper mapper) {
AuthenticationFailureHandler failureHandler, super(defaultProcessUrl);
ObjectMapper mapper) {
super(defaultFilterProcessesUrl);
this.successHandler = successHandler; this.successHandler = successHandler;
this.failureHandler = failureHandler; this.failureHandler = failureHandler;
this.objectMapper = mapper; this.objectMapper = mapper;

View File

@ -14,6 +14,7 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.RequestMatcher;
import com.svlada.security.auth.JwtAuthenticationToken; import com.svlada.security.auth.JwtAuthenticationToken;
import com.svlada.security.auth.jwt.extractor.TokenExtractor; import com.svlada.security.auth.jwt.extractor.TokenExtractor;
@ -33,9 +34,8 @@ public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticati
@Autowired @Autowired
public JwtTokenAuthenticationProcessingFilter(AuthenticationFailureHandler failureHandler, public JwtTokenAuthenticationProcessingFilter(AuthenticationFailureHandler failureHandler,
TokenExtractor tokenExtractor, TokenExtractor tokenExtractor, RequestMatcher matcher) {
String filterProcessingUrl) { super(matcher);
super(filterProcessingUrl);
this.failureHandler = failureHandler; this.failureHandler = failureHandler;
this.tokenExtractor = tokenExtractor; this.tokenExtractor = tokenExtractor;
} }

View File

@ -0,0 +1,38 @@
package com.svlada.security.auth.jwt;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* SkipPathRequestMatcher
*
* @author vladimir.stankovic
*
* Aug 19, 2016
*/
public class SkipPathRequestMatcher implements RequestMatcher {
private OrRequestMatcher matchers;
private RequestMatcher processingMatcher;
public SkipPathRequestMatcher(List<String> pathsToSkip, String processingPath) {
Assert.notNull(pathsToSkip);
List<RequestMatcher> m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
matchers = new OrRequestMatcher(m);
processingMatcher = new AntPathRequestMatcher(processingPath);
}
@Override
public boolean matches(HttpServletRequest request) {
if (matchers.matches(request)) {
return false;
}
return processingMatcher.matches(request) ? true : false;
}
}

View File

@ -1,5 +1,8 @@
package com.svlada.security.config; package com.svlada.security.config;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -20,8 +23,8 @@ import com.svlada.security.auth.ajax.AjaxAuthenticationProvider;
import com.svlada.security.auth.ajax.AjaxLoginProcessingFilter; import com.svlada.security.auth.ajax.AjaxLoginProcessingFilter;
import com.svlada.security.auth.jwt.JwtAuthenticationProvider; import com.svlada.security.auth.jwt.JwtAuthenticationProvider;
import com.svlada.security.auth.jwt.JwtTokenAuthenticationProcessingFilter; import com.svlada.security.auth.jwt.JwtTokenAuthenticationProcessingFilter;
import com.svlada.security.auth.jwt.SkipPathRequestMatcher;
import com.svlada.security.auth.jwt.extractor.TokenExtractor; import com.svlada.security.auth.jwt.extractor.TokenExtractor;
import com.svlada.security.model.token.JwtTokenFactory;
/** /**
* WebSecurityConfig * WebSecurityConfig
@ -45,12 +48,11 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private JwtAuthenticationProvider jwtAuthenticationProvider; @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider;
@Autowired private TokenExtractor tokenExtractor; @Autowired private TokenExtractor tokenExtractor;
@Autowired private JwtTokenFactory tokenFactory;
@Autowired private AuthenticationManager authenticationManager; @Autowired private AuthenticationManager authenticationManager;
@Autowired private ObjectMapper objectMapper; @Autowired private ObjectMapper objectMapper;
@Bean @Bean
protected AjaxLoginProcessingFilter buildAjaxLoginProcessingFilter() throws Exception { protected AjaxLoginProcessingFilter buildAjaxLoginProcessingFilter() throws Exception {
AjaxLoginProcessingFilter filter = new AjaxLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper); AjaxLoginProcessingFilter filter = new AjaxLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
@ -60,7 +62,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean @Bean
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
JwtTokenAuthenticationProcessingFilter filter = new JwtTokenAuthenticationProcessingFilter(failureHandler, tokenFactory, tokenExtractor, TOKEN_BASED_AUTH_ENTRY_POINT); List<String> pathsToSkip = Arrays.asList(TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT);
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
JwtTokenAuthenticationProcessingFilter filter
= new JwtTokenAuthenticationProcessingFilter(failureHandler, tokenExtractor, matcher);
filter.setAuthenticationManager(this.authenticationManager); filter.setAuthenticationManager(this.authenticationManager);
return filter; return filter;
} }

View File

@ -5,10 +5,12 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@ -21,6 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
import com.svlada.entity.User; import com.svlada.entity.User;
import com.svlada.security.UserService; import com.svlada.security.UserService;
import com.svlada.security.auth.jwt.extractor.TokenExtractor;
import com.svlada.security.auth.jwt.verifier.TokenVerifier; import com.svlada.security.auth.jwt.verifier.TokenVerifier;
import com.svlada.security.config.JwtSettings; import com.svlada.security.config.JwtSettings;
import com.svlada.security.config.WebSecurityConfig; import com.svlada.security.config.WebSecurityConfig;
@ -44,12 +47,15 @@ public class RefreshTokenEndpoint {
@Autowired private JwtSettings jwtSettings; @Autowired private JwtSettings jwtSettings;
@Autowired private UserService userService; @Autowired private UserService userService;
@Autowired private TokenVerifier tokenVerifier; @Autowired private TokenVerifier tokenVerifier;
@Autowired @Qualifier("jwtHeaderTokenExtractor") private TokenExtractor tokenExtractor;
@RequestMapping(value="/api/auth/token", method=RequestMethod.GET, produces={ MediaType.APPLICATION_JSON_VALUE }) @RequestMapping(value="/api/auth/token", method=RequestMethod.GET, produces={ MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody JwtToken refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { public @ResponseBody JwtToken refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
RawAccessJwtToken rawToken = new RawAccessJwtToken(request.getHeader(WebSecurityConfig.JWT_TOKEN_HEADER_PARAM)); String tokenPayload = tokenExtractor.extract(request.getHeader(WebSecurityConfig.JWT_TOKEN_HEADER_PARAM));
RefreshToken refreshToken = RefreshToken.create(rawToken, jwtSettings.getTokenSigningKey()).orElseThrow(() -> new InvalidJwtToken());
RawAccessJwtToken rawToken = new RawAccessJwtToken(tokenPayload);
RefreshToken refreshToken = RefreshToken.create(rawToken, jwtSettings.getTokenSigningKey()).orElseThrow(() -> new InvalidJwtToken());
String jti = refreshToken.getJti(); String jti = refreshToken.getJti();
if (!tokenVerifier.verify(jti)) { if (!tokenVerifier.verify(jti)) {
throw new InvalidJwtToken(); throw new InvalidJwtToken();

View File

@ -22,12 +22,12 @@ import io.jsonwebtoken.SignatureAlgorithm;
* *
* @author vladimir.stankovic * @author vladimir.stankovic
* *
* May 31, 2016 * May 31, 2016
*/ */
@Component @Component
public class JwtTokenFactory { public class JwtTokenFactory {
private final JwtSettings settings; private final JwtSettings settings;
@Autowired @Autowired
public JwtTokenFactory(JwtSettings settings) { public JwtTokenFactory(JwtSettings settings) {
this.settings = settings; this.settings = settings;
@ -43,7 +43,7 @@ public class JwtTokenFactory {
public AccessJwtToken createAccessJwtToken(UserContext userContext) { public AccessJwtToken createAccessJwtToken(UserContext userContext) {
if (StringUtils.isBlank(userContext.getUsername())) if (StringUtils.isBlank(userContext.getUsername()))
throw new IllegalArgumentException("Cannot create JWT Token without username"); throw new IllegalArgumentException("Cannot create JWT Token without username");
if (userContext.getAuthorities() == null || userContext.getAuthorities().isEmpty()) if (userContext.getAuthorities() == null || userContext.getAuthorities().isEmpty())
throw new IllegalArgumentException("User doesn't have any privileges"); throw new IllegalArgumentException("User doesn't have any privileges");
@ -51,7 +51,7 @@ public class JwtTokenFactory {
claims.put("scopes", userContext.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList())); claims.put("scopes", userContext.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
DateTime currentTime = new DateTime(); DateTime currentTime = new DateTime();
String token = Jwts.builder() String token = Jwts.builder()
.setClaims(claims) .setClaims(claims)
.setIssuer(settings.getTokenIssuer()) .setIssuer(settings.getTokenIssuer())
@ -71,7 +71,7 @@ public class JwtTokenFactory {
DateTime currentTime = new DateTime(); DateTime currentTime = new DateTime();
Claims claims = Jwts.claims().setSubject(userContext.getUsername()); Claims claims = Jwts.claims().setSubject(userContext.getUsername());
claims.put("scopes", Arrays.asList(Scopes.REFRESH_TOKEN)); claims.put("scopes", Arrays.asList(Scopes.REFRESH_TOKEN.authority()));
String token = Jwts.builder() String token = Jwts.builder()
.setClaims(claims) .setClaims(claims)

View File

@ -12,18 +12,20 @@ import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jws;
/** /**
* RefreshToken
* *
* @author vladimir.stankovic * @author vladimir.stankovic
* *
* Aug 19, 2016 * Aug 19, 2016
*/ */
@SuppressWarnings("unchecked")
public class RefreshToken implements JwtToken { public class RefreshToken implements JwtToken {
private Jws<Claims> claims; private Jws<Claims> claims;
private RefreshToken(Jws<Claims> claims) { private RefreshToken(Jws<Claims> claims) {
this.claims = claims; this.claims = claims;
} }
/** /**
* Creates and validates Refresh token * Creates and validates Refresh token
* *
@ -43,10 +45,10 @@ public class RefreshToken implements JwtToken {
|| !scopes.stream().filter(scope -> Scopes.REFRESH_TOKEN.authority().equals(scope)).findFirst().isPresent()) { || !scopes.stream().filter(scope -> Scopes.REFRESH_TOKEN.authority().equals(scope)).findFirst().isPresent()) {
return Optional.empty(); return Optional.empty();
} }
return Optional.of(new RefreshToken(claims)); return Optional.of(new RefreshToken(claims));
} }
@Override @Override
public String getToken() { public String getToken() {
return null; return null;