Skip to content

Instantly share code, notes, and snippets.

@akuma8
Last active July 15, 2025 11:48
Show Gist options
  • Save akuma8/2eb244b796f3d3506956207997fb290f to your computer and use it in GitHub Desktop.
Save akuma8/2eb244b796f3d3506956207997fb290f to your computer and use it in GitHub Desktop.

Revisions

  1. akuma8 renamed this gist Jun 20, 2023. 1 changed file with 0 additions and 0 deletions.
  2. akuma8 created this gist Jun 20, 2023.
    31 changes: 31 additions & 0 deletions AuthorizationServerConfiguration
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,31 @@
    /**
    * @author Attoumane AHAMADI
    */
    @Configuration(proxyBeanMethods = false)
    public class AuthorizationServerConfiguration {

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
    UserDetailsService userDetailsService,
    PasswordEncoder passwordEncoder,
    OAuth2AuthorizationService authorizationService) throws Exception {

    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.exceptionHandling(exceptions ->
    exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")))
    .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken); // I use opaque toekn in my app but

    OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);

    //Password Grant Authentication Provider registration
    OAuth2TokenGenerator<?> tokenGenerator = OAuth2ConfigurerUtils.getTokenGenerator(http);
    authorizationServerConfigurer.tokenEndpoint(tokenEndpoint ->
    tokenEndpoint.accessTokenRequestConverter(new OAuth2PasswordGrantAuthenticationConverter())
    .authenticationProvider(new OAuth2PasswordGrantAuthenticationProvider(userDetailsService, passwordEncoder, authorizationService, tokenGenerator))
    );

    return http.build();
    }

    }
    75 changes: 75 additions & 0 deletions OAuth2AuthenticationProviderUtils
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,75 @@
    /*
    * 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();
    }
    }
    206 changes: 206 additions & 0 deletions OAuth2ConfigurerUtils
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,206 @@
    /*
    * 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;
    }

    }
    80 changes: 80 additions & 0 deletions OAuth2EndpointUtils
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,80 @@
    /*
    * 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);
    }

    }
    142 changes: 142 additions & 0 deletions OAuth2PasswordGrantAuthenticationProvider
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,142 @@
    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);
    }
    }
    34 changes: 34 additions & 0 deletions OAuth2PasswordGrantAuthenticationToken
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,34 @@
    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;
    }
    }
    62 changes: 62 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    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);
    }
    }