Add How-to: Implement an Extension Authorization Grant Type
Closes gh-686
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.extgrant;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class CustomCodeGrantAuthenticationConverter implements AuthenticationConverter {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
// grant_type (REQUIRED)
|
||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||
if (!"urn:ietf:params:oauth:grant-type:custom_code".equals(grantType)) { // <1>
|
||||
return null;
|
||||
}
|
||||
|
||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
MultiValueMap<String, String> parameters = getParameters(request);
|
||||
|
||||
// code (REQUIRED)
|
||||
String code = parameters.getFirst(OAuth2ParameterNames.CODE); // <2>
|
||||
if (!StringUtils.hasText(code) ||
|
||||
parameters.get(OAuth2ParameterNames.CODE).size() != 1) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
parameters.forEach((key, value) -> {
|
||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
||||
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||
!key.equals(OAuth2ParameterNames.CODE)) {
|
||||
additionalParameters.put(key, value.get(0));
|
||||
}
|
||||
});
|
||||
|
||||
return new CustomCodeGrantAuthenticationToken(code, clientPrincipal, additionalParameters); // <3>
|
||||
}
|
||||
|
||||
// @fold:on
|
||||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
||||
parameterMap.forEach((key, values) -> {
|
||||
if (values.length > 0) {
|
||||
for (String value : values) {
|
||||
parameters.add(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return parameters;
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.extgrant;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class CustomCodeGrantAuthenticationProvider implements AuthenticationProvider {
|
||||
// @fold:on
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
|
||||
public CustomCodeGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService,
|
||||
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
||||
this.authorizationService = authorizationService;
|
||||
this.tokenGenerator = tokenGenerator;
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
CustomCodeGrantAuthenticationToken customCodeGrantAuthentication =
|
||||
(CustomCodeGrantAuthenticationToken) authentication;
|
||||
|
||||
// Ensure the client is authenticated
|
||||
OAuth2ClientAuthenticationToken clientPrincipal =
|
||||
getAuthenticatedClientElseThrowInvalidClient(customCodeGrantAuthentication);
|
||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||
|
||||
// Ensure the client is configured to use this authorization grant type
|
||||
if (!registeredClient.getAuthorizationGrantTypes().contains(customCodeGrantAuthentication.getGrantType())) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
||||
}
|
||||
|
||||
// TODO Validate the code parameter
|
||||
|
||||
// Generate the access token
|
||||
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal(clientPrincipal)
|
||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.authorizationGrantType(customCodeGrantAuthentication.getGrantType())
|
||||
.authorizationGrant(customCodeGrantAuthentication)
|
||||
.build();
|
||||
|
||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (generatedAccessToken == null) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||
"The token generator failed to generate the access token.", null);
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||
generatedAccessToken.getExpiresAt(), null);
|
||||
|
||||
// Initialize the OAuth2Authorization
|
||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(clientPrincipal.getName())
|
||||
.authorizationGrantType(customCodeGrantAuthentication.getGrantType());
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) ->
|
||||
metadata.put(
|
||||
OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
|
||||
((ClaimAccessor) generatedAccessToken).getClaims())
|
||||
);
|
||||
} else {
|
||||
authorizationBuilder.accessToken(accessToken);
|
||||
}
|
||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||
|
||||
// Save the OAuth2Authorization
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return CustomCodeGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
// @fold:on
|
||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
||||
}
|
||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
||||
return clientPrincipal;
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.extgrant;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class CustomCodeGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
||||
private final String code;
|
||||
|
||||
public CustomCodeGrantAuthenticationToken(String code, Authentication clientPrincipal,
|
||||
@Nullable Map<String, Object> additionalParameters) {
|
||||
super(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:custom_code"),
|
||||
clientPrincipal, additionalParameters);
|
||||
Assert.hasText(code, "code cannot be empty");
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.extgrant;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
// @formatter:off
|
||||
@Bean
|
||||
SecurityFilterChain authorizationServerSecurityFilterChain(
|
||||
HttpSecurity http,
|
||||
OAuth2AuthorizationService authorizationService,
|
||||
OAuth2TokenGenerator<?> tokenGenerator) throws Exception {
|
||||
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
|
||||
authorizationServerConfigurer
|
||||
.tokenEndpoint(tokenEndpoint ->
|
||||
tokenEndpoint
|
||||
.accessTokenRequestConverter( // <1>
|
||||
new CustomCodeGrantAuthenticationConverter())
|
||||
.authenticationProvider( // <2>
|
||||
new CustomCodeGrantAuthenticationProvider(
|
||||
authorizationService, tokenGenerator)));
|
||||
|
||||
// @fold:on
|
||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
|
||||
|
||||
http
|
||||
.securityMatcher(endpointsMatcher)
|
||||
.authorizeHttpRequests(authorize ->
|
||||
authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
|
||||
.apply(authorizationServerConfigurer);
|
||||
// @fold:off
|
||||
|
||||
return http.build();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
// @fold:on
|
||||
// @formatter:off
|
||||
@Bean
|
||||
RegisteredClientRepository registeredClientRepository() {
|
||||
RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
|
||||
.clientId("messaging-client")
|
||||
.clientSecret("{noop}secret")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:custom_code"))
|
||||
.scope("message.read")
|
||||
.scope("message.write")
|
||||
.build();
|
||||
|
||||
return new InMemoryRegisteredClientRepository(messagingClient);
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService() {
|
||||
return new InMemoryOAuth2AuthorizationService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource) {
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource));
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
return new DelegatingOAuth2TokenGenerator(
|
||||
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.extgrant;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import sample.test.SpringTestContext;
|
||||
import sample.test.SpringTestContextExtension;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class CustomCodeGrantTests {
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mvc;
|
||||
|
||||
@Test
|
||||
public void requestWhenTokenRequestValidThenTokenResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfig.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBasicAuth(registeredClient.getClientId(),
|
||||
registeredClient.getClientSecret().replace("{noop}", ""));
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(post("/oauth2/token")
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, "urn:ietf:params:oauth:grant-type:custom_code")
|
||||
.param(OAuth2ParameterNames.CODE, "7QR49T1W3")
|
||||
.headers(headers))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan
|
||||
static class AuthorizationServerConfig {
|
||||
}
|
||||
|
||||
}
|
||||
76
docs/src/docs/asciidoc/guides/how-to-ext-grant-type.adoc
Normal file
76
docs/src/docs/asciidoc/guides/how-to-ext-grant-type.adoc
Normal file
@@ -0,0 +1,76 @@
|
||||
[[how-to-extension-grant-type]]
|
||||
= How-to: Implement an Extension Authorization Grant Type
|
||||
:index-link: ../how-to.html
|
||||
:docs-dir: ..
|
||||
:examples-dir: {docs-dir}/examples
|
||||
|
||||
This guide shows how to extend xref:{docs-dir}/index.adoc#top[Spring Authorization Server] with an https://datatracker.ietf.org/doc/html/rfc6749#section-4.5[extension authorization grant type].
|
||||
The purpose of this guide is to demonstrate how to implement an extension authorization grant type and configure it at the xref:{docs-dir}/protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint].
|
||||
|
||||
Extending Spring Authorization Server with a new authorization grant type requires implementing an `AuthenticationConverter` and `AuthenticationProvider`, and configuring both components at the xref:{docs-dir}/protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint].
|
||||
In addition to the component implementations, a unique absolute URI needs to be assigned for use with the `grant_type` parameter.
|
||||
|
||||
* <<implement-authentication-converter>>
|
||||
* <<implement-authentication-provider>>
|
||||
* <<configure-token-endpoint>>
|
||||
* <<request-access-token>>
|
||||
|
||||
[[implement-authentication-converter]]
|
||||
== Implement AuthenticationConverter
|
||||
|
||||
Assuming the absolute URI for the `grant_type` parameter is `urn:ietf:params:oauth:grant-type:custom_code` and the `code` parameter represents the authorization grant, the following example shows a sample implementation of the `AuthenticationConverter`:
|
||||
|
||||
.AuthenticationConverter
|
||||
[source,java]
|
||||
----
|
||||
include::{examples-dir}/src/main/java/sample/extgrant/CustomCodeGrantAuthenticationConverter.java[]
|
||||
----
|
||||
|
||||
TIP: Click on the "Expand folded text" icon in the code sample above to display the full example.
|
||||
|
||||
<1> If the `grant_type` parameter is *not* `urn:ietf:params:oauth:grant-type:custom_code`, then return `null`, allowing another `AuthenticationConverter` to process the token request.
|
||||
<2> The `code` parameter contains the authorization grant.
|
||||
<3> Return an instance of `CustomCodeGrantAuthenticationToken`, which is processed by <<implement-authentication-provider,`CustomCodeGrantAuthenticationProvider`>>.
|
||||
|
||||
[[implement-authentication-provider]]
|
||||
== Implement AuthenticationProvider
|
||||
|
||||
The `AuthenticationProvider` implementation is responsible for validating the authorization grant, and if valid and authorized, issues an access token.
|
||||
|
||||
The following example shows a sample implementation of the `AuthenticationProvider`:
|
||||
|
||||
.AuthenticationProvider
|
||||
[source,java]
|
||||
----
|
||||
include::{examples-dir}/src/main/java/sample/extgrant/CustomCodeGrantAuthenticationProvider.java[]
|
||||
----
|
||||
|
||||
NOTE: `CustomCodeGrantAuthenticationProvider` processes `CustomCodeGrantAuthenticationToken`, which is created by <<implement-authentication-converter,`CustomCodeGrantAuthenticationConverter`>>.
|
||||
|
||||
[[configure-token-endpoint]]
|
||||
== Configure OAuth2 Token Endpoint
|
||||
|
||||
The following example shows how to configure the xref:{docs-dir}/protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint] with the `AuthenticationConverter` and `AuthenticationProvider`:
|
||||
|
||||
.SecurityConfig
|
||||
[source,java]
|
||||
----
|
||||
include::{examples-dir}/src/main/java/sample/extgrant/SecurityConfig.java[]
|
||||
----
|
||||
|
||||
<1> Add the `AuthenticationConverter` to the OAuth2 Token endpoint configuration.
|
||||
<2> Add the `AuthenticationProvider` to the OAuth2 Token endpoint configuration.
|
||||
|
||||
[[request-access-token]]
|
||||
== Request the Access Token
|
||||
|
||||
The client can request the access token by making the following (authenticated) request to the OAuth2 Token endpoint:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
POST /oauth2/token HTTP/1.1
|
||||
Authorization: Basic bWVzc2FnaW5nLWNsaWVudDpzZWNyZXQ=
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=urn:ietf:params:oauth:grant-type:custom_code&code=7QR49T1W3
|
||||
----
|
||||
@@ -6,5 +6,6 @@
|
||||
|
||||
* xref:guides/how-to-pkce.adoc[Authenticate using a Single Page Application with PKCE]
|
||||
* xref:guides/how-to-social-login.adoc[Authenticate using Social Login]
|
||||
* xref:guides/how-to-ext-grant-type.adoc[Implement an Extension Authorization Grant Type]
|
||||
* xref:guides/how-to-userinfo.adoc[Customize the OpenID Connect 1.0 UserInfo response]
|
||||
* xref:guides/how-to-jpa.adoc[Implement core services with JPA]
|
||||
|
||||
Reference in New Issue
Block a user