Melaksanakan Kerangka Kebenaran OAuth 2.0 Menggunakan Jakarta EE

1. Gambaran keseluruhan

Dalam tutorial ini, kami akan memberikan implementasi untuk OAuth 2.0 Authorization Framework menggunakan Jakarta EE dan MicroProfile. Yang paling penting, kita akan melaksanakan interaksi peranan OAuth 2.0 melalui jenis pemberian Kod Pengesahan. Motivasi di sebalik penulisan ini adalah memberikan sokongan untuk projek yang dilaksanakan menggunakan Jakarta EE kerana ini belum memberikan sokongan untuk OAuth.

Untuk peranan yang paling penting, Pelayan Pengesahan, kita akan melaksanakan Titik Akhir Pengesahan, Titik Akhir Token dan tambahannya, Titik Akhir Kunci JWK , yang berguna bagi Pelayan Sumber untuk mengambil kunci awam.

Oleh kerana kami mahu pelaksanaannya mudah dan mudah untuk penyiapan cepat, kami akan menggunakan kedai pelanggan dan pengguna yang sudah didaftarkan sebelumnya, dan jelas sebuah kedai JWT untuk token akses.

Sebelum masuk ke topik, penting untuk diperhatikan bahawa contoh dalam tutorial ini adalah untuk tujuan pendidikan. Untuk sistem pengeluaran, sangat digalakkan menggunakan penyelesaian yang matang dan teruji seperti Keycloak.

2. Gambaran Keseluruhan OAuth 2.0

Di bahagian ini, kami akan memberikan gambaran ringkas mengenai peranan OAuth 2.0 dan aliran pemberian Kod Pengesahan.

2.1. Peranan

Rangka kerja OAuth 2.0 menunjukkan kerjasama antara empat peranan berikut:

  • Pemilik Sumber : Biasanya, ini adalah pengguna akhir - entiti yang mempunyai beberapa sumber yang patut dilindungi
  • Pelayan Sumber : Perkhidmatan yang melindungi data pemilik sumber, biasanya menerbitkannya melalui REST API
  • Pelanggan : Aplikasi yang menggunakan data pemilik sumber
  • Pelayan Pengesahan : Aplikasi yang memberikan kebenaran - atau kuasa - kepada pelanggan dalam bentuk token yang akan tamat tempoh

2.2. Jenis Pemberian Kebenaran

A jenis geran adalah bagaimana pelanggan mendapat kebenaran untuk menggunakan data pemilik sumber ini, akhirnya dalam bentuk token akses.

Secara semula jadi, pelbagai jenis pelanggan lebih suka pelbagai jenis geran:

  • Kod Kebenaran : Paling disukai- sama ada aplikasi web, aplikasi asli, atau aplikasi satu halaman , walaupun aplikasi asli dan halaman tunggal memerlukan perlindungan tambahan yang disebut PKCE
  • Refresh Token : Geran pembaharuan khas, sesuai untuk aplikasi web untuk memperbaharui token yang ada
  • Kredensial Pelanggan : Diutamakan untuk komunikasi perkhidmatan ke perkhidmatan , katakan apabila pemilik sumber bukan pengguna akhir
  • Kata Laluan Pemilik Sumber : Lebih disukai untuk pengesahan aplikasi asli pihak pertama , katakan apabila aplikasi mudah alih memerlukan halaman log masuk sendiri

Di samping itu, pelanggan boleh menggunakan jenis pemberian yang tersirat . Walau bagaimanapun, biasanya lebih selamat menggunakan pemberian kod kebenaran dengan PKCE.

2.3. Aliran Pemberian Kod Kebenaran

Oleh kerana aliran pemberian kod kebenaran adalah yang paling biasa, mari kita tinjau juga cara kerjanya, dan itulah yang akan kita bina dalam tutorial ini.

Aplikasi - pelanggan - meminta izin dengan mengarahkan ke titik akhir pelayan kebenaran / membenarkan . Ke titik akhir ini, aplikasi memberikan titik akhir panggilan balik .

Pelayan kebenaran biasanya akan meminta kebenaran kepada pengguna akhir - pemilik sumber. Sekiranya pengguna akhir memberi kebenaran, pelayan kebenaran akan mengarahkan kembali ke panggil balik dengan kod .

