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:
parent
532437ae46
commit
6bd3b381d1
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue