Code refactor.

This commit is contained in:
svlada 2016-08-19 14:18:25 +02:00
parent 2f8988ad4f
commit a3e9b93382
16 changed files with 122 additions and 82 deletions

View File

@ -482,6 +482,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
### [](http://stackoverflow.com/questions/38557379/secure-and-stateless-jwt-implementation)
http://by.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html
http://nordicapis.com/how-to-control-user-identity-within-microservices/
http://stackoverflow.com/questions/3487991/why-does-oauth-v2-have-both-access-and-refresh-tokens/12885823
https://tools.ietf.org/html/rfc6749#section-1.4
@ -499,4 +503,6 @@ true statelessness and revocation are mutually exclusive
https://www.sslvpn.online/are-breaches-of-jwt-based-servers-more-damaging/
http://nordicapis.com/how-to-control-user-identity-within-microservices/
http://nordicapis.com/how-to-control-user-identity-within-microservices/
https://tools.ietf.org/html/rfc6749

View File

@ -5,8 +5,8 @@ import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import com.svlada.security.model.UnsafeJwtToken;
import com.svlada.security.model.UserContext;
import com.svlada.security.model.token.RawAccessJwtToken;
/**
* An {@link org.springframework.security.core.Authentication} implementation
@ -19,18 +19,18 @@ import com.svlada.security.model.UserContext;
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 2877954820905567501L;
private UnsafeJwtToken unsafeToken;
private RawAccessJwtToken rawAccessToken;
private UserContext userContext;
public JwtAuthenticationToken(UnsafeJwtToken unsafeToken) {
public JwtAuthenticationToken(RawAccessJwtToken unsafeToken) {
super(null);
this.unsafeToken = unsafeToken;
this.rawAccessToken = unsafeToken;
this.setAuthenticated(false);
}
public JwtAuthenticationToken(UserContext userContext, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.unsafeToken = null;
this.eraseCredentials();
this.userContext = userContext;
super.setAuthenticated(true);
}
@ -46,7 +46,7 @@ public class JwtAuthenticationToken extends AbstractAuthenticationToken {
@Override
public Object getCredentials() {
return unsafeToken;
return rawAccessToken;
}
@Override
@ -57,6 +57,6 @@ public class JwtAuthenticationToken extends AbstractAuthenticationToken {
@Override
public void eraseCredentials() {
super.eraseCredentials();
this.unsafeToken = null;
this.rawAccessToken = null;
}
}

View File

@ -16,9 +16,9 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.svlada.security.model.JwtToken;
import com.svlada.security.model.JwtTokenFactory;
import com.svlada.security.model.UserContext;
import com.svlada.security.model.token.JwtToken;
import com.svlada.security.model.token.JwtTokenFactory;
/**
* AjaxAwareAuthenticationSuccessHandler
@ -43,7 +43,7 @@ public class AjaxAwareAuthenticationSuccessHandler implements AuthenticationSucc
Authentication authentication) throws IOException, ServletException {
UserContext userContext = (UserContext) authentication.getPrincipal();
JwtToken token = tokenFactory.createSafeToken(userContext);
JwtToken token = tokenFactory.createAccessJwtToken(userContext);
JwtToken refreshToken = tokenFactory.createRefreshToken(userContext);
response.setStatus(HttpStatus.OK.value());

View File

@ -13,9 +13,9 @@ import org.springframework.stereotype.Component;
import com.svlada.security.auth.JwtAuthenticationToken;
import com.svlada.security.config.JwtSettings;
import com.svlada.security.model.JwtToken;
import com.svlada.security.model.UnsafeJwtToken;
import com.svlada.security.model.UserContext;
import com.svlada.security.model.token.JwtToken;
import com.svlada.security.model.token.RawAccessJwtToken;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
@ -40,13 +40,11 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UnsafeJwtToken unsafeToken = (UnsafeJwtToken) authentication.getCredentials();
RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
Jws<Claims> jwsClaims = unsafeToken.parseClaims(jwtSettings.getTokenSigningKey());
Jws<Claims> jwsClaims = rawAccessToken.parseClaims(jwtSettings.getTokenSigningKey());
String subject = jwsClaims.getBody().getSubject();
List<String> scopes = jwsClaims.getBody().get("scopes", List.class);
List<GrantedAuthority> authorities = scopes.stream()
.map(authority -> new SimpleGrantedAuthority(authority))
.collect(Collectors.toList());

View File

@ -14,13 +14,11 @@ 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;
import com.svlada.security.config.WebSecurityConfig;
import com.svlada.security.model.JwtTokenFactory;
import com.svlada.security.model.UnsafeJwtToken;
import com.svlada.security.model.token.RawAccessJwtToken;
/**
* Performs validation of provided JWT Token.
@ -32,24 +30,21 @@ import com.svlada.security.model.UnsafeJwtToken;
public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
private final AuthenticationFailureHandler failureHandler;
private final TokenExtractor tokenExtractor;
private final JwtTokenFactory tokenFactory;
@Autowired
public JwtTokenAuthenticationProcessingFilter(AuthenticationFailureHandler failureHandler,
JwtTokenFactory tokenFactory,
TokenExtractor tokenExtractor,
String filterProcessingUrl) {
super(filterProcessingUrl);
this.failureHandler = failureHandler;
this.tokenExtractor = tokenExtractor;
this.tokenFactory = tokenFactory;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
String tokenPayload = request.getHeader(WebSecurityConfig.JWT_TOKEN_HEADER_PARAM);
UnsafeJwtToken token = tokenFactory.createUnsafeToken(tokenExtractor.extract(tokenPayload));
RawAccessJwtToken token = new RawAccessJwtToken(tokenExtractor.extract(tokenPayload));
return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token));
}

View File

@ -1,4 +1,4 @@
package com.svlada.security.auth.jwt;
package com.svlada.security.auth.jwt.verifier;
import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package com.svlada.security.auth.jwt;
package com.svlada.security.auth.jwt.verifier;
/**
*

View File

@ -21,7 +21,7 @@ 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.extractor.TokenExtractor;
import com.svlada.security.model.JwtTokenFactory;
import com.svlada.security.model.token.JwtTokenFactory;
/**
* WebSecurityConfig

View File

@ -21,18 +21,15 @@ import org.springframework.web.bind.annotation.RestController;
import com.svlada.entity.User;
import com.svlada.security.UserService;
import com.svlada.security.auth.jwt.TokenVerifier;
import com.svlada.security.auth.jwt.verifier.TokenVerifier;
import com.svlada.security.config.JwtSettings;
import com.svlada.security.config.WebSecurityConfig;
import com.svlada.security.exceptions.InvalidJwtToken;
import com.svlada.security.model.JwtToken;
import com.svlada.security.model.JwtTokenFactory;
import com.svlada.security.model.Scopes;
import com.svlada.security.model.UnsafeJwtToken;
import com.svlada.security.model.UserContext;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import com.svlada.security.model.token.JwtToken;
import com.svlada.security.model.token.JwtTokenFactory;
import com.svlada.security.model.token.RawAccessJwtToken;
import com.svlada.security.model.token.RefreshToken;
/**
* RefreshTokenEndpoint
@ -41,7 +38,6 @@ import io.jsonwebtoken.Jws;
*
* Aug 17, 2016
*/
@SuppressWarnings("unchecked")
@RestController
public class RefreshTokenEndpoint {
@Autowired private JwtTokenFactory tokenFactory;
@ -51,31 +47,24 @@ public class RefreshTokenEndpoint {
@RequestMapping(value="/api/auth/token", method=RequestMethod.GET, produces={ MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody JwtToken refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
UnsafeJwtToken unsafeToken = this.tokenFactory.createUnsafeToken(request.getHeader(WebSecurityConfig.JWT_TOKEN_HEADER_PARAM));
Jws<Claims> jwsClaims = unsafeToken.parseClaims(jwtSettings.getTokenSigningKey());
List<String> scopes = jwsClaims.getBody().get("scopes", List.class);
if (scopes == null || scopes.isEmpty()
|| !scopes.stream().filter(scope -> Scopes.REFRESH_TOKEN.authority().equals(scope)).findFirst().isPresent()) {
throw new InvalidJwtToken();
}
RawAccessJwtToken rawToken = new RawAccessJwtToken(request.getHeader(WebSecurityConfig.JWT_TOKEN_HEADER_PARAM));
RefreshToken refreshToken = RefreshToken.create(rawToken, jwtSettings.getTokenSigningKey()).orElseThrow(() -> new InvalidJwtToken());
String jti = jwsClaims.getBody().getId();
String jti = refreshToken.getJti();
if (!tokenVerifier.verify(jti)) {
throw new InvalidJwtToken();
}
String subject = jwsClaims.getBody().getSubject();
String subject = refreshToken.getSubject();
User user = userService.getByUsername(subject).orElseThrow(() -> new UsernameNotFoundException("User not found: " + subject));
if (user.getRoles() == null) throw new InsufficientAuthenticationException("User has no roles assigned");
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getRole().authority()))
.collect(Collectors.toList());
UserContext userContext = UserContext.create(user.getUsername(), authorities);
return tokenFactory.createSafeToken(userContext);
return tokenFactory.createAccessJwtToken(userContext);
}
}