Aplikasi menerima kod ini dan kemudian membuat panggilan yang disahkan ke titik akhir pelayan / token kebenaran . Dengan "disahkan", kami bermaksud bahawa aplikasi membuktikan siapa itu sebagai sebahagian daripada panggilan ini. Sekiranya semuanya muncul dengan teratur, pelayan kebenaran bertindak balas dengan token.

Dengan token di tangan, aplikasi membuat permintaannya ke API - pelayan sumber - dan API tersebut akan mengesahkan token tersebut. Ia boleh meminta pelayan kebenaran untuk mengesahkan token menggunakan titik akhir / introspeksi . Atau, jika token itu serba lengkap, pelayan sumber dapat mengoptimumkan dengan mengesahkan tandatangan token secara tempatan, seperti yang berlaku pada JWT.

2.4. Apa yang disokong oleh Jakarta EE?

Belum banyak. Dalam tutorial ini, kami akan membina banyak perkara dari bawah ke atas.

3. Pelayan Pengesahan OAuth 2.0

Dalam pelaksanaan ini, kami akan memfokus pada jenis pemberian yang paling biasa digunakan : Kod Kebenaran.

3.1. Pendaftaran Pelanggan dan Pengguna

Pelayan kebenaran tentu saja perlu mengetahui tentang pelanggan dan pengguna sebelum dapat membenarkan permintaan mereka. Adalah biasa bagi pelayan kebenaran untuk mempunyai UI untuk ini.

Untuk kesederhanaan, kami akan menggunakan pelanggan yang telah dikonfigurasi sebelumnya:

INSERT INTO clients (client_id, client_secret, redirect_uri, scope, authorized_grant_types) VALUES ('webappclient', 'webappclientsecret', '//localhost:9180/callback', 'resource.read resource.write', 'authorization_code refresh_token');
@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; // ... }

Dan pengguna pra-konfigurasi:

INSERT INTO users (user_id, password, roles, scopes) VALUES ('appuser', 'appusersecret', 'USER', 'resource.read resource.write');
@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; // ... }

Perhatikan bahawa demi tutorial ini, kami telah menggunakan kata laluan dalam teks biasa, tetapi dalam lingkungan produksi, kata sandi harus dicincang .

Untuk sisa tutorial ini, kami akan menunjukkan bagaimana pemohon - pemilik sumber - dapat memberikan akses kepada pelanggan web - aplikasi - dengan menerapkan Kod Pengesahan.

3.2. Titik Akhir Kebenaran

The main role of the authorization endpoint is to first authenticate the user and then ask for the permissions – or scopes – that the application wants.

As instructed by the OAuth2 specs, this endpoint should support the HTTP GET method, although it can also support the HTTP POST method. In this implementation, we'll support only the HTTP GET method.

First, the authorization endpoint requires that the user be authenticated. The spec doesn't require a certain way here, so let's use Form Authentication from the Jakarta EE 8 Security API:

@FormAuthenticationMechanismDefinition( loginToContinue = @LoginToContinue(loginPage = "/login.jsp", errorPage = "/login.jsp") )

The user will be redirected to /login.jsp for authentication and then will be available as a CallerPrincipal through the SecurityContext API:

Principal principal = securityContext.getCallerPrincipal();

We can put these together using JAX-RS:

