add refresh token feature.

This commit is contained in:
eelhazati
2019-07-23 09:09:56 +01:00
parent dd0003a478
commit b6de1db857
8 changed files with 250 additions and 52 deletions

View File

@@ -15,15 +15,15 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@Path("token")
public class TokenEndpoint {
List<String> supportedGrantTypes = Collections.singletonList("authorization_code");
List<String> supportedGrantTypes = Arrays.asList("authorization_code", "refresh_token");
@Inject
private AppDataRepository appDataRepository;

View File

@@ -0,0 +1,87 @@
package com.baeldung.oauth2.authorization.server.handler;
import com.baeldung.oauth2.authorization.server.PEMKeyUtils;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.microprofile.config.Config;
import javax.inject.Inject;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
public abstract class AbstractGrantTypeHandler implements AuthorizationGrantTypeHandler {
//Always RSA 256, but could be parametrized
protected JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build();
@Inject
protected Config config;
//30 min
protected Long expiresInMin = 30L;
protected JWSVerifier getJWSVerifier() throws Exception {
String verificationkey = config.getValue("verificationkey", String.class);
String pemEncodedRSAPublicKey = PEMKeyUtils.readKeyAsString(verificationkey);
RSAKey rsaPublicKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPublicKey);
return new RSASSAVerifier(rsaPublicKey);
}
protected JWSSigner getJwsSigner() throws Exception {
String signingkey = config.getValue("signingkey", String.class);
String pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString(signingkey);
RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey);
return new RSASSASigner(rsaKey.toRSAPrivateKey());
}
protected String getAccessToken(String clientId, String subject, String approvedScope) throws Exception {
//4. Signing
JWSSigner jwsSigner = getJwsSigner();
Instant now = Instant.now();
//Long expiresInMin = 30L;
Date expirationTime = Date.from(now.plus(expiresInMin, ChronoUnit.MINUTES));
//3. JWT Payload or claims
JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder()
.issuer("http://localhost:9080")
.subject(subject)
.claim("upn", subject)
.claim("client_id", clientId)
.audience("http://localhost:9280")
.claim("scope", approvedScope)
.claim("groups", Arrays.asList(approvedScope.split(" ")))
.expirationTime(expirationTime) // expires in 30 minutes
.notBeforeTime(Date.from(now))
.issueTime(Date.from(now))
.jwtID(UUID.randomUUID().toString())
.build();
SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaims);
signedJWT.sign(jwsSigner);
return signedJWT.serialize();
}
protected String getRefreshToken(String clientId, String subject, String approvedScope) throws Exception {
JWSSigner jwsSigner = getJwsSigner();
Instant now = Instant.now();
//6.Build refresh token
JWTClaimsSet refreshTokenClaims = new JWTClaimsSet.Builder()
.subject(subject)
.claim("client_id", clientId)
.claim("scope", approvedScope)
//refresh token for 1 day.
.expirationTime(Date.from(now.plus(1, ChronoUnit.DAYS)))
.build();
SignedJWT signedRefreshToken = new SignedJWT(jwsHeader, refreshTokenClaims);
signedRefreshToken.sign(jwsSigner);
return signedRefreshToken.serialize();
}
}

View File