View File

@ -2,7 +2,7 @@ package com.svlada.security.exceptions;
import org.springframework.security.core.AuthenticationException;
import com.svlada.security.model.JwtToken;
import com.svlada.security.model.token.JwtToken;
/**
*

View File

@ -1,11 +1,9 @@
package com.svlada.security.model;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
/**
*

View File

@ -1,4 +1,4 @@
package com.svlada.security.model;
package com.svlada.security.model.token;
import com.fasterxml.jackson.annotation.JsonIgnore;
@ -11,11 +11,11 @@ import io.jsonwebtoken.Claims;
*
* May 31, 2016
*/
public final class SafeJwtToken implements JwtToken {
public final class AccessJwtToken implements JwtToken {
private final String rawToken;
@JsonIgnore private Claims claims;
protected SafeJwtToken(final String token, Claims claims) {
protected AccessJwtToken(final String token, Claims claims) {
this.rawToken = token;
this.claims = claims;
}

View File

@ -1,4 +1,4 @@
package com.svlada.security.model;
package com.svlada.security.model.token;
public interface JwtToken {
String getToken();

View File

@ -1,4 +1,4 @@
package com.svlada.security.model;
package com.svlada.security.model.token;
import java.util.Arrays;
import java.util.UUID;
@ -10,6 +10,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.svlada.security.config.JwtSettings;
import com.svlada.security.model.Scopes;
import com.svlada.security.model.UserContext;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
@ -38,14 +40,12 @@ public class JwtTokenFactory {
* @param roles
* @return
*/
public SafeJwtToken createSafeToken(UserContext userContext) {
if (StringUtils.isBlank(userContext.getUsername())) {
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()) {
if (userContext.getAuthorities() == null || userContext.getAuthorities().isEmpty())
throw new IllegalArgumentException("User doesn't have any privileges");
}
Claims claims = Jwts.claims().setSubject(userContext.getUsername());
claims.put("scopes", userContext.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
@ -60,7 +60,7 @@ public class JwtTokenFactory {
.signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey())
.compact();
return new SafeJwtToken(token, claims);
return new AccessJwtToken(token, claims);
}
public JwtToken createRefreshToken(UserContext userContext) {
@ -82,18 +82,6 @@ public class JwtTokenFactory {
.signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey())
.compact();
return new SafeJwtToken(token, claims);
}
/**
* Unsafe version of JWT token is created.
*
* <strong>WARNING:</strong> Token signature validation is not performed.
*
* @param tokenPayload
* @return unsafe version of JWT token.
*/
public UnsafeJwtToken createUnsafeToken(String tokenPayload) {
return new UnsafeJwtToken(tokenPayload);
return new AccessJwtToken(token, claims);
}
}

View File

@ -1,4 +1,4 @@
package com.svlada.security.model;
package com.svlada.security.model.token;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -14,12 +14,12 @@ import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
public class UnsafeJwtToken implements JwtToken {
private static Logger logger = LoggerFactory.getLogger(UnsafeJwtToken.class);
public class RawAccessJwtToken implements JwtToken {
private static Logger logger = LoggerFactory.getLogger(RawAccessJwtToken.class);
private String token;
public UnsafeJwtToken(String token) {
public RawAccessJwtToken(String token) {
this.token = token;
}

View File

@ -0,0 +1,66 @@
package com.svlada.security.model.token;
import java.util.List;
import java.util.Optional;
import org.springframework.security.authentication.BadCredentialsException;
import com.svlada.security.exceptions.JwtExpiredTokenException;
import com.svlada.security.model.Scopes;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
/**
*
* @author vladimir.stankovic
*
* Aug 19, 2016
*/
public class RefreshToken implements JwtToken {
private Jws<Claims> claims;
private RefreshToken(Jws<Claims> claims) {
this.claims = claims;
}
/**
* Creates and validates Refresh token
*
* @param token
* @param signingKey
*
* @throws BadCredentialsException
* @throws JwtExpiredTokenException
*
* @return
*/
public static Optional<RefreshToken> create(RawAccessJwtToken token, String signingKey) {
Jws<Claims> claims = token.parseClaims(signingKey);
List<String> scopes = claims.getBody().get("scopes", List.class);
if (scopes == null || scopes.isEmpty()
|| !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;
}
public Jws<Claims> getClaims() {
return claims;
}
public String getJti() {
return claims.getBody().getId();
}
public String getSubject() {
return claims.getBody().getSubject();
}
}