@FormAuthenticationMechanismDefinition( loginToContinue = @LoginToContinue(loginPage = "/login.jsp", errorPage = "/login.jsp") ) @Path("authorize") public class AuthorizationEndpoint { //... @GET @Produces(MediaType.TEXT_HTML) public Response doGet(@Context HttpServletRequest request, @Context HttpServletResponse response, @Context UriInfo uriInfo) throws ServletException, IOException { MultivaluedMap params = uriInfo.getQueryParameters(); Principal principal = securityContext.getCallerPrincipal(); // ... } }

At this point, the authorization endpoint can start processing the application's request, which must contain response_type and client_id parameters and – optionally, but recommended – the redirect_uri, scope, and state parameters.

The client_id should be a valid client, in our case from the clients database table.

The redirect_uri, if specified, should also match what we find in the clients database table.

And, because we're doing Authorization Code, response_type is code.

Since authorization is a multi-step process, we can temporarily store these values in the session:

request.getSession().setAttribute("ORIGINAL_PARAMS", params);

And then prepare to ask the user which permissions the application may use, redirecting to that page:

String allowedScopes = checkUserScopes(user.getScopes(), requestedScope); request.setAttribute("scopes", allowedScopes); request.getRequestDispatcher("/authorize.jsp").forward(request, response);

3.3. User Scopes Approval

At this point, the browser renders an authorization UI for the user, and the user makes a selection. Then, the browser submits the user's selection in an HTTP POST:

@POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.TEXT_HTML) public Response doPost(@Context HttpServletRequest request, @Context HttpServletResponse response, MultivaluedMap params) throws Exception { MultivaluedMap originalParams = (MultivaluedMap) request.getSession().getAttribute("ORIGINAL_PARAMS"); // ... String approvalStatus = params.getFirst("approval_status"); // YES OR NO // ... if YES List approvedScopes = params.get("scope"); // ... }

Next, we generate a temporary code that refers to the user_id, client_id, andredirect_uri, all of which the application will use later when it hits the token endpoint.

So let's create an AuthorizationCode JPA Entity with an auto-generated id:

@Entity @Table(name) public class AuthorizationCode { @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name = "code") private String code; //... }

And then populate it:

AuthorizationCode authorizationCode = new AuthorizationCode(); authorizationCode.setClientId(clientId); authorizationCode.setUserId(userId); authorizationCode.setApprovedScopes(String.join(" ", authorizedScopes)); authorizationCode.setExpirationDate(LocalDateTime.now().plusMinutes(2)); authorizationCode.setRedirectUri(redirectUri);

When we save the bean, the code attribute is auto-populated, and so we can get it and send it back to the client:

appDataRepository.save(authorizationCode); String code = authorizationCode.getCode();

Note that our authorization code will expire in two minutes – we should be as conservative as we can with this expiration. It can be short since the client is going to exchange it right away for an access token.

We then redirect back to the application's redirect_uri, giving it the code as well as any state parameter that the application specified in its /authorize request:

StringBuilder sb = new StringBuilder(redirectUri); // ... sb.append("?code=").append(code); String state = params.getFirst("state"); if (state != null) { sb.append("&state=").append(state); } URI location = UriBuilder.fromUri(sb.toString()).build(); return Response.seeOther(location).build();

Note again that redirectUri is whatever exists in the clients table, not the redirect_uri request parameter.

So, our next step is for the client to receive this code and exchange it for an access token using the token endpoint.

3.4. Token Endpoint

As opposed to the authorization endpoint, the token endpoint doesn't need a browser to communicate with the client, and we'll, therefore, implement it as a JAX-RS endpoint:

@Path("token") public class TokenEndpoint { List supportedGrantTypes = Collections.singletonList("authorization_code"); @Inject private AppDataRepository appDataRepository; @Inject Instance authorizationGrantTypeHandlers; @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response token(MultivaluedMap params, @HeaderParam(HttpHeaders.AUTHORIZATION) String authHeader) throws JOSEException { //... } }

The token endpoint requires a POST, as well as encoding the parameters using the application/x-www-form-urlencoded media type.

As we discussed, we'll be supporting only the authorization code grant type:

List supportedGrantTypes = Collections.singletonList("authorization_code");

So, the received grant_type as a required parameter should be supported:

String grantType = params.getFirst("grant_type"); Objects.requireNonNull(grantType, "grant_type params is required"); if (!supportedGrantTypes.contains(grantType)) { JsonObject error = Json.createObjectBuilder() .add("error", "unsupported_grant_type") .add("error_description", "grant type should be one of :" + supportedGrantTypes) .build(); return Response.status(Response.Status.BAD_REQUEST) .entity(error).build(); }

Next, we check the client authentication through via HTTP Basic authentication. That is, we check if the received client_id and client_secret, through the Authorization header, matches a registered client:

String[] clientCredentials = extract(authHeader); String clientId = clientCredentials[0]; String clientSecret = clientCredentials[1]; Client client = appDataRepository.getClient(clientId); if (client == null || clientSecret == null || !clientSecret.equals(client.getClientSecret())) { JsonObject error = Json.createObjectBuilder() .add("error", "invalid_client") .build(); return Response.status(Response.Status.UNAUTHORIZED) .entity(error).build(); }

Finally, we delegate the production of the TokenResponse to a corresponding grant type handler:

public interface AuthorizationGrantTypeHandler { TokenResponse createAccessToken(String clientId, MultivaluedMap params) throws Exception; }

As we're more interested in the authorization code grant type, we've provided an adequate implementation as a CDI bean and decorated it with the Named annotation:

@Named("authorization_code")

At runtime, and according to the received grant_type value, the corresponding implementation is activated through the CDI Instance mechanism:

String grantType = params.getFirst("grant_type"); //... AuthorizationGrantTypeHandler authorizationGrantTypeHandler = authorizationGrantTypeHandlers.select(NamedLiteral.of(grantType)).get();

It's now time to produce /token‘s response.

3.5. RSA Private and Public Keys

Before generating the token, we need an RSA private key for signing tokens.

For this purpose, we'll be using OpenSSL:

# PRIVATE KEY openssl genpkey -algorithm RSA -out private-key.pem -pkeyopt rsa_keygen_bits:2048

The private-key.pem is provided to the server through the MicroProfile Config signingKey property using the file META-INF/microprofile-config.properties:

signingkey=/META-INF/private-key.pem

The server can read the property using the injected Config object:

String signingkey = config.getValue("signingkey", String.class);

Similarly, we can generate the corresponding public key:

# PUBLIC KEY openssl rsa -pubout -in private-key.pem -out public-key.pem

And use the MicroProfile Config verificationKey to read it:

verificationkey=/META-INF/public-key.pem

The server should make it available for the resource server for the purpose of verification. This is done through a JWK endpoint.

Nimbus JOSE+JWT is a library that can be a big help here. Let's first add the nimbus-jose-jwt dependency:

 com.nimbusds nimbus-jose-jwt 7.7 

And now, we can leverage Nimbus's JWK support to simplify our endpoint:

@Path("jwk") @ApplicationScoped public class JWKEndpoint { @GET public Response getKey(@QueryParam("format") String format) throws Exception { //... 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(); } //... } }

We've used the format parameter to switch between the PEM and JWK formats. The MicroProfile JWT which we'll use for implementing the resource server supports both these formats.

3.6. Token Endpoint Response

It's now time for a given AuthorizationGrantTypeHandler to create the token response. In this implementation, we'll support only the structured JWT Tokens.

For creating a token in this format, we'll again use the Nimbus JOSE+JWT library, but there are numerous other JWT libraries, too.

So, to create a signed JWT, we first have to construct the JWT header:

JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build();

Then, we build the payload which is a Set of standardized and custom claims:

Instant now = Instant.now(); Long expiresInMin = 30L; Date in30Min = Date.from(now.plus(expiresInMin, ChronoUnit.MINUTES)); JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder() .issuer("//localhost:9080") .subject(authorizationCode.getUserId()) .claim("upn", authorizationCode.getUserId()) .audience("//localhost:9280") .claim("scope", authorizationCode.getApprovedScopes()) .claim("groups", Arrays.asList(authorizationCode.getApprovedScopes().split(" "))) .expirationTime(in30Min) .notBeforeTime(Date.from(now)) .issueTime(Date.from(now)) .jwtID(UUID.randomUUID().toString()) .build(); SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaims);

In addition to the standard JWT claims, we've added two more claims – upn and groups – as they're needed by the MicroProfile JWT. The upn will be mapped to the Jakarta EE Security CallerPrincipal and the groups will be mapped to Jakarta EE Roles.

Now that we have the header and the payload, we need to sign the access token with an RSA private key. The corresponding RSA public key will be exposed through the JWK endpoint or made available by other means so that the resource server can use it to verify the access token.

As we've provided the private key as a PEM format, we should retrieve it and transform it into an RSAPrivateKey:

SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaims); //... String signingkey = config.getValue("signingkey", String.class); String pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString(signingkey); RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey);

Next, we sign and serialize the JWT:

signedJWT.sign(new RSASSASigner(rsaKey.toRSAPrivateKey())); String accessToken = signedJWT.serialize();

And finally we construct a token response:

return Json.createObjectBuilder() .add("token_type", "Bearer") .add("access_token", accessToken) .add("expires_in", expiresInMin * 60) .add("scope", authorizationCode.getApprovedScopes()) .build();

which is, thanks to JSON-P, serialized to JSON format and sent to the client:

{ "access_token": "acb6803a48114d9fb4761e403c17f812", "token_type": "Bearer", "expires_in": 1800, "scope": "resource.read resource.write" }

4. OAuth 2.0 Client

In this section, we'll be building a web-based OAuth 2.0 Client using the Servlet, MicroProfile Config, and JAX RS Client APIs.

More precisely, we'll be implementing two main servlets: one for requesting the authorization server's authorization endpoint and getting a code using the authorization code grant type, and another servlet for using the received code and requesting an access token from the authorization server's token endpoint.

Additionally, we'll be implementing two more servlets: One for getting a new access token using the refresh token grant type, and another for accessing the resource server's APIs.

4.1. OAuth 2.0 Client Details

As the client is already registered within the authorization server, we first need to provide the client registration information:

  • client_id: Client Identifier and it's usually issued by the authorization server during the registration process.
  • client_secret: Client Secret.
  • redirect_uri: Location where to receive the authorization code.
  • scope: Client requested permissions.

Additionally, the client should know the authorization server's authorization and token endpoints:

  • authorization_uri: Location of the authorization server authorization endpoint that we can use to get a code.
  • token_uri: Location of the authorization server token endpoint that we can use to get a token.

All this information is provided through the MicroProfile Config file, META-INF/microprofile-config.properties:

# Client registration client.clientId=webappclient client.clientSecret=webappclientsecret client.redirectUri=//localhost:9180/callback client.scope=resource.read resource.write # Provider provider.authorizationUri=//127.0.0.1:9080/authorize provider.tokenUri=//127.0.0.1:9080/token

4.2. Authorization Code Request

The flow of getting an authorization code starts with the client by redirecting the browser to the authorization server's authorization endpoint.

Typically, this happens when the user tries to access a protected resource API without authorization, or by explicitly by invoking the client /authorize path:

@WebServlet(urlPatterns = "/authorize") public class AuthorizationCodeServlet extends HttpServlet { @Inject private Config config; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //... } }

In the doGet() method, we start by generating and storing a security state value:

String state = UUID.randomUUID().toString(); request.getSession().setAttribute("CLIENT_LOCAL_STATE", state);

Then, we retrieve the client configuration information:

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

We'll then append these pieces of information as query parameters to the authorization server's authorization endpoint:

String authorizationLocation = authorizationUri + "?response_type=code" + "&client_id=" + clientId + "&redirect_uri=" + redirectUri + "&scope=" + scope + "&state=" + state;

And finally, we'll redirect the browser to this URL:

response.sendRedirect(authorizationLocation);

After processing the request, the authorization server's authorization endpoint will generate and append a code, in addition to the received state parameter, to the redirect_uri and will redirect back the browser //localhost:9081/callback?code=A123&state=Y.

4.3. Access Token Request

The client callback servlet, /callback, begins by validating the received state:

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

Next, we'll use the code we previously received to request an access token through the authorization server's token endpoint:

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)); TokenResponse tokenResponse = target.request(MediaType.APPLICATION_JSON_TYPE) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeaderValue()) .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), TokenResponse.class);

As we can see, there's no browser interaction for this call, and the request is made directly using the JAX-RS client API as an HTTP POST.

As the token endpoint requires the client authentication, we have included the client credentials client_id and client_secret in the Authorization header.

The client can use this access token to invoke the resource server APIs which is the subject of the next subsection.

4.4. Protected Resource Access

At this point, we have a valid access token and we can call the resource server's /read and /write APIs.

To do that, we have to provide the Authorization header. Using the JAX-RS Client API, this is simply done through the Invocation.Builder header() method:

resourceWebTarget = webTarget.path("resource/read"); Invocation.Builder invocationBuilder = resourceWebTarget.request(); response = invocationBuilder .header("authorization", tokenResponse.getString("access_token")) .get(String.class);

5. OAuth 2.0 Resource Server

In this section, we'll be building a secured web application based on JAX-RS, MicroProfile JWT, and MicroProfile Config. The MicroProfile JWT takes care of validating the received JWT and mapping the JWT scopes to Jakarta EE roles.

5.1. Maven Dependencies

In addition to the Java EE Web API dependency, we need also the MicroProfile Config and MicroProfile JWT APIs:

 javax javaee-web-api 8.0 provided   org.eclipse.microprofile.config microprofile-config-api 1.3   org.eclipse.microprofile.jwt microprofile-jwt-auth-api 1.1 

