This commit is contained in:
Jonathan Cook
2019-10-23 15:01:44 +02:00
parent db85c8f275
commit 684ec0d2e3
20486 changed files with 1642483 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
## OAuth 2.0 Implementation
This module contains articles about the implementation of OAuth2 with Java EE.
### Relevant Articles
- [Implementing The OAuth 2.0 Authorization Framework Using Java EE](https://www.baeldung.com/java-ee-oauth2-implementation)
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<artifactId>oauth2-authorization-server</artifactId>
<packaging>war</packaging>
<name>oauth2-authorization-server</name>
<parent>
<groupId>com.baeldung.oauth2</groupId>
<artifactId>oauth2-framework-impl</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>${nimbus-jose-jwt.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bcprov-jdk15on.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>${bcpkix-jdk15on.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>net.wasdev.wlp.maven.plugins</groupId>
<artifactId>liberty-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-h2-dependency</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
</execution>
</executions>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<type>jar</type>
<outputDirectory>${project.build.directory}/liberty/wlp/usr/shared/resources/
</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<h2.version>1.4.199</h2.version>
<httpPort>9080</httpPort>
<httpsPort>9443</httpsPort>
<nimbus-jose-jwt.version>7.3</nimbus-jose-jwt.version>
<bcprov-jdk15on.version>1.62</bcprov-jdk15on.version>
<bcpkix-jdk15on.version>1.62</bcpkix-jdk15on.version>
</properties>
</project>
@@ -0,0 +1,8 @@
package com.baeldung.oauth2.authorization.server;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/")
public class OAuth2ServerApplication extends Application {
}
@@ -0,0 +1,17 @@
package com.baeldung.oauth2.authorization.server;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import static java.lang.Thread.currentThread;
public class PEMKeyUtils {
public static String readKeyAsString(String keyLocation) throws Exception {
URI uri = currentThread().getContextClassLoader().getResource(keyLocation).toURI();
byte[] byteArray = Files.readAllBytes(Paths.get(uri));
return new String(byteArray);
}
}
@@ -0,0 +1,185 @@
package com.baeldung.oauth2.authorization.server.api;
import com.baeldung.oauth2.authorization.server.handler.AuthorizationGrantTypeHandler;
import com.baeldung.oauth2.authorization.server.model.AppDataRepository;
import com.baeldung.oauth2.authorization.server.model.AuthorizationCode;
import com.baeldung.oauth2.authorization.server.model.Client;
import com.baeldung.oauth2.authorization.server.model.User;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.literal.NamedLiteral;
import javax.inject.Inject;
import javax.json.JsonObject;
import javax.security.enterprise.SecurityContext;
import javax.security.enterprise.authentication.mechanism.http.FormAuthenticationMechanismDefinition;
import javax.security.enterprise.authentication.mechanism.http.LoginToContinue;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.io.IOException;
import java.net.URI;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.*;
@FormAuthenticationMechanismDefinition(
loginToContinue = @LoginToContinue(loginPage = "/login.jsp", errorPage = "/login.jsp")
)
@RolesAllowed("USER")
@RequestScoped
@Path("authorize")
public class AuthorizationEndpoint {
@Inject
private SecurityContext securityContext;
@Inject
private AppDataRepository appDataRepository;
@Inject
Instance<AuthorizationGrantTypeHandler> authorizationGrantTypeHandlers;
@GET
@Produces(MediaType.TEXT_HTML)
public Response doGet(@Context HttpServletRequest request,
@Context HttpServletResponse response,
@Context UriInfo uriInfo) throws ServletException, IOException {
MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
Principal principal = securityContext.getCallerPrincipal();
//error about redirect_uri && client_id ==> forward user, thus to error.jsp.
//otherwise ==> sendRedirect redirect_uri?error=error&error_description=error_description
//1. client_id
String clientId = params.getFirst("client_id");
if (clientId == null || clientId.isEmpty()) {
return informUserAboutError(request, response, "Invalid client_id :" + clientId);
}
Client client = appDataRepository.getClient(clientId);
if (client == null) {
return informUserAboutError(request, response, "Invalid client_id :" + clientId);
}
//2. Client Authorized Grant Type
String clientError = "";
if (client.getAuthorizedGrantTypes() != null && !client.getAuthorizedGrantTypes().contains("authorization_code")) {
return informUserAboutError(request, response, "Authorization Grant type, authorization_code, is not allowed for this client :" + clientId);
}
//3. redirectUri
String redirectUri = params.getFirst("redirect_uri");
if (client.getRedirectUri() != null && !client.getRedirectUri().isEmpty()) {
if (redirectUri != null && !redirectUri.isEmpty() && !client.getRedirectUri().equals(redirectUri)) {
//sould be in the client.redirectUri
return informUserAboutError(request, response, "redirect_uri is pre-registred and should match");
}
redirectUri = client.getRedirectUri();
params.putSingle("resolved_redirect_uri", redirectUri);
} else {
if (redirectUri == null || redirectUri.isEmpty()) {
return informUserAboutError(request, response, "redirect_uri is not pre-registred and should be provided");
}
params.putSingle("resolved_redirect_uri", redirectUri);
}
request.setAttribute("client", client);
//4. response_type
String responseType = params.getFirst("response_type");
if (!"code".equals(responseType) && !"token".equals(responseType)) {
//error = "invalid_grant :" + responseType + ", response_type params should be code or token:";
//return informUserAboutError(error);
}
//Save params in session
request.getSession().setAttribute("ORIGINAL_PARAMS", params);
//4.scope: Optional
String requestedScope = request.getParameter("scope");
if (requestedScope == null || requestedScope.isEmpty()) {
requestedScope = client.getScope();
}
User user = appDataRepository.getUser(principal.getName());
String allowedScopes = checkUserScopes(user.getScopes(), requestedScope);
request.setAttribute("scopes", allowedScopes);
request.getRequestDispatcher("/authorize.jsp").forward(request, response);
return null;
}
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_HTML)
public Response doPost(@Context HttpServletRequest request,
@Context HttpServletResponse response,
MultivaluedMap<String, String> params) throws Exception {
MultivaluedMap<String, String> originalParams = (MultivaluedMap<String, String>) request.getSession().getAttribute("ORIGINAL_PARAMS");
if (originalParams == null) {
return informUserAboutError(request, response, "No pending authorization request.");
}
String redirectUri = originalParams.getFirst("resolved_redirect_uri");
StringBuilder sb = new StringBuilder(redirectUri);
String approvalStatus = params.getFirst("approval_status");
if ("NO".equals(approvalStatus)) {
URI location = UriBuilder.fromUri(sb.toString())
.queryParam("error", "User doesn't approved the request.")
.queryParam("error_description", "User doesn't approved the request.")
.build();
return Response.seeOther(location).build();
}
//==> YES
List<String> approvedScopes = params.get("scope");
if (approvedScopes == null || approvedScopes.isEmpty()) {
URI location = UriBuilder.fromUri(sb.toString())
.queryParam("error", "User doesn't approved the request.")
.queryParam("error_description", "User doesn't approved the request.")
.build();
return Response.seeOther(location).build();
}
String responseType = originalParams.getFirst("response_type");
String clientId = originalParams.getFirst("client_id");
if ("code".equals(responseType)) {
String userId = securityContext.getCallerPrincipal().getName();
AuthorizationCode authorizationCode = new AuthorizationCode();
authorizationCode.setClientId(clientId);
authorizationCode.setUserId(userId);
authorizationCode.setApprovedScopes(String.join(" ", approvedScopes));
authorizationCode.setExpirationDate(LocalDateTime.now().plusMinutes(10));
authorizationCode.setRedirectUri(redirectUri);
appDataRepository.save(authorizationCode);
String code = authorizationCode.getCode();
sb.append("?code=").append(code);
} else {
//Implicit: responseType=token
AuthorizationGrantTypeHandler authorizationGrantTypeHandler = authorizationGrantTypeHandlers.select(NamedLiteral.of("implicit")).get();
JsonObject tokenResponse = authorizationGrantTypeHandler.createAccessToken(clientId, params);
sb.append("#access_token=").append(tokenResponse.getString("access_token"))
.append("&token_type=").append(tokenResponse.getString("token_type"))
.append("&scope=").append(tokenResponse.getString("scope"));
}
String state = originalParams.getFirst("state");
if (state != null) {
sb.append("&state=").append(state);
}
return Response.seeOther(UriBuilder.fromUri(sb.toString()).build()).build();
}
private String checkUserScopes(String userScopes, String requestedScope) {
Set<String> allowedScopes = new LinkedHashSet<>();
Set<String> rScopes = new HashSet(Arrays.asList(requestedScope.split(" ")));
Set<String> uScopes = new HashSet(Arrays.asList(userScopes.split(" ")));
for (String scope : uScopes) {
if (rScopes.contains(scope)) allowedScopes.add(scope);
}
return String.join(" ", allowedScopes);
}
private Response informUserAboutError(HttpServletRequest request, HttpServletResponse response, String error) throws ServletException, IOException {
request.setAttribute("error", error);
request.getRequestDispatcher("/error.jsp").forward(request, response);
return null;
}
}
@@ -0,0 +1,38 @@
package com.baeldung.oauth2.authorization.server.api;
import com.baeldung.oauth2.authorization.server.PEMKeyUtils;
import com.nimbusds.jose.jwk.JWK;
import org.eclipse.microprofile.config.Config;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Arrays;
@Path("jwk")
@ApplicationScoped
public class JWKEndpoint {
@Inject
private Config config;
@GET
public Response getKey(@QueryParam("format") String format) throws Exception {
if (format != null && !Arrays.asList("jwk", "pem").contains(format)) {
return Response.status(Response.Status.BAD_REQUEST).entity("Public Key Format should be : jwk or pem").build();
}
String verificationkey = config.getValue("verificationkey", String.class);
String pemEncodedRSAPublicKey = PEMKeyUtils.readKeyAsString(verificationkey);
if (format == null || format.equals("jwk")) {
JWK jwk = JWK.parseFromPEMEncodedObjects(pemEncodedRSAPublicKey);
return Response.ok(jwk.toJSONString()).type(MediaType.APPLICATION_JSON).build();
} else if (format.equals("pem")) {
return Response.ok(pemEncodedRSAPublicKey).build();
}
return null;
}
}
@@ -0,0 +1,94 @@
package com.baeldung.oauth2.authorization.server.api;
import com.baeldung.oauth2.authorization.server.handler.AuthorizationGrantTypeHandler;
import com.baeldung.oauth2.authorization.server.model.AppDataRepository;
import com.baeldung.oauth2.authorization.server.model.Client;
import com.nimbusds.jose.JOSEException;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.literal.NamedLiteral;
import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonObject;
import javax.ws.rs.*;
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.List;
@Path("token")
public class TokenEndpoint {
List<String> supportedGrantTypes = Arrays.asList("authorization_code", "refresh_token");
@Inject
private AppDataRepository appDataRepository;
@Inject
Instance<AuthorizationGrantTypeHandler> authorizationGrantTypeHandlers;
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response token(MultivaluedMap<String, String> params,
@HeaderParam(HttpHeaders.AUTHORIZATION) String authHeader) throws JOSEException {
//Check grant_type params
String grantType = params.getFirst("grant_type");
if (grantType == null || grantType.isEmpty())
return responseError("Invalid_request", "grant_type is required", Response.Status.BAD_REQUEST);
if (!supportedGrantTypes.contains(grantType)) {
return responseError("unsupported_grant_type", "grant_type should be one of :" + supportedGrantTypes, Response.Status.BAD_REQUEST);
}
//Client Authentication
String[] clientCredentials = extract(authHeader);
if (clientCredentials.length != 2) {
return responseError("Invalid_request", "Bad Credentials client_id/client_secret", Response.Status.BAD_REQUEST);
}
String clientId = clientCredentials[0];
Client client = appDataRepository.getClient(clientId);
if (client == null) {
return responseError("Invalid_request", "Invalid client_id", Response.Status.BAD_REQUEST);
}
String clientSecret = clientCredentials[1];
if (!clientSecret.equals(client.getClientSecret())) {
return responseError("Invalid_request", "Invalid client_secret", Response.Status.UNAUTHORIZED);
}
AuthorizationGrantTypeHandler authorizationGrantTypeHandler = authorizationGrantTypeHandlers.select(NamedLiteral.of(grantType)).get();
JsonObject tokenResponse = null;
try {
tokenResponse = authorizationGrantTypeHandler.createAccessToken(clientId, params);
} catch (WebApplicationException e) {
return e.getResponse();
} catch (Exception e) {
return responseError("Invalid_request", "Can't get token", Response.Status.INTERNAL_SERVER_ERROR);
}
return Response.ok(tokenResponse)
.header("Cache-Control", "no-store")
.header("Pragma", "no-cache")
.build();
}
private String[] extract(String authHeader) {
if (authHeader != null && authHeader.startsWith("Basic ")) {
return new String(Base64.getDecoder().decode(authHeader.substring(6))).split(":");
}
return new String[]{};
}
private Response responseError(String error, String errorDescription, Response.Status status) {
JsonObject errorResponse = Json.createObjectBuilder()
.add("error", error)
.add("error_description", errorDescription)
.build();
return Response.status(status)
.entity(errorResponse).build();
}
}
@@ -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();
}
}
@@ -0,0 +1,54 @@
package com.baeldung.oauth2.authorization.server.handler;
import com.baeldung.oauth2.authorization.server.model.AuthorizationCode;
import javax.inject.Named;
import javax.json.Json;
import javax.json.JsonObject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;
import java.time.LocalDateTime;
@Named("authorization_code")
public class AuthorizationCodeGrantTypeHandler extends AbstractGrantTypeHandler {
@PersistenceContext
private EntityManager entityManager;
@Override
public JsonObject createAccessToken(String clientId, MultivaluedMap<String, String> params) throws Exception {
//1. code is required
String code = params.getFirst("code");
if (code == null || "".equals(code)) {
throw new WebApplicationException("invalid_grant");
}
AuthorizationCode authorizationCode = entityManager.find(AuthorizationCode.class, code);
if (!authorizationCode.getExpirationDate().isAfter(LocalDateTime.now())) {
throw new WebApplicationException("code Expired !");
}
String redirectUri = params.getFirst("redirect_uri");
//redirecturi match
if (authorizationCode.getRedirectUri() != null && !authorizationCode.getRedirectUri().equals(redirectUri)) {
//redirectUri params should be the same as the requested redirectUri.
throw new WebApplicationException("invalid_grant");
}
//client match
if (!clientId.equals(authorizationCode.getClientId())) {
throw new WebApplicationException("invalid_grant");
}
//3. JWT Payload or claims
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();
}
}
@@ -0,0 +1,8 @@
package com.baeldung.oauth2.authorization.server.handler;
import javax.json.JsonObject;
import javax.ws.rs.core.MultivaluedMap;
public interface AuthorizationGrantTypeHandler {
JsonObject createAccessToken(String clientId, MultivaluedMap<String, String> params) throws Exception;
}
@@ -0,0 +1,72 @@
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 javax.ws.rs.core.Response;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@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 requestedScopes = params.getFirst("scope");
if (requestedScopes != null && !requestedScopes.isEmpty()) {
Set<String> rScopes = new HashSet(Arrays.asList(requestedScopes.split(" ")));
Set<String> aScopes = new HashSet(Arrays.asList(approvedScopes.split(" ")));
if (!aScopes.containsAll(rScopes)) {
JsonObject error = Json.createObjectBuilder()
.add("error", "Invalid_request")
.add("error_description", "Requested scopes should be a subset of the original scopes.")
.build();
Response response = Response.status(Response.Status.BAD_REQUEST).entity(error).build();
throw new WebApplicationException(response);
}
} else {
requestedScopes = approvedScopes;
}
String accessToken = getAccessToken(clientId, subject, requestedScopes);
return Json.createObjectBuilder()
.add("token_type", "Bearer")
.add("access_token", accessToken)
.add("expires_in", expiresInMin * 60)
.add("scope", requestedScopes)
.add("refresh_token", refreshToken)
.build();
}
}
@@ -0,0 +1,27 @@
package com.baeldung.oauth2.authorization.server.model;
import javax.enterprise.context.ApplicationScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
@ApplicationScoped
public class AppDataRepository {
@PersistenceContext
private EntityManager entityManager;
public Client getClient(String clientId) {
return entityManager.find(Client.class, clientId);
}
public User getUser(String userId) {
return entityManager.find(User.class, userId);
}
@Transactional
public AuthorizationCode save(AuthorizationCode authorizationCode) {
entityManager.persist(authorizationCode);
return authorizationCode;
}
}
@@ -0,0 +1,73 @@
package com.baeldung.oauth2.authorization.server.model;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "authorization_code")
public class AuthorizationCode {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "code")
private String code;
@Column(name = "client_id")
private String clientId;
@Column(name = "user_id")
private String userId;
@Column(name = "approved_scopes")
private String approvedScopes;
@Column(name = "redirect_uri")
private String redirectUri;
@Column(name = "expiration_date")
private LocalDateTime expirationDate;
public String getUserId() {
return userId;
}
public void setUserId(String username) {
this.userId = username;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getApprovedScopes() {
return approvedScopes;
}
public void setApprovedScopes(String approvedScopes) {
this.approvedScopes = approvedScopes;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public LocalDateTime getExpirationDate() {
return expirationDate;
}
public void setExpirationDate(LocalDateTime expirationDate) {
this.expirationDate = expirationDate;
}
}
@@ -0,0 +1,62 @@
package com.baeldung.oauth2.authorization.server.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "clients")
public class Client {
@Id
@Column(name = "client_id")
private String clientId;
@Column(name = "client_secret")
private String clientSecret;
@Column(name = "redirect_uri")
private String redirectUri;
@Column(name = "scope")
private String scope;
@Column(name = "authorized_grant_types")
private String authorizedGrantTypes;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getAuthorizedGrantTypes() {
return authorizedGrantTypes;
}
public void setAuthorizedGrantTypes(String authorizedGrantTypes) {
this.authorizedGrantTypes = authorizedGrantTypes;
}
}
@@ -0,0 +1,58 @@
package com.baeldung.oauth2.authorization.server.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.security.Principal;
@Entity
@Table(name = "users")
public class User implements Principal {
@Id
@Column(name = "user_id")
private String userId;
@Column(name = "password")
private String password;
@Column(name = "roles")
private String roles;
@Column(name = "scopes")
private String scopes;
public String getUserId() {
return userId;
}
public void setUserId(String username) {
this.userId = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
public String getScopes() {
return scopes;
}
public void setScopes(String scopes) {
this.scopes = scopes;
}
@Override
public String getName() {
return getUserId();
}
}
@@ -0,0 +1,37 @@
package com.baeldung.oauth2.authorization.server.security;
import com.baeldung.oauth2.authorization.server.model.AppDataRepository;
import com.baeldung.oauth2.authorization.server.model.User;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.security.enterprise.credential.Credential;
import javax.security.enterprise.credential.UsernamePasswordCredential;
import javax.security.enterprise.identitystore.CredentialValidationResult;
import javax.security.enterprise.identitystore.IdentityStore;
import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
@ApplicationScoped
@Transactional
public class UserIdentityStore implements IdentityStore {
@Inject
private AppDataRepository appDataRepository;
@Override
public CredentialValidationResult validate(Credential credential) {
UsernamePasswordCredential usernamePasswordCredential = (UsernamePasswordCredential) credential;
String userId = usernamePasswordCredential.getCaller();
User user = appDataRepository.getUser(userId);
Objects.requireNonNull(user, "User should be not null");
if (usernamePasswordCredential.getPasswordAsString().equals(user.getPassword())) {
return new CredentialValidationResult(userId, new HashSet<>(Arrays.asList(user.getRoles().split(","))));
}
return INVALID_RESULT;
}
}
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<server description="${project.artifactId}">
<featureManager>
<feature>localConnector-1.0</feature>
<feature>cdi-2.0</feature>
<feature>jaxrs-2.1</feature>
<feature>jpa-2.2</feature>
<feature>appSecurity-3.0</feature>
<feature>jsp-2.3</feature>
<feature>mpConfig-1.3</feature>
</featureManager>
<httpEndpoint id="defaultHttpEndpoint" httpPort="${httpPort}" httpsPort="${httpsPort}"/>
<library id="H2_JDBC_Lib">
<fileset dir="${shared.resource.dir}" includes="h2*.jar"/>
</library>
<dataSource id="oauth2datasource" jndiName="jdbc/OAuth2_DS">
<jdbcDriver
javax.sql.XADataSource="org.h2.jdbcx.JdbcDataSource"
javax.sql.ConnectionPoolDataSource="org.h2.jdbcx.JdbcDataSource"
javax.sql.DataSource="org.h2.jdbcx.JdbcDataSource"
libraryRef="H2_JDBC_Lib"/>
<properties url="jdbc:h2:mem:OAuth2_DB" user="sa" password=""/>
</dataSource>
<applicationManager autoExpand="true"/>
<applicationMonitor updateTrigger="mbean"/>
<application type="war" location="${project.build.finalName}.war" context-root="/"/>
</server>
@@ -0,0 +1,2 @@
signingkey=/META-INF/private-key.pem
verificationkey=/META-INF/public-key.pem
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="jpa-oauth2-pu" transaction-type="JTA">
<jta-data-source>jdbc/OAuth2_DS</jta-data-source>
<properties>
<property name="javax.persistence.schema-generation.database.action" value="create"/>
<property name="javax.persistence.sql-load-script-source" value="data.sql"/>
</properties>
</persistence-unit>
</persistence>
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYizUyvwY6qMd0
ZkYPzP9mLTJOSczW8WtAbjhrgxk5LORIjcdv/aB1BAzDEOCX4UC+Rbs9YwaekIvW
7dL/t6xPzsA7+9tMt27TqUxXigWEFQG73w/WbEVzCG09IHqG7Ztjb6aGL+8pfcu+
SLvOtXWrctHNbr35BYC5yBQaos9bH+nLYbh2Ff+emrpg7Kwoeis6MY6RgZIL/lxt
3beZt1NQhLEvB9t6XoSkyLQKSjT/OC8XOa4OYI4OfOSFqEUEp+8dKe3svZbQUNJf
KKehx78xx0rfwH+iBsFKkHdvt1ZHtINvYedAfGrc5rWnesunzOW7OKMAiIXAJSRA
rn9Iv5/fAgMBAAECggEAYHKwgSfAGIRwQhIDhqoh31qmG2SXjez9fjcZfhloNKUg
EIjFmcX3n+br4D42Kq+zbIwWd6MRobJz9oj6/9bJMsq9qHnnFWZmQHQZgqwBBPFu
UkVqAnE7BZ9tOFqs+EgAe+uQ2hejiHF1PA2dSNZd0L1VYRDAIJgo25aYDb0Sal0c
qaFmK1XzPnSIhgnopKP/TmUfALjeshtGvh6WkiXHTY2VKJlyLEiGd3/1tBZnzwSQ
QiS/2zfXL7lO0SYRA10m1BmtqGi3wSKIXUA6tNulmpXEMnYk4RtsqBClOanzQIm7
f4Ie59AtUUlDdHzRmhxBaDprgAI/FdCxXkseLzk5sQKBgQDyptOhJ/VQ5tkgffJW
X2QwGIaNdvNCEL4GlgXKAWNkCzT4h+Q/2wFoKhaN2JH2YZwKCRwG/UZh4Hq22fdx
fa/DP8PK3zlX0c9dWQOtmBOe0/nG28kq9iqziP3ILLNuelfaMhxtRkUmR652VCzx
qazZmjoo2MxtA6BMiK3Vx5aikwKBgQDkdLalVveHKTVkrplQYNapQAtAwjlsNnMt
VJh6nLo3eOg1xBNA7JrYqx7sXRQInUGAyNKvgQL/lCQdMkTzyetLjJGsiFSEgeot
RMsDXBrJFYZiErbpQungpaevwAymaGi++nbCzhw1/n3AvVUhRKNknX8Ms0UKlLRZ
TCktCTChBQKBgGA7ZzzHgxPFqaCoMl6s0Cf+4gXigdDWoPYtszgM2uUHSMez5QKq
EWHFJ1Kz7BdBWMfmGvZupeYVR7WStf6NcRJHDJg9dRlt/QYxUjMbV9Sqjqmd6qce
H4s6LiOgDr0mygafzwRLVQs8bGVDNtvUhdd6wcwHRvOI957CqeZZlFT/AoGBAN2P
j79EW6U6wuyVJF0+vZDBauhwNQ6MtCEnZQWs0DCSUuop8d5KWVZ+huwGzTIZiPhk
S2goP4cs3eVu5k5k6oyHlJP2V7l24WzrxdPJVLTl6kFdEwWgfn//SGR7ZglRQxzM
fbcp+1QmL0FonZI5JhmjYR8pEXFUjJ/57AkgW4gdAoGASV3TlaNd+reb2l0kETqp
HrzzMIYkM2faZ8LmpcEO0wz606SK468bnl6SqVBYT2J0bkqCpEmPBtWs/OFCWd6x
93NndSfJk57/7AqNxfVyZWp2sjRzt5vZ4KPlRVvMHIGeJNbIV7RqUr6XGu+u3ZrS
B7SqwKZqdHVSULG9nMK7Iz8=
-----END PRIVATE KEY-----
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Is1Mr8GOqjHdGZGD8z/
Zi0yTknM1vFrQG44a4MZOSzkSI3Hb/2gdQQMwxDgl+FAvkW7PWMGnpCL1u3S/7es
T87AO/vbTLdu06lMV4oFhBUBu98P1mxFcwhtPSB6hu2bY2+mhi/vKX3Lvki7zrV1
q3LRzW69+QWAucgUGqLPWx/py2G4dhX/npq6YOysKHorOjGOkYGSC/5cbd23mbdT
UISxLwfbel6EpMi0Cko0/zgvFzmuDmCODnzkhahFBKfvHSnt7L2W0FDSXyinoce/
McdK38B/ogbBSpB3b7dWR7SDb2HnQHxq3Oa1p3rLp8zluzijAIiFwCUkQK5/SL+f
3wIDAQAB
-----END PUBLIC KEY-----
@@ -0,0 +1,3 @@
INSERT INTO `users` (`user_id`, `password`, `roles`, `scopes`) VALUES ('appuser', 'appusersecret', 'USER', 'resource.read resource.write');
INSERT INTO `clients` (`client_id`, `client_secret`, `redirect_uri`,`scope`,`authorized_grant_types`) VALUES ('webappclient', 'webappclientsecret', 'http://localhost:9180/callback', 'resource.read resource.write', 'authorization_code refresh_token');
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
bean-discovery-mode="all">
</beans>
@@ -0,0 +1,54 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>Authorization</title>
<style>
input[type=submit]{
width: 25%;
padding: 4px 0px;
display: inline-block;
border: 1px solid #ccc;
box-sizing: border-box;
}
.container {
width: 75%;
padding: 25px;
border: 1px solid #ccc;
background: beige;
}
</style>
</head>
<body>
<div class="container">
<p><h3>Want to Authorize scopes for client : ${client.clientId} ?</h3></p>
<hr>
<form method="post" action="authorize">
<table>
<tr>
<td valign="top">Scopes :</td>
<td>
<c:forTokens items="${scopes}" delims=" " var="scope">
<input type="checkbox" name="scope" value="${scope}">${scope}</input><br/>
</c:forTokens>
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" name="approval_status" value="YES"/>
<input type="submit" name="approval_status" value="NO"/>
</td>
</tr>
</table>
</form>
</div>
</body>
</html>
@@ -0,0 +1,25 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Error</title>
</head>
<style>
.container {
padding: 16px;
border: 1px solid #ccc;
background: #f57007;
}
</style>
<body>
<div class="container">
<p>${error}</p>
</div>
</body>
</html>
@@ -0,0 +1,49 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Login Form</title>
</head>
<style>
input[type=text], input[type=password] {
width: 75%;
padding: 4px 0px;
display: inline-block;
border: 1px solid #ccc;
box-sizing: border-box;
}
.container {
width: 50%;
padding: 16px;
border: 1px solid #ccc;
background: beige;
}
</style>
<body>
<div class="container">
<p>Login Form</p>
<hr>
<form id="loginform" action="j_security_check" method="post">
<table style="with: 50%">
<tr>
<td width="20%">Username</td>
<td><input type="text" name="j_username"/></td>
</tr>
<tr>
<td width="20%">Password</td>
<td><input type="password" name="j_password"/></td>
</tr>
<tr>
<td width="20%"></td>
<td><input type="submit" value="Login"/></td>
</tr>
</table>
</form>
</div>
</body>
</html>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<artifactId>oauth2-client</artifactId>
<packaging>war</packaging>
<name>oauth2-client</name>
<parent>
<groupId>com.baeldung.oauth2</groupId>
<artifactId>oauth2-framework-impl</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<build>
<plugins>
<plugin>
<groupId>net.wasdev.wlp.maven.plugins</groupId>
<artifactId>liberty-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<properties>
<httpPort>9180</httpPort>
<httpsPort>9543</httpsPort>
</properties>
</project>
@@ -0,0 +1,23 @@
package com.baeldung.oauth2.client;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Base64;
public abstract class AbstractServlet extends HttpServlet {
protected void dispatch(String location, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher requestDispatcher = request.getRequestDispatcher(location);
requestDispatcher.forward(request, response);
}
protected String getAuthorizationHeaderValue(String clientId, String clientSecret) {
String token = clientId + ":" + clientSecret;
String encodedString = Base64.getEncoder().encodeToString(token.getBytes());
return "Basic " + encodedString;
}
}
@@ -0,0 +1,39 @@
package com.baeldung.oauth2.client;
import org.eclipse.microprofile.config.Config;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
@WebServlet(urlPatterns = "/authorize")
public class AuthorizationCodeServlet extends HttpServlet {
@Inject
private Config config;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//...
request.getSession().removeAttribute("tokenResponse");
String state = UUID.randomUUID().toString();
request.getSession().setAttribute("CLIENT_LOCAL_STATE", state);
String authorizationUri = config.getValue("provider.authorizationUri", String.class);
String clientId = config.getValue("client.clientId", String.class);
String redirectUri = config.getValue("client.redirectUri", String.class);
String scope = config.getValue("client.scope", String.class);
String authorizationLocation = authorizationUri + "?response_type=code"
+ "&client_id=" + clientId
+ "&redirect_uri=" + redirectUri
+ "&scope=" + scope
+ "&state=" + state;
response.sendRedirect(authorizationLocation);
}
}
@@ -0,0 +1,67 @@
package com.baeldung.oauth2.client;
import org.eclipse.microprofile.config.Config;
import javax.inject.Inject;
import javax.json.JsonObject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
@WebServlet(urlPatterns = "/callback")
public class CallbackServlet extends AbstractServlet {
@Inject
private Config config;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String clientId = config.getValue("client.clientId", String.class);
String clientSecret = config.getValue("client.clientSecret", String.class);
//Error:
String error = request.getParameter("error");
if (error != null) {
request.setAttribute("error", error);
dispatch("/", request, response);
return;
}
String localState = (String) request.getSession().getAttribute("CLIENT_LOCAL_STATE");
if (!localState.equals(request.getParameter("state"))) {
request.setAttribute("error", "The state attribute doesn't match !!");
dispatch("/", request, response);
return;
}
String code = request.getParameter("code");
Client client = ClientBuilder.newClient();
WebTarget target = client.target(config.getValue("provider.tokenUri", String.class));
Form form = new Form();
form.param("grant_type", "authorization_code");
form.param("code", code);
form.param("redirect_uri", config.getValue("client.redirectUri", String.class));
try {
JsonObject tokenResponse = target.request(MediaType.APPLICATION_JSON_TYPE)
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeaderValue(clientId, clientSecret))
.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), JsonObject.class);
request.getSession().setAttribute("tokenResponse", tokenResponse);
} catch (Exception ex) {
System.out.println(ex.getMessage());
request.setAttribute("error", ex.getMessage());
}
dispatch("/", request, response);
}
}
@@ -0,0 +1,49 @@
package com.baeldung.oauth2.client;
import org.eclipse.microprofile.config.Config;
import javax.inject.Inject;
import javax.json.JsonObject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.*;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(urlPatterns = "/downstream")
public class DownstreamCallServlet extends HttpServlet {
@Inject
private Config config;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
String action = request.getParameter("action");
Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target(config.getValue("resourceServerUri", String.class));
WebTarget resourceWebTarget;
String response = null;
JsonObject tokenResponse = (JsonObject) request.getSession().getAttribute("tokenResponse");
if ("read".equals(action)) {
resourceWebTarget = webTarget.path("resource/read");
Invocation.Builder invocationBuilder = resourceWebTarget.request();
response = invocationBuilder
.header("authorization", tokenResponse.getString("access_token"))
.get(String.class);
} else if ("write".equals(action)) {
resourceWebTarget = webTarget.path("resource/write");
Invocation.Builder invocationBuilder = resourceWebTarget.request();
response = invocationBuilder
.header("authorization", tokenResponse.getString("access_token"))
.post(Entity.text("body string"), String.class);
}
PrintWriter out = resp.getWriter();
out.println(response);
out.flush();
out.close();
}
}
@@ -0,0 +1,57 @@
package com.baeldung.oauth2.client;
import org.eclipse.microprofile.config.Config;
import javax.inject.Inject;
import javax.json.JsonObject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
@WebServlet(urlPatterns = "/refreshtoken")
public class RefreshTokenServlet extends AbstractServlet {
@Inject
private Config config;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String clientId = config.getValue("client.clientId", String.class);
String clientSecret = config.getValue("client.clientSecret", String.class);
JsonObject actualTokenResponse = (JsonObject) request.getSession().getAttribute("tokenResponse");
Client client = ClientBuilder.newClient();
WebTarget target = client.target(config.getValue("provider.tokenUri", String.class));
Form form = new Form();
form.param("grant_type", "refresh_token");
form.param("refresh_token", actualTokenResponse.getString("refresh_token"));
String scope = request.getParameter("scope");
if (scope != null && !scope.isEmpty()) {
form.param("scope", scope);
}
Response jaxrsResponse = target.request(MediaType.APPLICATION_JSON_TYPE)
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeaderValue(clientId, clientSecret))
.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), Response.class);
JsonObject tokenResponse = jaxrsResponse.readEntity(JsonObject.class);
if (jaxrsResponse.getStatus() == 200) {
request.getSession().setAttribute("tokenResponse", tokenResponse);
} else {
request.setAttribute("error", tokenResponse.getString("error_description", "error!"));
}
dispatch("/", request, response);
}
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<server description="${project.artifactId}">
<featureManager>
<feature>localConnector-1.0</feature>
<feature>cdi-2.0</feature>
<feature>jsp-2.3</feature>
<feature>mpConfig-1.3</feature>
<feature>jaxrsClient-2.1</feature>
<feature>jsonp-1.1</feature>
</featureManager>
<httpEndpoint id="defaultHttpEndpoint" httpPort="${httpPort}" httpsPort="${httpsPort}"/>
<applicationManager autoExpand="true"/>
<applicationMonitor updateTrigger="mbean"/>
<application type="war" location="${project.build.finalName}.war" context-root="/"/>
</server>
@@ -0,0 +1,12 @@
#Client registration
client.clientId=webappclient
client.clientSecret=webappclientsecret
client.redirectUri=http://localhost:9180/callback
client.scope=resource.read resource.write
#Provider
provider.authorizationUri=http://127.0.0.1:9080/authorize
provider.tokenUri=http://127.0.0.1:9080/token
#Resource Server
resourceServerUri=http://localhost:9280/api
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
bean-discovery-mode="all">
</beans>
@@ -0,0 +1,5 @@
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="3.0">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
@@ -0,0 +1,111 @@
<%@ page import="org.eclipse.microprofile.config.Config" %>
<%@ page import="org.eclipse.microprofile.config.ConfigProvider" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OAuth2 Client</title>
<style>
body {
margin: 0px;
}
input[type=text], input[type=password] {
width: 75%;
padding: 4px 0px;
display: inline-block;
border: 1px solid #502bcc;
box-sizing: border-box;
}
.container-error {
padding: 16px;
border: 1px solid #cc102a;
background: #f50307;
width: 75%;
margin-left: 25px;
margin-bottom: 25px;
}
.container {
padding: 16px;
border: 1px solid #130ecc;
background: #eaecf5;
width: 75%;
margin-left: 25px;
margin-bottom: 25px;
}
</style>
</head>
<body>
<%
Config config1 = ConfigProvider.getConfig();
%>
<div class="container-error" style="visibility: <%=request.getAttribute("error")!=null?"visible":"hidden"%>">
<span>Error : ${error}</span>
</div>
<div class="container">
<span><h4>OAuth2 Authorization Server</h4></span>
<hr>
<p>Client Registration :</p>
<ul>
<li>client_id: <%=config1.getValue("client.clientId", String.class)%>
</li>
<li>client_secret: <%=config1.getValue("client.clientSecret", String.class)%>
</li>
<li>redirect_uri: <%=config1.getValue("client.redirectUri", String.class)%>
</li>
<li>scope: <%=config1.getValue("client.scope", String.class)%>
</li>
</ul>
<p>Authorization Server :</p>
<ul>
<li>authorization_uri: <%=config1.getValue("provider.authorizationUri", String.class)%>
</li>
<li>token_uri: <%=config1.getValue("provider.tokenUri", String.class)%>
</li>
</ul>
</div>
<div class="container">
<span><h4>OAuth2 Client</h4></span>
<hr>
<p>Token Request :</p>
<ul>
<li><a class="btn btn-primary" href="/authorize">Get Access Token</a></li>
</ul>
<p>Token Response:</p>
<ul>
<li>access_token: ${tokenResponse.getString("access_token")}</li>
<li>scope: ${tokenResponse.getString("scope")}</li>
<li>Expires in (s): ${tokenResponse.getInt("expires_in")}</li>
<li>refresh_token: ${tokenResponse.getString("refresh_token")}</li>
</ul>
</div>
<div class="container">
<span><h4>Refresh Token</h4></span>
<hr>
<ul>
<li><a href="refreshtoken">Refresh token (original scope)</a></li>
<li><a href="refreshtoken?scope=resource.read">Refresh token (scope: resource.read)</a></li>
<li><a href="refreshtoken?scope=resource.write">Refresh token (scope: resource.write)</a></li>
</ul>
</div>
<div class="container">
<span><h4>OAuth2 Resource Server Call</h4></span>
<hr>
<ul>
<li><a href="downstream?action=read">Read Protected Resource</a></li>
<li><a href="downstream?action=write">Write Protected Resource</a></li>
</ul>
</div>
</body>
</html>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<artifactId>oauth2-resource-server</artifactId>
<packaging>war</packaging>
<name>oauth2-resource-server</name>
<parent>
<groupId>com.baeldung.oauth2</groupId>
<artifactId>oauth2-framework-impl</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.eclipse.microprofile.jwt</groupId>
<artifactId>microprofile-jwt-auth-api</artifactId>
<version>${microprofile-jwt-auth-api.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>net.wasdev.wlp.maven.plugins</groupId>
<artifactId>liberty-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<properties>
<httpPort>9280</httpPort>
<httpsPort>8643</httpsPort>
<jwt.issuer>http://localhost:9080</jwt.issuer>
<jwt.resourceId>http://localhost:9280</jwt.resourceId>
<microprofile-jwt-auth-api.version>1.1</microprofile-jwt-auth-api.version>
</properties>
</project>
@@ -0,0 +1,13 @@
package com.baeldung.oauth2.resource.server;
import org.eclipse.microprofile.auth.LoginConfig;
import javax.annotation.security.DeclareRoles;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/api")
@DeclareRoles({"resource.read", "resource.write"})
@LoginConfig(authMethod = "MP-JWT")
public class OAuth2ResourceServerApplication extends Application {
}
@@ -0,0 +1,38 @@
package com.baeldung.oauth2.resource.server.secure;
import org.eclipse.microprofile.jwt.JsonWebToken;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import java.util.UUID;
@Path("/resource")
@RequestScoped
public class ProtectedResource {
@Inject
private JsonWebToken principal;
@GET
@RolesAllowed("resource.read")
@Path("/read")
public Response read() {
//DoStaff
return Response.ok("Hello, " + principal.getName()).build();
}
@POST
@RolesAllowed("resource.write")
@Path("/write")
public Response write() {
//DoStaff
return Response.ok("Hello, " + principal.getName())
.header("location", UUID.randomUUID().toString())
.build();
}
}
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<server description="${project.artifactId}">
<featureManager>
<feature>localConnector-1.0</feature>
<feature>cdi-2.0</feature>
<feature>jaxrs-2.1</feature>
<feature>jsonp-1.1</feature>
<feature>mpConfig-1.3</feature>
<feature>mpJwt-1.1</feature>
</featureManager>
<httpEndpoint id="defaultHttpEndpoint" httpPort="${httpPort}" httpsPort="${httpsPort}"/>
<mpJwt id="mpJwt123" audiences="${jwt.resourceId}"/>
<applicationManager autoExpand="true"/>
<applicationMonitor updateTrigger="mbean"/>
<application type="war" location="${project.build.finalName}.war" context-root="/"/>
</server>
@@ -0,0 +1,2 @@
mp.jwt.verify.publickey.location=/META-INF/public-key.pem
mp.jwt.verify.issuer=http://localhost:9080
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Is1Mr8GOqjHdGZGD8z/
Zi0yTknM1vFrQG44a4MZOSzkSI3Hb/2gdQQMwxDgl+FAvkW7PWMGnpCL1u3S/7es
T87AO/vbTLdu06lMV4oFhBUBu98P1mxFcwhtPSB6hu2bY2+mhi/vKX3Lvki7zrV1
q3LRzW69+QWAucgUGqLPWx/py2G4dhX/npq6YOysKHorOjGOkYGSC/5cbd23mbdT
UISxLwfbel6EpMi0Cko0/zgvFzmuDmCODnzkhahFBKfvHSnt7L2W0FDSXyinoce/
McdK38B/ogbBSpB3b7dWR7SDb2HnQHxq3Oa1p3rLp8zluzijAIiFwCUkQK5/SL+f
3wIDAQAB
-----END PUBLIC KEY-----
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
</beans>
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Eclipse MicroProfile demo</title>
</head>
<body>
<h2>MicroProfile</h2>
<a href="data/hello" target="_blank" >Hello JAX-RS endpoint</a> <br/>
<h3>Config</h3>
<a href="data/config/injected" target="_blank" >Injected config values</a> <br/>
<a href="data/config/lookup" target="_blank" >Config values by lookup</a> <br/>
<h3>JWT Auth</h3>
Look at readme.md on how to test protected endpoint.
</body>
</html>
+97
View File
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung.oauth2</groupId>
<artifactId>oauth2-framework-impl</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>oauth2-framework-impl</name>
<modules>
<module>oauth2-authorization-server</module>
<module>oauth2-resource-server</module>
<module>oauth2-client</module>
</modules>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>${javaee-web-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.config</groupId>
<artifactId>microprofile-config-api</artifactId>
<version>${microprofile-config-api.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
<resource>
<filtering>true</filtering>
<directory>${basedir}/src/main/liberty</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<pluginManagement>
<plugins>
<plugin>
<groupId>net.wasdev.wlp.maven.plugins</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>${openliberty.maven.version}</version>
<executions>
<execution>
<id>package-server</id>
<phase>package</phase>
<goals>
<goal>create-server</goal>
<goal>install-apps</goal>
</goals>
<configuration>
<outputDirectory>target/wlp-package</outputDirectory>
</configuration>
</execution>
</executions>
<configuration>
<assemblyArtifact>
<groupId>io.openliberty</groupId>
<artifactId>openliberty-webProfile8</artifactId>
<version>${openliberty.version}</version>
<type>zip</type>
</assemblyArtifact>
<configFile>target/classes/config/server.xml</configFile>
<appArchive>${project.build.directory}/${project.build.finalName}.war</appArchive>
<installAppPackages>project</installAppPackages>
<configDirectory>${project.basedir}/src/main/liberty/server</configDirectory>
<appsDirectory>apps</appsDirectory>
<looseApplication>true</looseApplication>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<failOnMissingWebXml>false</failOnMissingWebXml>
<openliberty.version>RELEASE</openliberty.version>
<openliberty.maven.version>2.6.4</openliberty.maven.version>
<javaee-web-api.version>8.0</javaee-web-api.version>
<microprofile-config-api.version>1.3</microprofile-config-api.version>
</properties>
</project>