From f8af4297bcd19f4496c64a5529df0b8b912901b0 Mon Sep 17 00:00:00 2001 From: svlada Date: Fri, 5 Aug 2016 16:51:59 +0200 Subject: [PATCH] This commit has compile errors. I'm in a hurry to catch a train. --- etc/blog.md | 2 +- .../auth/jwt/JwtAuthenticationProvider.java | 42 ++++++----- .../auth/jwt/RefreshTokenAuthStrategy.java | 69 +++++++++++++++++++ .../security/auth/jwt/TokenAuthStrategy.java | 3 +- .../svlada/security/config/JwtSettings.java | 13 ++++ .../security/model/JwtTokenFactory.java | 16 ++--- .../svlada/security/model/UnsafeJwtToken.java | 23 ++----- .../svlada/security/model/UserContext.java | 8 +-- .../svlada/security/service/UserService.java | 8 ++- 9 files changed, 130 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/svlada/security/auth/jwt/RefreshTokenAuthStrategy.java diff --git a/etc/blog.md b/etc/blog.md index 391c313..a1488cd 100644 --- a/etc/blog.md +++ b/etc/blog.md @@ -413,7 +413,7 @@ public class AjaxAwareAuthenticationFailureHandler implements AuthenticationFail #### WebSecurityConfig - Initial version to support AJAX based login -This is first version of WebSecurityConfig. We will add more configuration to it once we start with showcase of JWT Authentication flow. +This is first version of WebSecurityConfig. We will add more configuration to it once we start with showcase of JWT Authentication flow. ``` @Configuration diff --git a/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java b/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java index fc3062d..dccd6fa 100644 --- a/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java +++ b/src/main/java/com/svlada/security/auth/jwt/JwtAuthenticationProvider.java @@ -1,9 +1,12 @@ package com.svlada.security.auth.jwt; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; +import org.joda.time.DateTime; +import org.joda.time.Minutes; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; @@ -17,10 +20,14 @@ import com.svlada.security.auth.JwtAuthenticationToken; import com.svlada.security.config.JwtSettings; import com.svlada.security.exceptions.JwtExpiredTokenException; import com.svlada.security.model.JwtToken; +import com.svlada.security.model.SafeJwtToken; import com.svlada.security.model.UnsafeJwtToken; +import com.svlada.security.model.UserContext; +import com.svlada.security.service.UserService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jws; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; @@ -35,36 +42,37 @@ import io.jsonwebtoken.UnsupportedJwtException; */ @Component public class JwtAuthenticationProvider implements AuthenticationProvider { - private final JwtSettings jwtSettings; - private final TokenAuthStrategy tokenAuthStrategy; + private final TokenAuthStrategy tokenAuthStrategy; + private final UserService userService; + private final JwtSettings jwtSettings; @Autowired - public JwtAuthenticationProvider(JwtSettings jwtSettings, TokenAuthStrategy tokenAuthStrategy) { - this.jwtSettings = jwtSettings; + public JwtAuthenticationProvider(TokenAuthStrategy tokenAuthStrategy, UserService userService, JwtSettings jwtSettings) { this.tokenAuthStrategy = tokenAuthStrategy; + this.userService = userService; + this.jwtSettings = jwtSettings; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - UnsafeJwtToken token = ((JwtAuthenticationToken) authentication).getUnsafeToken(); - - SafeToken safeToken = token.authenticate(tokenAuthStrategy); + UnsafeJwtToken unsafeToken = ((JwtAuthenticationToken) authentication).getUnsafeToken(); try { - token.validateToken(jwtSettings.getTokenSigningKey()); + Jws jwsClaims = unsafeToken.parse(jwtSettings.getTokenSigningKey()); } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { throw new BadCredentialsException("Invalid JWT token: ", ex); } catch (ExpiredJwtException expiredEx) { - throw new JwtExpiredTokenException(token, "Token expired.", expiredEx); - } + Date expDateTime = expiredEx.getClaims().getExpiration(); + + if (expDate != null && tokenAuthStrategy.isExpired(expDate)) { + + } + } - Claims claims = token.claims(jwtSettings.getTokenSigningKey()); - ArrayList rawAuthorities = claims.get("roles", ArrayList.class); - - List authorities = rawAuthorities.stream() - .map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList()); - - JwtAuthenticationToken authToken = new JwtAuthenticationToken(token, authorities, claims.getSubject()); + SafeJwtToken safeToken = ; + Claims claims = safeToken.getClaims(); + + JwtAuthenticationToken authToken = new JwtAuthenticationToken(userContext, safeToken, userContext.getAuthorities()); return authToken; } diff --git a/src/main/java/com/svlada/security/auth/jwt/RefreshTokenAuthStrategy.java b/src/main/java/com/svlada/security/auth/jwt/RefreshTokenAuthStrategy.java new file mode 100644 index 0000000..add36fd --- /dev/null +++ b/src/main/java/com/svlada/security/auth/jwt/RefreshTokenAuthStrategy.java @@ -0,0 +1,69 @@ +package com.svlada.security.auth.jwt; + +import java.util.Date; + +import org.joda.time.DateTime; +import org.joda.time.Minutes; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.stereotype.Component; + +import com.svlada.security.config.JwtSettings; +import com.svlada.security.exceptions.JwtExpiredTokenException; +import com.svlada.security.model.JwtTokenFactory; +import com.svlada.security.model.SafeJwtToken; +import com.svlada.security.model.UnsafeJwtToken; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.UnsupportedJwtException; + +/** + * + * @author vladimir.stankovic + * + * Aug 5, 2016 + */ +@Component +public class RefreshTokenAuthStrategy implements TokenAuthStrategy { + private final JwtSettings jwtSettings; + private final JwtTokenFactory tokenFactory; + + @Autowired + public RefreshTokenAuthStrategy(JwtSettings jwtSettings, JwtTokenFactory tokenFactory) { + this.jwtSettings = jwtSettings; + this.tokenFactory = tokenFactory; + } + + @Override + public SafeJwtToken authenticate(UnsafeJwtToken token) { + try { + Jws jwsClaims = token.parse(jwtSettings.getTokenSigningKey()); + return tokenFactory.createSafeToken(token.getToken(), jwsClaims.getBody()); + } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { + throw new BadCredentialsException("Invalid JWT token: ", ex); + } catch (ExpiredJwtException expiredEx) { + Date expDateTime = expiredEx.getClaims().getExpiration(); + + if (expDateTime == null) { + throw new BadCredentialsException("Expiry time is not set"); + } + + DateTime expirationTime = new DateTime(expiredEx.getClaims().getExpiration()); + DateTime currentTime = DateTime.now(); + + if (Minutes.minutesBetween(currentTime, expirationTime).isGreaterThan(Minutes.minutes(jwtSettings.getTokenValidationTimeframe()))) { + throw new JwtExpiredTokenException(token, "JWT token has expired", expiredEx); + } + + return refreshToken(); + } + } + + public SafeJwtToken refreshToken() { + return null; + } +} diff --git a/src/main/java/com/svlada/security/auth/jwt/TokenAuthStrategy.java b/src/main/java/com/svlada/security/auth/jwt/TokenAuthStrategy.java index ca466cf..b579f23 100644 --- a/src/main/java/com/svlada/security/auth/jwt/TokenAuthStrategy.java +++ b/src/main/java/com/svlada/security/auth/jwt/TokenAuthStrategy.java @@ -1,6 +1,7 @@ package com.svlada.security.auth.jwt; import com.svlada.security.model.SafeJwtToken; +import com.svlada.security.model.UnsafeJwtToken; /** * @@ -9,5 +10,5 @@ import com.svlada.security.model.SafeJwtToken; * Aug 5, 2016 */ public interface TokenAuthStrategy { - public SafeJwtToken authenticate(String token); + public SafeJwtToken authenticate(UnsafeJwtToken token); } diff --git a/src/main/java/com/svlada/security/config/JwtSettings.java b/src/main/java/com/svlada/security/config/JwtSettings.java index b617f28..de7d23a 100644 --- a/src/main/java/com/svlada/security/config/JwtSettings.java +++ b/src/main/java/com/svlada/security/config/JwtSettings.java @@ -21,6 +21,19 @@ public class JwtSettings { */ private String tokenSigningKey; + /** + * {@link JwtToken} can be refreshed during this timeframe. + */ + private Integer tokenValidationTimeframe; + + public Integer getTokenValidationTimeframe() { + return tokenValidationTimeframe; + } + + public void setTokenValidationTimeframe(Integer tokenValidationTimeframe) { + this.tokenValidationTimeframe = tokenValidationTimeframe; + } + public Integer getTokenExpirationTime() { return tokenExpirationTime; } diff --git a/src/main/java/com/svlada/security/model/JwtTokenFactory.java b/src/main/java/com/svlada/security/model/JwtTokenFactory.java index bbf9f4c..b28d476 100644 --- a/src/main/java/com/svlada/security/model/JwtTokenFactory.java +++ b/src/main/java/com/svlada/security/model/JwtTokenFactory.java @@ -14,6 +14,7 @@ import com.svlada.security.config.JwtSettings; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.impl.DefaultClaims; import io.jsonwebtoken.lang.Collections; /** @@ -39,24 +40,18 @@ public class JwtTokenFactory { * @param roles * @return */ - public SafeJwtToken createSafeToken(UserContext userContext, final Collection roles) { + public SafeJwtToken createSafeToken(UserContext userContext) { if (StringUtils.isBlank(userContext.getUsername())) { throw new IllegalArgumentException("Cannot create JWT Token without username"); } - if (Collections.isEmpty(roles)) { - throw new IllegalArgumentException("Cannot create JWT Token without roles"); - } - DateTime currentTime = new DateTime(); - Claims claims = Jwts.claims(); - claims.put("roles", AuthorityUtils.authorityListToSet(roles)); + Claims claims = Jwts.claims().setSubject(userContext.getUsername()); String token = Jwts.builder() .setClaims(claims) .setIssuer(settings.getTokenIssuer()) - .setSubject(userContext.getUsername()) .setIssuedAt(currentTime.toDate()) .setExpiration(currentTime.plusMinutes(settings.getTokenExpirationTime()).toDate()) .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey()) @@ -64,6 +59,11 @@ public class JwtTokenFactory { return new SafeJwtToken(token, claims); } + + public SafeJwtToken createSafeToken(String token, Claims claims) { + return new SafeJwtToken(token, claims); + } + /** * Unsafe version of JWT token is created. diff --git a/src/main/java/com/svlada/security/model/UnsafeJwtToken.java b/src/main/java/com/svlada/security/model/UnsafeJwtToken.java index 38eab9a..9796edb 100644 --- a/src/main/java/com/svlada/security/model/UnsafeJwtToken.java +++ b/src/main/java/com/svlada/security/model/UnsafeJwtToken.java @@ -1,8 +1,7 @@ package com.svlada.security.model; -import com.svlada.security.auth.jwt.TokenAuthStrategy; - import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; public class UnsafeJwtToken implements JwtToken { @@ -16,24 +15,10 @@ public class UnsafeJwtToken implements JwtToken { * Validates JWT Token signature. * */ - public void validateToken(String signingKey) { - Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); + public Jws parse(String signingKey) { + return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); } - - /** - * Extract Claims object from the rawToken. - * - * @param signingKey - * @return - */ - public Claims parseClaims(String signingKey) { - return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(token).getBody(); - } - - public SafeJwtToken authenticate(TokenAuthStrategy strategy) { - return strategy.authenticate(this.token); - } - + @Override public String getToken() { return token; diff --git a/src/main/java/com/svlada/security/model/UserContext.java b/src/main/java/com/svlada/security/model/UserContext.java index ee126c8..80ab314 100644 --- a/src/main/java/com/svlada/security/model/UserContext.java +++ b/src/main/java/com/svlada/security/model/UserContext.java @@ -12,12 +12,10 @@ import org.springframework.security.core.GrantedAuthority; */ public class UserContext { private final String username; - private final String email; private final List authorities; - public UserContext(String username, String email, List authorities) { + public UserContext(String username, List authorities) { this.username = username; - this.email = email; this.authorities = authorities; } @@ -25,10 +23,6 @@ public class UserContext { return username; } - public String getEmail() { - return email; - } - public List getAuthorities() { return authorities; } diff --git a/src/main/java/com/svlada/security/service/UserService.java b/src/main/java/com/svlada/security/service/UserService.java index ac8b7b6..e8ca314 100644 --- a/src/main/java/com/svlada/security/service/UserService.java +++ b/src/main/java/com/svlada/security/service/UserService.java @@ -22,6 +22,12 @@ public class UserService { public UserContext loadUser(String username, String password) { List authorities = new ArrayList(); authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.authority())); - return new UserContext(username, "svlada@gmail.com", authorities); + return new UserContext(username, authorities); + } + + public UserContext loadUser(String username) { + List authorities = new ArrayList(); + authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.authority())); + return new UserContext(username, authorities); } }