5.2. JWT Authentication Mechanism

The MicroProfile JWT provides an implementation of the Bearer Token Authentication mechanism. This takes care of processing the JWT present in the Authorization header, makes available a Jakarta EE Security Principal as a JsonWebToken which holds the JWT claims, and maps the scopes to Jakarta EE roles. Take a look at the Jakarta EE Security API for more background.

To enable the JWT authentication mechanism in the server, we need to add the LoginConfig annotation in the JAX-RS application:

@ApplicationPath("/api") @DeclareRoles({"resource.read", "resource.write"}) @LoginConfig(authMethod = "MP-JWT") public class OAuth2ResourceServerApplication extends Application { }

Additionally, MicroProfile JWT needs the RSA public key in order to verify the JWT signature. We can provide this either by introspection or, for simplicity, by manually copying the key from the authorization server. In either case, we need to provide the location of the public key:

mp.jwt.verify.publickey.location=/META-INF/public-key.pem

Finally, the MicroProfile JWT needs to verify the iss claim of the incoming JWT, which should be present and match the value of the MicroProfile Config property:

mp.jwt.verify.issuer=//127.0.0.1:9080

Typically, this is the location of the Authorization Server.

5.3. The Secured Endpoints

For demonstration purposes, we'll add a resource API with two endpoints. One is a read endpoint that's accessible by users having the resource.read scope and another write endpoint for users with resource.write scope.

The restriction on the scopes is done through the @RolesAllowed annotation:

@Path("/resource") @RequestScoped public class ProtectedResource { @Inject private JsonWebToken principal; @GET @RolesAllowed("resource.read") @Path("/read") public String read() { return "Protected Resource accessed by : " + principal.getName(); } @POST @RolesAllowed("resource.write") @Path("/write") public String write() { return "Protected Resource accessed by : " + principal.getName(); } }

6. Running All Servers

To run one server, we just need to invoke the Maven command in the corresponding directory:

mvn package liberty:run-server

The authorization server, the client and the resource server will be running and available respectively at the following locations:

# Authorization Server //localhost:9080/ # Client //localhost:9180/ # Resource Server //localhost:9280/ 

So, we can access the client home page and then we click on “Get Access Token” to start the authorization flow. After receiving the access token, we can access the resource server's read and write APIs.

Depending on the granted scopes, the resource server will respond either by a successful message or we'll get an HTTP 403 forbidden status.

7. Conclusion

In this article, we've provided an implementation of an OAuth 2.0 Authorization Server that can be used with any compatible OAuth 2.0 Client and Resource Server.

Untuk menjelaskan kerangka keseluruhan, kami juga telah menyediakan pelaksanaan untuk klien dan pelayan sumber. Untuk menerapkan semua komponen ini, kami telah menggunakan API Jakarta EE 8, terutama CDI, Servlet, JAX RS, Jakarta EE Security. Selain itu, kami telah menggunakan API EE pseudo-Jakarta dari MicroProfile: MicroProfile Config dan MicroProfile JWT.

Kod sumber lengkap untuk contoh boleh didapati di GitHub. Perhatikan bahawa kod tersebut merangkumi contoh kod kebenaran dan jenis pemberian token penyegaran.

Akhirnya, penting untuk mengetahui sifat pendidikan artikel ini dan bahawa contoh yang diberikan tidak boleh digunakan dalam sistem pengeluaran.