From 6bd3b381d15d61ca040d284587a03be96fcfc855 Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Wed, 13 Jul 2016 23:23:21 -0400 Subject: [PATCH] Added SecretService for managing good secrets. Updated all secret operations to use SecretService. Added controller to return and set secrets. --- .../jjwtfun/config/CSRFConfig.java | 9 ++- .../config/JWTCsrfTokenRepository.java | 28 +++---- .../jjwtfun/config/WebSecurityConfig.java | 11 ++- .../controller/DynamicJWTController.java | 18 ++--- .../jjwtfun/controller/SecretsController.java | 35 +++++++++ .../controller/StaticJWTController.java | 16 ++-- .../jjwtfun/model/JwtResponse.java | 14 ++-- .../jjwtfun/service/SecretService.java | 74 +++++++++++++++++++ 8 files changed, 156 insertions(+), 49 deletions(-) create mode 100644 jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/SecretsController.java create mode 100644 jjwt/src/main/java/io/jsonwebtoken/jjwtfun/service/SecretService.java diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/CSRFConfig.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/CSRFConfig.java index b3d3bdedfd..8f88cc9ead 100644 --- a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/CSRFConfig.java +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/CSRFConfig.java @@ -1,6 +1,7 @@ package io.jsonwebtoken.jjwtfun.config; -import org.springframework.beans.factory.annotation.Value; +import io.jsonwebtoken.jjwtfun.service.SecretService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,12 +10,12 @@ import org.springframework.security.web.csrf.CsrfTokenRepository; @Configuration public class CSRFConfig { - @Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }") - String secret; + @Autowired + SecretService secretService; @Bean @ConditionalOnMissingBean public CsrfTokenRepository jwtCsrfTokenRepository() { - return new JWTCsrfTokenRepository(secret); + return new JWTCsrfTokenRepository(secretService.getHS256Secret()); } } diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/JWTCsrfTokenRepository.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/JWTCsrfTokenRepository.java index ce55f2a092..efc5bc5839 100644 --- a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/JWTCsrfTokenRepository.java +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/JWTCsrfTokenRepository.java @@ -2,6 +2,7 @@ package io.jsonwebtoken.jjwtfun.config; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.impl.TextCodec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.web.csrf.CsrfToken; @@ -11,7 +12,6 @@ import org.springframework.security.web.csrf.DefaultCsrfToken; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.UUID; @@ -20,10 +20,10 @@ public class JWTCsrfTokenRepository implements CsrfTokenRepository { private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = CSRFConfig.class.getName().concat(".CSRF_TOKEN"); private static final Logger log = LoggerFactory.getLogger(JWTCsrfTokenRepository.class); - private String secret; + private byte[] secret; - public JWTCsrfTokenRepository(String secret) { - this.secret = secret; + public JWTCsrfTokenRepository(String base64Secret) { + this.secret = TextCodec.BASE64.decode(base64Secret); } @Override @@ -33,19 +33,13 @@ public class JWTCsrfTokenRepository implements CsrfTokenRepository { Date now = new Date(); Date exp = new Date(System.currentTimeMillis() + (1000*30)); // 30 seconds - String token; - try { - token = Jwts.builder() - .setId(id) - .setIssuedAt(now) - .setNotBefore(now) - .setExpiration(exp) - .signWith(SignatureAlgorithm.HS256, secret.getBytes("UTF-8")) - .compact(); - } catch (UnsupportedEncodingException e) { - log.error("Unable to create CSRf JWT: {}", e.getMessage(), e); - token = id; - } + String token = Jwts.builder() + .setId(id) + .setIssuedAt(now) + .setNotBefore(now) + .setExpiration(exp) + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token); } diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/WebSecurityConfig.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/WebSecurityConfig.java index 3e7ed45724..638cd0abab 100644 --- a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/WebSecurityConfig.java +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/config/WebSecurityConfig.java @@ -2,6 +2,8 @@ package io.jsonwebtoken.jjwtfun.config; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.impl.TextCodec; +import io.jsonwebtoken.jjwtfun.service.SecretService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @@ -22,12 +24,12 @@ import java.io.IOException; @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - @Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }") - String secret; - @Autowired CsrfTokenRepository jwtCsrfTokenRepository; + @Autowired + SecretService secretService; + @Override protected void configure(HttpSecurity http) throws Exception { http @@ -37,6 +39,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .ignoringAntMatchers("/dynamic-builder-general") .ignoringAntMatchers("/dynamic-builder-specific") .ignoringAntMatchers("/dynamic-builder-compress") + .ignoringAntMatchers("/set-secrets") .and().authorizeRequests() .antMatchers("/**") .permitAll(); @@ -55,7 +58,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { if ("POST".equals(request.getMethod()) && token != null) { try { Jwts.parser() - .setSigningKey(secret.getBytes("UTF-8")) + .setSigningKeyResolver(secretService.getSigningKeyResolver()) .parseClaimsJws(token.getToken()); } catch (JwtException e) { // most likely an ExpiredJwtException, but this will handle any diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/DynamicJWTController.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/DynamicJWTController.java index cfac0af54e..82ae0f01d1 100644 --- a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/DynamicJWTController.java +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/DynamicJWTController.java @@ -1,17 +1,15 @@ package io.jsonwebtoken.jjwtfun.controller; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.impl.compression.CompressionCodecs; import io.jsonwebtoken.jjwtfun.model.JwtResponse; -import org.springframework.beans.factory.annotation.Value; +import io.jsonwebtoken.jjwtfun.service.SecretService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.UnsupportedEncodingException; @@ -20,12 +18,12 @@ import java.util.Date; import java.util.Map; import static org.springframework.web.bind.annotation.RequestMethod.POST; -import static org.springframework.web.bind.annotation.RequestMethod.GET; @RestController public class DynamicJWTController extends BaseController { - @Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }") - String secret; + + @Autowired + SecretService secretService; @RequestMapping(value = "/dynamic-builder-general", method = POST) public JwtResponse dynamicBuilderGeneric(@RequestBody Map claims) throws UnsupportedEncodingException { @@ -33,7 +31,7 @@ public class DynamicJWTController extends BaseController { .setClaims(claims) .signWith( SignatureAlgorithm.HS256, - secret.getBytes("UTF-8") + secretService.getHS256Secret() ) .compact(); return new JwtResponse(jws); @@ -46,7 +44,7 @@ public class DynamicJWTController extends BaseController { .compressWith(CompressionCodecs.DEFLATE) .signWith( SignatureAlgorithm.HS256, - secret.getBytes("UTF-8") + secretService.getHS256Secret() ) .compact(); return new JwtResponse(jws); @@ -91,7 +89,7 @@ public class DynamicJWTController extends BaseController { } }); - builder.signWith(SignatureAlgorithm.HS256, secret.getBytes("UTF-8")); + builder.signWith(SignatureAlgorithm.HS256, secretService.getHS256Secret()); return new JwtResponse(builder.compact()); } diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/SecretsController.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/SecretsController.java new file mode 100644 index 0000000000..c962e009d7 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/SecretsController.java @@ -0,0 +1,35 @@ +package io.jsonwebtoken.jjwtfun.controller; + +import io.jsonwebtoken.jjwtfun.service.SecretService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +@RestController +public class SecretsController { + + @Autowired + SecretService secretService; + + @RequestMapping(value = "/get-secrets", method = GET) + public Map getSecrets() { + return secretService.getSecrets(); + } + + @RequestMapping(value = "/refresh-secrets", method = GET) + public Map refreshSecrets() { + return secretService.refreshSecrets(); + } + + @RequestMapping(value = "/set-secrets", method = POST) + public Map setSecrets(@RequestBody Map secrets) { + secretService.setSecrets(secrets); + return secretService.getSecrets(); + } +} diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/StaticJWTController.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/StaticJWTController.java index c363b59e13..489c85a32b 100644 --- a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/StaticJWTController.java +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/controller/StaticJWTController.java @@ -4,8 +4,10 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.impl.TextCodec; import io.jsonwebtoken.jjwtfun.model.JwtResponse; -import org.springframework.beans.factory.annotation.Value; +import io.jsonwebtoken.jjwtfun.service.SecretService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -19,12 +21,11 @@ import static org.springframework.web.bind.annotation.RequestMethod.GET; @RestController public class StaticJWTController extends BaseController { - @Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }") - String secret; + @Autowired + SecretService secretService; @RequestMapping(value = "/static-builder", method = GET) public JwtResponse fixedBuilder() throws UnsupportedEncodingException { - String jws = Jwts.builder() .setIssuer("Stormpath") .setSubject("msilverman") @@ -34,7 +35,7 @@ public class StaticJWTController extends BaseController { .setExpiration(Date.from(Instant.ofEpochSecond(4622470422L))) // Sat Jun 24 2116 15:33:42 GMT-0400 (EDT) .signWith( SignatureAlgorithm.HS256, - "secret".getBytes("UTF-8") + TextCodec.BASE64.decode(secretService.getHS256Secret()) ) .compact(); @@ -43,8 +44,9 @@ public class StaticJWTController extends BaseController { @RequestMapping(value = "/parser", method = GET) public JwtResponse parser(@RequestParam String jwt) throws UnsupportedEncodingException { + Jws claims = Jwts.parser() - .setSigningKey(secret.getBytes("UTF-8")) + .setSigningKeyResolver(secretService.getSigningKeyResolver()) .parseClaimsJws(jwt); return new JwtResponse(claims); @@ -55,7 +57,7 @@ public class StaticJWTController extends BaseController { Jws claims = Jwts.parser() .requireIssuer("Stormpath") .require("hasMotorcycle", true) - .setSigningKey(secret.getBytes("UTF-8")) + .setSigningKeyResolver(secretService.getSigningKeyResolver()) .parseClaimsJws(jwt); return new JwtResponse(claims); diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/model/JwtResponse.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/model/JwtResponse.java index 4c664bc5f7..491f003289 100644 --- a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/model/JwtResponse.java +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/model/JwtResponse.java @@ -10,7 +10,7 @@ public class JwtResponse { private Status status; private String exceptionType; private String jwt; - private Jws claims; + private Jws jws; public enum Status { SUCCESS, ERROR @@ -23,8 +23,8 @@ public class JwtResponse { this.status = Status.SUCCESS; } - public JwtResponse(Jws claims) { - this.claims = claims; + public JwtResponse(Jws jws) { + this.jws = jws; this.status = Status.SUCCESS; } @@ -60,11 +60,11 @@ public class JwtResponse { this.jwt = jwt; } - public Jws getClaims() { - return claims; + public Jws getJws() { + return jws; } - public void setClaims(Jws claims) { - this.claims = claims; + public void setJws(Jws jws) { + this.jws = jws; } } diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/service/SecretService.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/service/SecretService.java new file mode 100644 index 0000000000..8af538f90e --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/service/SecretService.java @@ -0,0 +1,74 @@ +package io.jsonwebtoken.jjwtfun.service; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.SigningKeyResolver; +import io.jsonwebtoken.SigningKeyResolverAdapter; +import io.jsonwebtoken.impl.TextCodec; +import io.jsonwebtoken.impl.crypto.MacProvider; +import io.jsonwebtoken.lang.Assert; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.crypto.SecretKey; +import java.util.HashMap; +import java.util.Map; + +@Service +public class SecretService { + + private Map secrets = new HashMap<>(); + + private SigningKeyResolver signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + return TextCodec.BASE64.decode(secrets.get(header.getAlgorithm())); + } + }; + + @PostConstruct + public void setup() { + refreshSecrets(); + } + + public SigningKeyResolver getSigningKeyResolver() { + return signingKeyResolver; + } + + public Map getSecrets() { + return secrets; + } + + public void setSecrets(Map secrets) { + Assert.notNull(secrets); + Assert.isTrue(secrets.get(SignatureAlgorithm.HS256.getValue()) != null); + Assert.isTrue(secrets.get(SignatureAlgorithm.HS384.getValue()) != null); + Assert.isTrue(secrets.get(SignatureAlgorithm.HS512.getValue()) != null); + + this.secrets = secrets; + } + + public String getHS256Secret() { + return secrets.get(SignatureAlgorithm.HS256.getValue()); + } + + public String getHS384Secret() { + return secrets.get(SignatureAlgorithm.HS384.getValue()); + } + + public String getHS512Secret() { + return secrets.get(SignatureAlgorithm.HS512.getValue()); + } + + + public Map refreshSecrets() { + SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256); + secrets.put(SignatureAlgorithm.HS256.getValue(), TextCodec.BASE64.encode(key.getEncoded())); + key = MacProvider.generateKey(SignatureAlgorithm.HS384); + secrets.put(SignatureAlgorithm.HS384.getValue(), TextCodec.BASE64.encode(key.getEncoded())); + key = MacProvider.generateKey(SignatureAlgorithm.HS512); + secrets.put(SignatureAlgorithm.HS512.getValue(), TextCodec.BASE64.encode(key.getEncoded())); + return secrets; + } +}