7 changed files with 520 additions and 0 deletions
@ -0,0 +1,83 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 { |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@
@@ -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 |
||||
---- |
||||
Loading…
Reference in new issue