Made BaseController to provide uniform exception response. Made JwtResponse for uniform responses. Added ensureType method to enforce type on registered claims.
This commit is contained in:
parent
df9e4d7ef5
commit
29a33c3407
|
@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class JJWTFunApplication {
|
public class JJWTFunApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(JJWTFunApplication.class, args);
|
SpringApplication.run(JJWTFunApplication.class, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package io.jsonwebtoken.jjwtfun.controller;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import io.jsonwebtoken.MalformedJwtException;
|
||||||
|
import io.jsonwebtoken.SignatureException;
|
||||||
|
import io.jsonwebtoken.jjwtfun.model.JwtResponse;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
public class BaseController {
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
@ExceptionHandler({SignatureException.class, MalformedJwtException.class, JwtException.class})
|
||||||
|
public JwtResponse exception(Exception e) {
|
||||||
|
JwtResponse response = new JwtResponse();
|
||||||
|
response.setStatus(JwtResponse.Status.ERROR);
|
||||||
|
response.setMessage(e.getMessage());
|
||||||
|
response.setExceptionType(e.getClass().getName());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
package io.jsonwebtoken.jjwtfun.controller;
|
package io.jsonwebtoken.jjwtfun.controller;
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtBuilder;
|
import io.jsonwebtoken.JwtBuilder;
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.jjwtfun.model.JwtResponse;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
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;
|
||||||
|
@ -16,47 +18,55 @@ import java.util.Map;
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class DynamicJWTController {
|
public class DynamicJWTController extends BaseController {
|
||||||
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }")
|
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }")
|
||||||
String secret;
|
String secret;
|
||||||
|
|
||||||
@RequestMapping(value = "/dynamic-builder-general", method = POST)
|
@RequestMapping(value = "/dynamic-builder-general", method = POST)
|
||||||
public String dynamicBuilderGeneric(@RequestBody Map<String, Object> claims) throws UnsupportedEncodingException {
|
public JwtResponse dynamicBuilderGeneric(@RequestBody Map<String, Object> claims) throws UnsupportedEncodingException {
|
||||||
return Jwts.builder()
|
String jws = Jwts.builder()
|
||||||
.setClaims(claims)
|
.setClaims(claims)
|
||||||
.signWith(
|
.signWith(
|
||||||
SignatureAlgorithm.HS256,
|
SignatureAlgorithm.HS256,
|
||||||
secret.getBytes("UTF-8")
|
secret.getBytes("UTF-8")
|
||||||
)
|
)
|
||||||
.compact();
|
.compact();
|
||||||
|
return new JwtResponse(jws);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/dynamic-builder-specific", method = POST)
|
@RequestMapping(value = "/dynamic-builder-specific", method = POST)
|
||||||
public String dynamicBuilderSpecific(@RequestBody Map<String, Object> claims) throws UnsupportedEncodingException {
|
public JwtResponse dynamicBuilderSpecific(@RequestBody Map<String, Object> claims) throws UnsupportedEncodingException {
|
||||||
JwtBuilder builder = Jwts.builder();
|
JwtBuilder builder = Jwts.builder();
|
||||||
|
|
||||||
claims.forEach((key, value) -> {
|
claims.forEach((key, value) -> {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "iss":
|
case "iss":
|
||||||
builder.setIssuer((String)value);
|
ensureType(key, value, String.class);
|
||||||
|
builder.setIssuer((String) value);
|
||||||
break;
|
break;
|
||||||
case "sub":
|
case "sub":
|
||||||
builder.setSubject((String)value);
|
ensureType(key, value, String.class);
|
||||||
|
builder.setSubject((String) value);
|
||||||
break;
|
break;
|
||||||
case "aud":
|
case "aud":
|
||||||
builder.setAudience((String)value);
|
ensureType(key, value, String.class);
|
||||||
|
builder.setAudience((String) value);
|
||||||
break;
|
break;
|
||||||
case "exp":
|
case "exp":
|
||||||
builder.setExpiration(Date.from(Instant.ofEpochSecond(Long.parseLong((String)value))));
|
value = ensureType(key, value, Long.class);
|
||||||
|
builder.setExpiration(Date.from(Instant.ofEpochSecond((Long) value)));
|
||||||
break;
|
break;
|
||||||
case "nbf":
|
case "nbf":
|
||||||
builder.setNotBefore(Date.from(Instant.ofEpochSecond(Long.parseLong((String)value))));
|
value = ensureType(key, value, Long.class);
|
||||||
|
builder.setNotBefore(Date.from(Instant.ofEpochSecond((Long) value)));
|
||||||
break;
|
break;
|
||||||
case "iat":
|
case "iat":
|
||||||
builder.setIssuedAt(Date.from(Instant.ofEpochSecond(Long.parseLong((String)value))));
|
value = ensureType(key, value, Long.class);
|
||||||
|
builder.setIssuedAt(Date.from(Instant.ofEpochSecond((Long) value)));
|
||||||
break;
|
break;
|
||||||
case "jti":
|
case "jti":
|
||||||
builder.setId((String)value);
|
ensureType(key, value, String.class);
|
||||||
|
builder.setId((String) value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
builder.claim(key, value);
|
builder.claim(key, value);
|
||||||
|
@ -65,6 +75,23 @@ public class DynamicJWTController {
|
||||||
|
|
||||||
builder.signWith(SignatureAlgorithm.HS256, secret.getBytes("UTF-8"));
|
builder.signWith(SignatureAlgorithm.HS256, secret.getBytes("UTF-8"));
|
||||||
|
|
||||||
return builder.compact();
|
return new JwtResponse(builder.compact());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object ensureType(String registeredClaim, Object value, Class expectedType) {
|
||||||
|
// we want to promote Integers to Longs in this case
|
||||||
|
if (expectedType == Long.class && value instanceof Integer) {
|
||||||
|
value = ((Integer) value).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCorrectType = expectedType.isInstance(value);
|
||||||
|
|
||||||
|
if (!isCorrectType) {
|
||||||
|
String msg = "Expected type: " + expectedType.getCanonicalName() + " for registered claim: '" +
|
||||||
|
registeredClaim + "', but got value: " + value + " of type: " + value.getClass().getCanonicalName();
|
||||||
|
throw new JwtException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,65 +3,46 @@ package io.jsonwebtoken.jjwtfun.controller;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.jsonwebtoken.Jws;
|
import io.jsonwebtoken.Jws;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import io.jsonwebtoken.MalformedJwtException;
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.SignatureException;
|
import io.jsonwebtoken.jjwtfun.model.JwtResponse;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class StaticJWTController {
|
public class StaticJWTController extends BaseController {
|
||||||
|
|
||||||
@RequestMapping(value = "/static-builder", method = GET)
|
@RequestMapping(value = "/static-builder", method = GET)
|
||||||
public String fixedBuilder() throws UnsupportedEncodingException {
|
public JwtResponse fixedBuilder() throws UnsupportedEncodingException {
|
||||||
|
|
||||||
String jws = Jwts.builder()
|
String jws = Jwts.builder()
|
||||||
.setIssuer("Stormpath")
|
.setIssuer("Stormpath")
|
||||||
.setSubject("msilverman")
|
.setSubject("msilverman")
|
||||||
.claim("name", "Micah Silverman")
|
.claim("name", "Micah Silverman")
|
||||||
.claim("scope", "admins")
|
.claim("scope", "admins")
|
||||||
.setIssuedAt(Date.from(Instant.ofEpochSecond(1466796822))) // Fri Jun 24 2016 15:33:42 GMT-0400 (EDT)
|
.setIssuedAt(Date.from(Instant.ofEpochSecond(1466796822L))) // Fri Jun 24 2016 15:33:42 GMT-0400 (EDT)
|
||||||
.setExpiration(Date.from(Instant.ofEpochSecond(1466883222))) // Sat Jun 25 2016 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")
|
"secret".getBytes("UTF-8")
|
||||||
)
|
)
|
||||||
.compact();
|
.compact();
|
||||||
|
|
||||||
return jws;
|
return new JwtResponse(jws);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/parser", method = GET)
|
@RequestMapping(value = "/parser", method = GET)
|
||||||
public Jws<Claims> fixedParser(@RequestParam String jws) throws UnsupportedEncodingException {
|
public JwtResponse fixedParser(@RequestParam String jws) throws UnsupportedEncodingException {
|
||||||
Jws<Claims> claims = Jwts.parser()
|
Jws<Claims> claims = Jwts.parser()
|
||||||
.setSigningKey("secret".getBytes("UTF-8"))
|
.setSigningKey("secret".getBytes("UTF-8"))
|
||||||
.parseClaimsJws(jws);
|
.parseClaimsJws(jws);
|
||||||
|
|
||||||
return claims;
|
return new JwtResponse(claims);
|
||||||
}
|
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|
||||||
@ExceptionHandler({SignatureException.class, MalformedJwtException.class})
|
|
||||||
public Map<String, String> exception(Exception e) {
|
|
||||||
Map<String, String> response = new HashMap<>();
|
|
||||||
response.put("status", "ERROR");
|
|
||||||
response.put("message", e.getMessage());
|
|
||||||
response.put("exception-type", e.getClass().getName());
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package io.jsonwebtoken.jjwtfun.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jws;
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class JwtResponse {
|
||||||
|
private String message;
|
||||||
|
private Status status;
|
||||||
|
private String exceptionType;
|
||||||
|
private String jwt;
|
||||||
|
private Jws<Claims> claims;
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
SUCCESS, ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
public JwtResponse() {}
|
||||||
|
|
||||||
|
public JwtResponse(String jwt) {
|
||||||
|
this.jwt = jwt;
|
||||||
|
this.status = Status.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JwtResponse(Jws<Claims> claims) {
|
||||||
|
this.claims = claims;
|
||||||
|
this.status = Status.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Status status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExceptionType() {
|
||||||
|
return exceptionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExceptionType(String exceptionType) {
|
||||||
|
this.exceptionType = exceptionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJwt() {
|
||||||
|
return jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJwt(String jwt) {
|
||||||
|
this.jwt = jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jws<Claims> getClaims() {
|
||||||
|
return claims;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClaims(Jws<Claims> claims) {
|
||||||
|
this.claims = claims;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue