Last active
April 5, 2024 04:58
-
-
Save akuma8/2eb244b796f3d3506956207997fb290f to your computer and use it in GitHub Desktop.
Example of Password Grant Type implementation for Spring Authorization Server: https://github.com/spring-projects/spring-authorization-server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2020-2022 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 com.company.authorizationserver.configuration.grants; | |
import org.springframework.security.authentication.AuthenticationProvider; | |
import org.springframework.security.core.Authentication; | |
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; | |
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; | |
import org.springframework.security.oauth2.core.OAuth2RefreshToken; | |
import org.springframework.security.oauth2.core.OAuth2Token; | |
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; | |
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; | |
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; | |
/** | |
* Utility methods for the OAuth 2.0 {@link AuthenticationProvider}'s. This class comes from Spring Security OAuth2 Authorization Server. | |
*/ | |
public final class OAuth2AuthenticationProviderUtils { | |
private OAuth2AuthenticationProviderUtils() { | |
} | |
public static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) { | |
OAuth2ClientAuthenticationToken clientPrincipal = null; | |
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getClass())) { | |
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication; | |
} | |
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) { | |
return clientPrincipal; | |
} | |
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); | |
} | |
static <T extends OAuth2Token> OAuth2Authorization invalidate( | |
OAuth2Authorization authorization, T token) { | |
// @formatter:off | |
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization) | |
.token(token, | |
(metadata) -> | |
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)); | |
if (OAuth2RefreshToken.class.isAssignableFrom(token.getClass())) { | |
authorizationBuilder.token( | |
authorization.getAccessToken().getToken(), | |
(metadata) -> | |
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)); | |
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = | |
authorization.getToken(OAuth2AuthorizationCode.class); | |
if (authorizationCode != null && !authorizationCode.isInvalidated()) { | |
authorizationBuilder.token( | |
authorizationCode.getToken(), | |
(metadata) -> | |
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)); | |
} | |
} | |
// @formatter:on | |
return authorizationBuilder.build(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2020-2022 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 com.company.authorizationserver.configuration.grants; | |
import com.nimbusds.jose.jwk.source.JWKSource; | |
import com.nimbusds.jose.proc.SecurityContext; | |
import org.springframework.beans.factory.BeanFactoryUtils; | |
import org.springframework.beans.factory.NoSuchBeanDefinitionException; | |
import org.springframework.beans.factory.NoUniqueBeanDefinitionException; | |
import org.springframework.context.ApplicationContext; | |
import org.springframework.core.ResolvableType; | |
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | |
import org.springframework.security.oauth2.core.OAuth2Token; | |
import org.springframework.security.oauth2.jwt.JwtEncoder; | |
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; | |
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService; | |
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService; | |
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; | |
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; | |
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; | |
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; | |
import org.springframework.security.oauth2.server.authorization.token.*; | |
import org.springframework.util.StringUtils; | |
import java.util.Map; | |
/** | |
* Utility methods for the OAuth 2.0 Configurers. This class comes from Spring Security OAuth2 Authorization Server. | |
*/ | |
public final class OAuth2ConfigurerUtils { | |
private OAuth2ConfigurerUtils() { | |
} | |
static RegisteredClientRepository getRegisteredClientRepository(HttpSecurity httpSecurity) { | |
RegisteredClientRepository registeredClientRepository = httpSecurity.getSharedObject(RegisteredClientRepository.class); | |
if (registeredClientRepository == null) { | |
registeredClientRepository = getBean(httpSecurity, RegisteredClientRepository.class); | |
httpSecurity.setSharedObject(RegisteredClientRepository.class, registeredClientRepository); | |
} | |
return registeredClientRepository; | |
} | |
static OAuth2AuthorizationService getAuthorizationService(HttpSecurity httpSecurity) { | |
OAuth2AuthorizationService authorizationService = httpSecurity.getSharedObject(OAuth2AuthorizationService.class); | |
if (authorizationService == null) { | |
authorizationService = getOptionalBean(httpSecurity, OAuth2AuthorizationService.class); | |
if (authorizationService == null) { | |
authorizationService = new InMemoryOAuth2AuthorizationService(); | |
} | |
httpSecurity.setSharedObject(OAuth2AuthorizationService.class, authorizationService); | |
} | |
return authorizationService; | |
} | |
static OAuth2AuthorizationConsentService getAuthorizationConsentService(HttpSecurity httpSecurity) { | |
OAuth2AuthorizationConsentService authorizationConsentService = httpSecurity.getSharedObject(OAuth2AuthorizationConsentService.class); | |
if (authorizationConsentService == null) { | |
authorizationConsentService = getOptionalBean(httpSecurity, OAuth2AuthorizationConsentService.class); | |
if (authorizationConsentService == null) { | |
authorizationConsentService = new InMemoryOAuth2AuthorizationConsentService(); | |
} | |
httpSecurity.setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService); | |
} | |
return authorizationConsentService; | |
} | |
public static OAuth2TokenGenerator<? extends OAuth2Token> getTokenGenerator(HttpSecurity httpSecurity) { | |
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = httpSecurity.getSharedObject(OAuth2TokenGenerator.class); | |
if (tokenGenerator == null) { | |
tokenGenerator = getOptionalBean(httpSecurity, OAuth2TokenGenerator.class); | |
if (tokenGenerator == null) { | |
JwtGenerator jwtGenerator = getJwtGenerator(httpSecurity); | |
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator(); | |
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getAccessTokenCustomizer(httpSecurity); | |
if (accessTokenCustomizer != null) { | |
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer); | |
} | |
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); | |
if (jwtGenerator != null) { | |
tokenGenerator = new DelegatingOAuth2TokenGenerator( | |
jwtGenerator, accessTokenGenerator, refreshTokenGenerator); | |
} else { | |
tokenGenerator = new DelegatingOAuth2TokenGenerator( | |
accessTokenGenerator, refreshTokenGenerator); | |
} | |
} | |
httpSecurity.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator); | |
} | |
return tokenGenerator; | |
} | |
private static JwtGenerator getJwtGenerator(HttpSecurity httpSecurity) { | |
JwtGenerator jwtGenerator = httpSecurity.getSharedObject(JwtGenerator.class); | |
if (jwtGenerator == null) { | |
JwtEncoder jwtEncoder = getJwtEncoder(httpSecurity); | |
if (jwtEncoder != null) { | |
jwtGenerator = new JwtGenerator(jwtEncoder); | |
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(httpSecurity); | |
if (jwtCustomizer != null) { | |
jwtGenerator.setJwtCustomizer(jwtCustomizer); | |
} | |
httpSecurity.setSharedObject(JwtGenerator.class, jwtGenerator); | |
} | |
} | |
return jwtGenerator; | |
} | |
private static JwtEncoder getJwtEncoder(HttpSecurity httpSecurity) { | |
JwtEncoder jwtEncoder = httpSecurity.getSharedObject(JwtEncoder.class); | |
if (jwtEncoder == null) { | |
jwtEncoder = getOptionalBean(httpSecurity, JwtEncoder.class); | |
if (jwtEncoder == null) { | |
JWKSource<SecurityContext> jwkSource = getJwkSource(httpSecurity); | |
if (jwkSource != null) { | |
jwtEncoder = new NimbusJwtEncoder(jwkSource); | |
} | |
} | |
if (jwtEncoder != null) { | |
httpSecurity.setSharedObject(JwtEncoder.class, jwtEncoder); | |
} | |
} | |
return jwtEncoder; | |
} | |
static JWKSource<SecurityContext> getJwkSource(HttpSecurity httpSecurity) { | |
JWKSource<SecurityContext> jwkSource = httpSecurity.getSharedObject(JWKSource.class); | |
if (jwkSource == null) { | |
ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, SecurityContext.class); | |
jwkSource = getOptionalBean(httpSecurity, type); | |
if (jwkSource != null) { | |
httpSecurity.setSharedObject(JWKSource.class, jwkSource); | |
} | |
} | |
return jwkSource; | |
} | |
private static OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(HttpSecurity httpSecurity) { | |
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class); | |
return getOptionalBean(httpSecurity, type); | |
} | |
private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(HttpSecurity httpSecurity) { | |
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class); | |
return getOptionalBean(httpSecurity, type); | |
} | |
static AuthorizationServerSettings getAuthorizationServerSettings(HttpSecurity httpSecurity) { | |
AuthorizationServerSettings authorizationServerSettings = httpSecurity.getSharedObject(AuthorizationServerSettings.class); | |
if (authorizationServerSettings == null) { | |
authorizationServerSettings = getBean(httpSecurity, AuthorizationServerSettings.class); | |
httpSecurity.setSharedObject(AuthorizationServerSettings.class, authorizationServerSettings); | |
} | |
return authorizationServerSettings; | |
} | |
static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) { | |
return httpSecurity.getSharedObject(ApplicationContext.class).getBean(type); | |
} | |
static <T> T getBean(HttpSecurity httpSecurity, ResolvableType type) { | |
ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class); | |
String[] names = context.getBeanNamesForType(type); | |
if (names.length == 1) { | |
return (T) context.getBean(names[0]); | |
} | |
if (names.length > 1) { | |
throw new NoUniqueBeanDefinitionException(type, names); | |
} | |
throw new NoSuchBeanDefinitionException(type); | |
} | |
static <T> T getOptionalBean(HttpSecurity httpSecurity, Class<T> type) { | |
Map<String, T> beansMap = BeanFactoryUtils.beansOfTypeIncludingAncestors( | |
httpSecurity.getSharedObject(ApplicationContext.class), type); | |
if (beansMap.size() > 1) { | |
throw new NoUniqueBeanDefinitionException(type, beansMap.size(), | |
"Expected single matching bean of type '" + type.getName() + "' but found " + | |
beansMap.size() + ": " + StringUtils.collectionToCommaDelimitedString(beansMap.keySet())); | |
} | |
return (!beansMap.isEmpty() ? beansMap.values().iterator().next() : null); | |
} | |
static <T> T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) { | |
ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class); | |
String[] names = context.getBeanNamesForType(type); | |
if (names.length > 1) { | |
throw new NoUniqueBeanDefinitionException(type, names); | |
} | |
return names.length == 1 ? (T) context.getBean(names[0]) : null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2020-2022 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 com.company.authorizationserver.configuration.grants; | |
import jakarta.servlet.http.HttpServletRequest; | |
import org.springframework.security.oauth2.core.AuthorizationGrantType; | |
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; | |
import org.springframework.security.oauth2.core.OAuth2Error; | |
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; | |
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; | |
import org.springframework.util.LinkedMultiValueMap; | |
import org.springframework.util.MultiValueMap; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.Map; | |
/** | |
* Utility methods for the OAuth 2.0 Protocol Endpoints. This class comes from Spring Security OAuth2 Authorization Server. | |
*/ | |
public final class OAuth2EndpointUtils { | |
public static final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; | |
private OAuth2EndpointUtils() { | |
} | |
public 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; | |
} | |
static Map<String, Object> getParametersIfMatchesAuthorizationCodeGrantRequest(HttpServletRequest request, String... exclusions) { | |
if (!matchesAuthorizationCodeGrantRequest(request)) { | |
return Collections.emptyMap(); | |
} | |
Map<String, Object> parameters = new HashMap<>(getParameters(request).toSingleValueMap()); | |
for (String exclusion : exclusions) { | |
parameters.remove(exclusion); | |
} | |
return parameters; | |
} | |
static boolean matchesAuthorizationCodeGrantRequest(HttpServletRequest request) { | |
return AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals( | |
request.getParameter(OAuth2ParameterNames.GRANT_TYPE)) && | |
request.getParameter(OAuth2ParameterNames.CODE) != null; | |
} | |
static boolean matchesPkceTokenRequest(HttpServletRequest request) { | |
return matchesAuthorizationCodeGrantRequest(request) && | |
request.getParameter(PkceParameterNames.CODE_VERIFIER) != null; | |
} | |
public static void throwError(String errorCode, String parameterName, String errorUri) { | |
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); | |
throw new OAuth2AuthenticationException(error); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.company.authorizationserver.configuration.grants.password; | |
import jakarta.servlet.http.HttpServletRequest; | |
import com.company.authorizationserver.configuration.grants.OAuth2EndpointUtils; | |
import org.apache.commons.lang3.StringUtils; | |
import org.springframework.lang.Nullable; | |
import org.springframework.security.core.Authentication; | |
import org.springframework.security.core.context.SecurityContextHolder; | |
import org.springframework.security.oauth2.core.AuthorizationGrantType; | |
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.MultiValueMap; | |
import java.util.Set; | |
/** | |
* Converter for OAuth2 password grant type. This converter is used to convert a request to an authentication object. | |
* Spring Authorization Server does not provide a converter for this grant type. So we have to implement it on our own. | |
* | |
* @author Attoumane AHAMADI | |
*/ | |
public class OAuth2PasswordGrantAuthenticationConverter implements AuthenticationConverter { | |
public static final AuthorizationGrantType PASSWORD_GRANT_TYPE = new AuthorizationGrantType("password"); | |
@Nullable | |
@Override | |
public Authentication convert(HttpServletRequest request) { | |
// grant_type (REQUIRED) | |
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); | |
if (!PASSWORD_GRANT_TYPE.getValue().equals(grantType)) { | |
return null; | |
} | |
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); | |
if (clientPrincipal == null) { | |
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_CLIENT, OAuth2ParameterNames.CLIENT_ID, | |
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); | |
} | |
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request); | |
//if client id does not match the client id in the request, throw an error | |
if (!StringUtils.equals(clientPrincipal.getName(), parameters.getFirst(OAuth2ParameterNames.CLIENT_ID))) { | |
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_CLIENT, OAuth2ParameterNames.CLIENT_ID, | |
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); | |
} | |
// scope (OPTIONAL) | |
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); | |
if (StringUtils.isNotBlank(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { | |
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, | |
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); | |
} | |
Set<String> scopes = scope != null ? Set.of(scope.split(" ")) : null; | |
return new OAuth2PasswordGrantAuthenticationToken(parameters.getFirst(OAuth2ParameterNames.USERNAME), | |
parameters.getFirst(OAuth2ParameterNames.PASSWORD), clientPrincipal, scopes); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.company.authorizationserver.configuration.grants.password; | |
import lombok.extern.slf4j.Slf4j; | |
import com.company.authorizationserver.configuration.grants.OAuth2AuthenticationProviderUtils; | |
import org.apache.commons.collections4.CollectionUtils; | |
import org.springframework.security.authentication.AuthenticationProvider; | |
import org.springframework.security.core.Authentication; | |
import org.springframework.security.core.AuthenticationException; | |
import org.springframework.security.core.GrantedAuthority; | |
import org.springframework.security.core.userdetails.UserDetails; | |
import org.springframework.security.core.userdetails.UserDetailsService; | |
import org.springframework.security.crypto.password.PasswordEncoder; | |
import org.springframework.security.oauth2.core.*; | |
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 java.util.*; | |
import java.util.stream.Collectors; | |
import static com.company.authorizationserver.configuration.grants.OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI; | |
import static com.company.authorizationserver.configuration.grants.password.OAuth2PasswordGrantAuthenticationConverter.PASSWORD_GRANT_TYPE; | |
/** | |
* {@link AuthenticationProvider} implementation for the OAuth 2.0 Resource Owner Password Credentials Grant. | |
* | |
* @author Attoumane AHAMADI | |
*/ | |
@Slf4j | |
public class OAuth2PasswordGrantAuthenticationProvider implements AuthenticationProvider { | |
private final UserDetailsService userDetailsService; | |
private final PasswordEncoder passwordEncoder; | |
private final OAuth2AuthorizationService authorizationService; | |
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator; | |
public OAuth2PasswordGrantAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder, OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) { | |
this.userDetailsService = userDetailsService; | |
this.passwordEncoder = passwordEncoder; | |
this.authorizationService = authorizationService; | |
this.tokenGenerator = tokenGenerator; | |
} | |
@Override | |
public Authentication authenticate(Authentication authentication) throws AuthenticationException { | |
OAuth2PasswordGrantAuthenticationToken passwordGrantAuthenticationToken = (OAuth2PasswordGrantAuthenticationToken) authentication; | |
// Ensure the client is authenticated | |
OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient((Authentication) passwordGrantAuthenticationToken.getPrincipal()); | |
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); | |
//si le client n'est pas enregistré ou ne supporte pas le grant type password | |
if (registeredClient == null || !registeredClient.getAuthorizationGrantTypes().contains(passwordGrantAuthenticationToken.getGrantType())) { | |
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); | |
} | |
Set<String> authorizedScopes = Collections.emptySet(); | |
if (CollectionUtils.isNotEmpty(passwordGrantAuthenticationToken.getScopes())) { | |
for (String requestedScope : passwordGrantAuthenticationToken.getScopes()) { | |
if (!registeredClient.getScopes().contains(requestedScope)) { | |
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE); | |
} | |
} | |
authorizedScopes = new LinkedHashSet<>(passwordGrantAuthenticationToken.getScopes()); | |
} | |
if (log.isDebugEnabled()) { | |
log.debug("Checking user credentials"); | |
} | |
//verifie si l'utilisateur existe et ses credentials sont valides | |
String providedUsername = passwordGrantAuthenticationToken.getUsername(); | |
String providedPassword = passwordGrantAuthenticationToken.getPassword(); | |
UserDetails userDetails = this.userDetailsService.loadUserByUsername(providedUsername); | |
if (!this.passwordEncoder.matches(providedPassword, userDetails.getPassword())) { | |
throw new OAuth2AuthenticationException("Invalid resource owner credentials"); | |
} | |
if (log.isDebugEnabled()) { | |
log.debug("Generating access token"); | |
} | |
//Generate the access token | |
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() | |
.registeredClient(registeredClient) | |
.principal(clientPrincipal) | |
.authorizationServerContext(AuthorizationServerContextHolder.getContext()) | |
.authorizedScopes(authorizedScopes) | |
.tokenType(OAuth2TokenType.ACCESS_TOKEN) | |
.authorizationGrantType(PASSWORD_GRANT_TYPE) | |
.authorizationGrant(passwordGrantAuthenticationToken) | |
.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.", ACCESS_TOKEN_REQUEST_ERROR_URI); | |
throw new OAuth2AuthenticationException(error); | |
} | |
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, | |
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(), | |
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes()); | |
if (log.isDebugEnabled()) { | |
log.debug("Creating authorization"); | |
} | |
Map<String, Object> tokenMetadata = new HashMap<>(); | |
tokenMetadata.put("username", userDetails.getUsername()); | |
tokenMetadata.put("roles", userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())); | |
if (CollectionUtils.isNotEmpty(authorizedScopes)) { | |
tokenMetadata.put("scopes", authorizedScopes); | |
} | |
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) | |
.principalName(userDetails.getUsername()) | |
.authorizationGrantType(PASSWORD_GRANT_TYPE) | |
.token(accessToken, (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, tokenMetadata)) | |
.build(); | |
if (log.isDebugEnabled()) { | |
log.debug("Saving authorization"); | |
} | |
this.authorizationService.save(authorization); | |
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken); | |
} | |
@Override | |
public boolean supports(Class<?> authentication) { | |
return OAuth2PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.company.authorizationserver.configuration.grants.password; | |
import lombok.Getter; | |
import org.springframework.security.core.Authentication; | |
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; | |
import java.io.Serial; | |
import java.util.Set; | |
import static com.company.authorizationserver.configuration.grants.password.OAuth2PasswordGrantAuthenticationConverter.PASSWORD_GRANT_TYPE; | |
/** | |
* Authentication token for the OAuth 2.0 Resource Owner Password Credentials Grant. | |
* | |
* @author Attoumane AHAMADI | |
*/ | |
@Getter | |
public class OAuth2PasswordGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { | |
@Serial | |
private static final long serialVersionUID = 7840626509676504832L; | |
private final String username; | |
private final String password; | |
private final String clientId; | |
private final Set<String> scopes; | |
public OAuth2PasswordGrantAuthenticationToken(String username, String password, Authentication clientPrincipal, Set<String> scopes) { | |
super(PASSWORD_GRANT_TYPE, clientPrincipal, null); | |
this.password = password; | |
this.username = username; | |
this.clientId = clientPrincipal.getName(); | |
this.scopes = scopes; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment