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; 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.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -9,12 +10,12 @@ import org.springframework.security.web.csrf.CsrfTokenRepository;
@Configuration @Configuration
public class CSRFConfig { public class CSRFConfig {
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }") @Autowired
String secret; SecretService secretService;
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public CsrfTokenRepository jwtCsrfTokenRepository() { 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.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.security.web.csrf.CsrfToken; 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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.util.Date; import java.util.Date;
import java.util.UUID; 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 String DEFAULT_CSRF_TOKEN_ATTR_NAME = CSRFConfig.class.getName().concat(".CSRF_TOKEN");
private static final Logger log = LoggerFactory.getLogger(JWTCsrfTokenRepository.class); private static final Logger log = LoggerFactory.getLogger(JWTCsrfTokenRepository.class);
private String secret; private byte[] secret;
public JWTCsrfTokenRepository(String secret) { public JWTCsrfTokenRepository(String base64Secret) {
this.secret = secret; this.secret = TextCodec.BASE64.decode(base64Secret);
} }
@Override @Override
@ -33,19 +33,13 @@ public class JWTCsrfTokenRepository implements CsrfTokenRepository {
Date now = new Date(); Date now = new Date();
Date exp = new Date(System.currentTimeMillis() + (1000*30)); // 30 seconds Date exp = new Date(System.currentTimeMillis() + (1000*30)); // 30 seconds
String token; String token = Jwts.builder()
try { .setId(id)
token = Jwts.builder() .setIssuedAt(now)
.setId(id) .setNotBefore(now)
.setIssuedAt(now) .setExpiration(exp)
.setNotBefore(now) .signWith(SignatureAlgorithm.HS256, secret)
.setExpiration(exp) .compact();
.signWith(SignatureAlgorithm.HS256, secret.getBytes("UTF-8"))
.compact();
} catch (UnsupportedEncodingException e) {
log.error("Unable to create CSRf JWT: {}", e.getMessage(), e);
token = id;
}
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token); 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.JwtException;
import io.jsonwebtoken.Jwts; 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.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -22,12 +24,12 @@ import java.io.IOException;
@Configuration @Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }")
String secret;
@Autowired @Autowired
CsrfTokenRepository jwtCsrfTokenRepository; CsrfTokenRepository jwtCsrfTokenRepository;
@Autowired
SecretService secretService;
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http http
@ -37,6 +39,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.ignoringAntMatchers("/dynamic-builder-general") .ignoringAntMatchers("/dynamic-builder-general")
.ignoringAntMatchers("/dynamic-builder-specific") .ignoringAntMatchers("/dynamic-builder-specific")
.ignoringAntMatchers("/dynamic-builder-compress") .ignoringAntMatchers("/dynamic-builder-compress")
.ignoringAntMatchers("/set-secrets")
.and().authorizeRequests() .and().authorizeRequests()
.antMatchers("/**") .antMatchers("/**")
.permitAll(); .permitAll();
@ -55,7 +58,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
if ("POST".equals(request.getMethod()) && token != null) { if ("POST".equals(request.getMethod()) && token != null) {
try { try {
Jwts.parser() Jwts.parser()
.setSigningKey(secret.getBytes("UTF-8")) .setSigningKeyResolver(secretService.getSigningKeyResolver())
.parseClaimsJws(token.getToken()); .parseClaimsJws(token.getToken());
} catch (JwtException e) { } catch (JwtException e) {
// most likely an ExpiredJwtException, but this will handle any // most likely an ExpiredJwtException, but this will handle any

View File

@ -1,17 +1,15 @@
package io.jsonwebtoken.jjwtfun.controller; package io.jsonwebtoken.jjwtfun.controller;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtException; import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.compression.CompressionCodecs; import io.jsonwebtoken.impl.compression.CompressionCodecs;
import io.jsonwebtoken.jjwtfun.model.JwtResponse; 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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@ -20,12 +18,12 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import static org.springframework.web.bind.annotation.RequestMethod.POST; import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
@RestController @RestController
public class DynamicJWTController extends BaseController { public class DynamicJWTController extends BaseController {
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }")
String secret; @Autowired
SecretService secretService;
@RequestMapping(value = "/dynamic-builder-general", method = POST) @RequestMapping(value = "/dynamic-builder-general", method = POST)
public JwtResponse dynamicBuilderGeneric(@RequestBody Map<String, Object> claims) throws UnsupportedEncodingException { public JwtResponse dynamicBuilderGeneric(@RequestBody Map<String, Object> claims) throws UnsupportedEncodingException {
@ -33,7 +31,7 @@ public class DynamicJWTController extends BaseController {
.setClaims(claims) .setClaims(claims)
.signWith( .signWith(
SignatureAlgorithm.HS256, SignatureAlgorithm.HS256,
secret.getBytes("UTF-8") secretService.getHS256Secret()
) )
.compact(); .compact();
return new JwtResponse(jws); return new JwtResponse(jws);
@ -46,7 +44,7 @@ public class DynamicJWTController extends BaseController {
.compressWith(CompressionCodecs.DEFLATE) .compressWith(CompressionCodecs.DEFLATE)
.signWith( .signWith(
SignatureAlgorithm.HS256, SignatureAlgorithm.HS256,
secret.getBytes("UTF-8") secretService.getHS256Secret()
) )
.compact(); .compact();
return new JwtResponse(jws); 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()); 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.Jws;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.jjwtfun.model.JwtResponse; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -19,12 +21,11 @@ import static org.springframework.web.bind.annotation.RequestMethod.GET;
@RestController @RestController
public class StaticJWTController extends BaseController { public class StaticJWTController extends BaseController {
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }") @Autowired
String secret; SecretService secretService;
@RequestMapping(value = "/static-builder", method = GET) @RequestMapping(value = "/static-builder", method = GET)
public JwtResponse fixedBuilder() throws UnsupportedEncodingException { public JwtResponse fixedBuilder() throws UnsupportedEncodingException {
String jws = Jwts.builder() String jws = Jwts.builder()
.setIssuer("Stormpath") .setIssuer("Stormpath")
.setSubject("msilverman") .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) .setExpiration(Date.from(Instant.ofEpochSecond(4622470422L))) // Sat Jun 24 2116 15:33:42 GMT-0400 (EDT)
.signWith( .signWith(
SignatureAlgorithm.HS256, SignatureAlgorithm.HS256,
"secret".getBytes("UTF-8") TextCodec.BASE64.decode(secretService.getHS256Secret())
) )
.compact(); .compact();
@ -43,8 +44,9 @@ public class StaticJWTController extends BaseController {
@RequestMapping(value = "/parser", method = GET) @RequestMapping(value = "/parser", method = GET)
public JwtResponse parser(@RequestParam String jwt) throws UnsupportedEncodingException { public JwtResponse parser(@RequestParam String jwt) throws UnsupportedEncodingException {
Jws<Claims> claims = Jwts.parser() Jws<Claims> claims = Jwts.parser()
.setSigningKey(secret.getBytes("UTF-8")) .setSigningKeyResolver(secretService.getSigningKeyResolver())
.parseClaimsJws(jwt); .parseClaimsJws(jwt);
return new JwtResponse(claims); return new JwtResponse(claims);
@ -55,7 +57,7 @@ public class StaticJWTController extends BaseController {
Jws<Claims> claims = Jwts.parser() Jws<Claims> claims = Jwts.parser()
.requireIssuer("Stormpath") .requireIssuer("Stormpath")
.require("hasMotorcycle", true) .require("hasMotorcycle", true)
.setSigningKey(secret.getBytes("UTF-8")) .setSigningKeyResolver(secretService.getSigningKeyResolver())
.parseClaimsJws(jwt); .parseClaimsJws(jwt);
return new JwtResponse(claims); return new JwtResponse(claims);

View File

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