@@ -1,18 +1,7 @@
package com.baeldung.oauth2.authorization.server.handler;
import com.baeldung.oauth2.authorization.server.PEMKeyUtils;
import com.baeldung.oauth2.authorization.server.model.AuthorizationCode;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.microprofile.config.Config;
import javax.inject.Inject;
import javax.inject.Named;
import javax.json.Json;
import javax.json.JsonObject;
@@ -20,22 +9,14 @@ import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
@Named("authorization_code")
public class AuthorizationCodeGrantTypeHandler implements AuthorizationGrantTypeHandler {
public class AuthorizationCodeGrantTypeHandler extends AbstractGrantTypeHandler {
@PersistenceContext
private EntityManager entityManager;
@Inject
private Config config;
@Override
public JsonObject createAccessToken(String clientId, MultivaluedMap<String, String> params) throws Exception {
//1. code is required
@@ -58,42 +39,16 @@ public class AuthorizationCodeGrantTypeHandler implements AuthorizationGrantType
throw new WebApplicationException("invalid_grant");
}
//JWT Header
JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build();
Instant now = Instant.now();
Long expiresInMin = 30L;
Date expiresIn = Date.from(now.plus(expiresInMin, ChronoUnit.MINUTES));
//3. JWT Payload or claims
JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder()
.issuer("http://localhost:9080")
.subject(authorizationCode.getUserId())
.claim("upn", authorizationCode.getUserId())
.audience("http://localhost:9280")
.claim("scope", authorizationCode.getApprovedScopes())
.claim("groups", Arrays.asList(authorizationCode.getApprovedScopes().split(" ")))
.expirationTime(expiresIn) // expires in 30 minutes
.notBeforeTime(Date.from(now))
.issueTime(Date.from(now))
.jwtID(UUID.randomUUID().toString())
.build();
SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaims);
//4. Signing
String signingkey = config.getValue("signingkey", String.class);
String pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString(signingkey);
RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey);
signedJWT.sign(new RSASSASigner(rsaKey.toRSAPrivateKey()));
//5. Finally the JWT access token
String accessToken = signedJWT.serialize();
String accessToken = getAccessToken(clientId, authorizationCode.getUserId(), authorizationCode.getApprovedScopes());
String refreshToken = getRefreshToken(clientId, authorizationCode.getUserId(), authorizationCode.getApprovedScopes());
return Json.createObjectBuilder()
.add("token_type", "Bearer")
.add("access_token", accessToken)
.add("expires_in", expiresInMin * 60)
.add("scope", authorizationCode.getApprovedScopes())
.add("refresh_token", refreshToken)
.build();
}
}

View File

@@ -0,0 +1,64 @@
package com.baeldung.oauth2.authorization.server.handler;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jwt.SignedJWT;
import javax.inject.Named;
import javax.json.Json;
import javax.json.JsonObject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;
import java.util.*;
@Named("refresh_token")
public class RefreshTokenGrantTypeHandler extends AbstractGrantTypeHandler {
@Override
public JsonObject createAccessToken(String clientId, MultivaluedMap<String, String> params) throws Exception {
String refreshToken = params.getFirst("refresh_token");
if (refreshToken == null || "".equals(refreshToken)) {
throw new WebApplicationException("invalid_grant");
}
//Decode refresh token
SignedJWT signedRefreshToken = SignedJWT.parse(refreshToken);
JWSVerifier verifier = getJWSVerifier();
if (!signedRefreshToken.verify(verifier)) {
throw new WebApplicationException("Invalid refresh token.");
}
if (!(new Date().before(signedRefreshToken.getJWTClaimsSet().getExpirationTime()))) {
throw new WebApplicationException("Refresh token expired.");
}
String refreshTokenClientId = signedRefreshToken.getJWTClaimsSet().getStringClaim("client_id");
if (!clientId.equals(refreshTokenClientId)) {
throw new WebApplicationException("Invalid client_id.");
}
//At this point, the refresh token is valid and not yet expired
//So create a new access token from it.
String subject = signedRefreshToken.getJWTClaimsSet().getSubject();
String approvedScopes = signedRefreshToken.getJWTClaimsSet().getStringClaim("scope");
String finalScope = approvedScopes;
String requestedScopes = params.getFirst("scope");
if (requestedScopes != null && !requestedScopes.isEmpty()) {
Set<String> allowedScopes = new LinkedHashSet<>();
Set<String> rScopes = new HashSet(Arrays.asList(requestedScopes.split(" ")));
Set<String> aScopes = new HashSet(Arrays.asList(approvedScopes.split(" ")));
for (String scope : rScopes) {
if (aScopes.contains(scope)) allowedScopes.add(scope);
}
finalScope = String.join(" ", allowedScopes);
}
String accessToken = getAccessToken(clientId, subject, finalScope);
return Json.createObjectBuilder()
.add("token_type", "Bearer")
.add("access_token", accessToken)
.add("expires_in", expiresInMin * 60)
.add("scope", finalScope)
.add("refresh_token", refreshToken)
.build();
}
}