Code refactor.
This commit is contained in:
parent
2f8988ad4f
commit
a3e9b93382
@ -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
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.svlada.security.auth.jwt;
|
||||
package com.svlada.security.auth.jwt.verifier;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.svlada.security.auth.jwt;
|
||||
package com.svlada.security.auth.jwt.verifier;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.svlada.security.model;
|
||||
package com.svlada.security.model.token;
|
||||
|
||||
public interface JwtToken {
|
||||
String getToken();
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user