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:
Micah Silverman 2016-06-27 02:30:09 -04:00
parent df9e4d7ef5
commit 29a33c3407
5 changed files with 143 additions and 42 deletions

View File

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

View File

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

View File

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

View File

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

View File

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