From f1a5be74123be6abc384fe6f6f202a7d7a5dd256 Mon Sep 17 00:00:00 2001 From: svlada Date: Fri, 19 Aug 2016 16:58:25 +0200 Subject: [PATCH] Refresh token endpoint. --- ...AjaxAwareAuthenticationSuccessHandler.java | 10 ++++- .../auth/ajax/AjaxLoginProcessingFilter.java | 8 ++-- ...wtTokenAuthenticationProcessingFilter.java | 6 +-- .../auth/jwt/SkipPathRequestMatcher.java | 38 +++++++++++++++++++ .../security/config/WebSecurityConfig.java | 15 +++++--- .../endpoint/RefreshTokenEndpoint.java | 10 ++++- .../security/model/token/JwtTokenFactory.java | 10 ++--- .../security/model/token/RefreshToken.java | 10 +++-- 8 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/svlada/security/auth/jwt/SkipPathRequestMatcher.java diff --git a/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java b/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java index 77dd408..4392a81 100644 --- a/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java +++ b/src/main/java/com/svlada/security/auth/ajax/AjaxAwareAuthenticationSuccessHandler.java @@ -1,6 +1,8 @@ package com.svlada.security.auth.ajax; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -43,12 +45,16 @@ public class AjaxAwareAuthenticationSuccessHandler implements AuthenticationSucc Authentication authentication) throws IOException, ServletException { UserContext userContext = (UserContext) authentication.getPrincipal(); - JwtToken token = tokenFactory.createAccessJwtToken(userContext); + JwtToken accessToken = tokenFactory.createAccessJwtToken(userContext); JwtToken refreshToken = tokenFactory.createRefreshToken(userContext); + + Map tokenMap = new HashMap(); + tokenMap.put("token", accessToken.getToken()); + tokenMap.put("refreshToken", refreshToken.getToken()); response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); - mapper.writeValue(response.getWriter(), token); + mapper.writeValue(response.getWriter(), tokenMap); clearAuthenticationAttributes(request); } diff --git a/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java b/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java index 69ca4c3..e39781f 100644 --- a/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java +++ b/src/main/java/com/svlada/security/auth/ajax/AjaxLoginProcessingFilter.java @@ -39,11 +39,9 @@ public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingF private final ObjectMapper objectMapper; - public AjaxLoginProcessingFilter(String defaultFilterProcessesUrl, - AuthenticationSuccessHandler successHandler, - AuthenticationFailureHandler failureHandler, - ObjectMapper mapper) { - super(defaultFilterProcessesUrl); + public AjaxLoginProcessingFilter(String defaultProcessUrl, AuthenticationSuccessHandler successHandler, + AuthenticationFailureHandler failureHandler, ObjectMapper mapper) { + super(defaultProcessUrl); this.successHandler = successHandler; this.failureHandler = failureHandler; this.objectMapper = mapper; diff --git a/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java b/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java index 6763c9e..e4b9dd1 100644 --- a/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java +++ b/src/main/java/com/svlada/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java @@ -14,6 +14,7 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 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.jwt.extractor.TokenExtractor; @@ -33,9 +34,8 @@ public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticati @Autowired public JwtTokenAuthenticationProcessingFilter(AuthenticationFailureHandler failureHandler, - TokenExtractor tokenExtractor, - String filterProcessingUrl) { - super(filterProcessingUrl); + TokenExtractor tokenExtractor, RequestMatcher matcher) { + super(matcher); this.failureHandler = failureHandler; this.tokenExtractor = tokenExtractor; } diff --git a/src/main/java/com/svlada/security/auth/jwt/SkipPathRequestMatcher.java b/src/main/java/com/svlada/security/auth/jwt/SkipPathRequestMatcher.java new file mode 100644 index 0000000..4d7c05d --- /dev/null +++ b/src/main/java/com/svlada/security/auth/jwt/SkipPathRequestMatcher.java @@ -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 pathsToSkip, String processingPath) { + Assert.notNull(pathsToSkip); + List 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; + } +} diff --git a/src/main/java/com/svlada/security/config/WebSecurityConfig.java b/src/main/java/com/svlada/security/config/WebSecurityConfig.java index 341b7a6..f63c048 100644 --- a/src/main/java/com/svlada/security/config/WebSecurityConfig.java +++ b/src/main/java/com/svlada/security/config/WebSecurityConfig.java @@ -1,5 +1,8 @@ package com.svlada.security.config; +import java.util.Arrays; +import java.util.List; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; 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.jwt.JwtAuthenticationProvider; 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.model.token.JwtTokenFactory; /** * WebSecurityConfig @@ -45,12 +48,11 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider; @Autowired private TokenExtractor tokenExtractor; - @Autowired private JwtTokenFactory tokenFactory; - + @Autowired private AuthenticationManager authenticationManager; @Autowired private ObjectMapper objectMapper; - + @Bean protected AjaxLoginProcessingFilter buildAjaxLoginProcessingFilter() throws Exception { AjaxLoginProcessingFilter filter = new AjaxLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper); @@ -60,7 +62,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { - JwtTokenAuthenticationProcessingFilter filter = new JwtTokenAuthenticationProcessingFilter(failureHandler, tokenFactory, tokenExtractor, TOKEN_BASED_AUTH_ENTRY_POINT); + List 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); return filter; } diff --git a/src/main/java/com/svlada/security/endpoint/RefreshTokenEndpoint.java b/src/main/java/com/svlada/security/endpoint/RefreshTokenEndpoint.java index 47acfbe..e3dbe42 100644 --- a/src/main/java/com/svlada/security/endpoint/RefreshTokenEndpoint.java +++ b/src/main/java/com/svlada/security/endpoint/RefreshTokenEndpoint.java @@ -5,10 +5,12 @@ import java.util.List; import java.util.stream.Collectors; import javax.servlet.ServletException; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.security.authentication.InsufficientAuthenticationException; 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.security.UserService; +import com.svlada.security.auth.jwt.extractor.TokenExtractor; import com.svlada.security.auth.jwt.verifier.TokenVerifier; import com.svlada.security.config.JwtSettings; import com.svlada.security.config.WebSecurityConfig; @@ -44,12 +47,15 @@ public class RefreshTokenEndpoint { @Autowired private JwtSettings jwtSettings; @Autowired private UserService userService; @Autowired private TokenVerifier tokenVerifier; + @Autowired @Qualifier("jwtHeaderTokenExtractor") private TokenExtractor tokenExtractor; @RequestMapping(value="/api/auth/token", method=RequestMethod.GET, produces={ MediaType.APPLICATION_JSON_VALUE }) public @ResponseBody JwtToken refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - RawAccessJwtToken rawToken = new RawAccessJwtToken(request.getHeader(WebSecurityConfig.JWT_TOKEN_HEADER_PARAM)); - RefreshToken refreshToken = RefreshToken.create(rawToken, jwtSettings.getTokenSigningKey()).orElseThrow(() -> new InvalidJwtToken()); + String tokenPayload = tokenExtractor.extract(request.getHeader(WebSecurityConfig.JWT_TOKEN_HEADER_PARAM)); + RawAccessJwtToken rawToken = new RawAccessJwtToken(tokenPayload); + RefreshToken refreshToken = RefreshToken.create(rawToken, jwtSettings.getTokenSigningKey()).orElseThrow(() -> new InvalidJwtToken()); + String jti = refreshToken.getJti(); if (!tokenVerifier.verify(jti)) { throw new InvalidJwtToken(); diff --git a/src/main/java/com/svlada/security/model/token/JwtTokenFactory.java b/src/main/java/com/svlada/security/model/token/JwtTokenFactory.java index d263366..e1b4cb1 100644 --- a/src/main/java/com/svlada/security/model/token/JwtTokenFactory.java +++ b/src/main/java/com/svlada/security/model/token/JwtTokenFactory.java @@ -22,12 +22,12 @@ import io.jsonwebtoken.SignatureAlgorithm; * * @author vladimir.stankovic * - * May 31, 2016 + * May 31, 2016 */ @Component public class JwtTokenFactory { private final JwtSettings settings; - + @Autowired public JwtTokenFactory(JwtSettings settings) { this.settings = settings; @@ -43,7 +43,7 @@ public class JwtTokenFactory { public AccessJwtToken createAccessJwtToken(UserContext userContext) { if (StringUtils.isBlank(userContext.getUsername())) throw new IllegalArgumentException("Cannot create JWT Token without username"); - + if (userContext.getAuthorities() == null || userContext.getAuthorities().isEmpty()) 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())); DateTime currentTime = new DateTime(); - + String token = Jwts.builder() .setClaims(claims) .setIssuer(settings.getTokenIssuer()) @@ -71,7 +71,7 @@ public class JwtTokenFactory { DateTime currentTime = new DateTime(); 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() .setClaims(claims) diff --git a/src/main/java/com/svlada/security/model/token/RefreshToken.java b/src/main/java/com/svlada/security/model/token/RefreshToken.java index e730e5e..21ab420 100644 --- a/src/main/java/com/svlada/security/model/token/RefreshToken.java +++ b/src/main/java/com/svlada/security/model/token/RefreshToken.java @@ -12,18 +12,20 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; /** + * RefreshToken * * @author vladimir.stankovic * * Aug 19, 2016 */ +@SuppressWarnings("unchecked") public class RefreshToken implements JwtToken { private Jws claims; - + private RefreshToken(Jws claims) { this.claims = claims; } - + /** * 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()) { return Optional.empty(); } - + return Optional.of(new RefreshToken(claims)); } - + @Override public String getToken() { return null;