Added SecretService for managing good secrets. Updated all secret operations to use SecretService. Added controller to return and set secrets.

This commit is contained in:
Micah Silverman 2016-07-13 23:23:21 -04:00
parent 532437ae46
commit 6bd3b381d1
8 changed files with 156 additions and 49 deletions

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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<String, Object> 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());
}

View File

@ -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<String, String> getSecrets() {
return secretService.getSecrets();
}
@RequestMapping(value = "/refresh-secrets", method = GET)
public Map<String, String> refreshSecrets() {
return secretService.refreshSecrets();
}
@RequestMapping(value = "/set-secrets", method = POST)
public Map<String, String> setSecrets(@RequestBody Map<String, String> secrets) {
secretService.setSecrets(secrets);
return secretService.getSecrets();
}
}

View File

@ -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> 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> claims = Jwts.parser()
.requireIssuer("Stormpath")
.require("hasMotorcycle", true)
.setSigningKey(secret.getBytes("UTF-8"))
.setSigningKeyResolver(secretService.getSigningKeyResolver())
.parseClaimsJws(jwt);
return new JwtResponse(claims);

View File

@ -10,7 +10,7 @@ public class JwtResponse {
private Status status;
private String exceptionType;
private String jwt;
private Jws<Claims> claims;
private Jws<Claims> jws;
public enum Status {
SUCCESS, ERROR
@ -23,8 +23,8 @@ public class JwtResponse {
this.status = Status.SUCCESS;
}
public JwtResponse(Jws<Claims> claims) {
this.claims = claims;
public JwtResponse(Jws<Claims> jws) {
this.jws = jws;
this.status = Status.SUCCESS;
}
@ -60,11 +60,11 @@ public class JwtResponse {
this.jwt = jwt;
}
public Jws<Claims> getClaims() {
return claims;
public Jws<Claims> getJws() {
return jws;
}
public void setClaims(Jws<Claims> claims) {
this.claims = claims;
public void setJws(Jws<Claims> jws) {
this.jws = jws;
}
}

View File

@ -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<String, String> 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<String, String> getSecrets() {
return secrets;
}
public void setSecrets(Map<String, String> 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<String, String> 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;
}
}