使用Spring OAuth2 Authorization Server 1.1.0(Sping Boot 3.1.0)自定义OAuth2令牌

1l5u6lss  于 2023-06-05  发布在  Spring
关注(0)|答案(1)|浏览(480)

我已经使用Sping Boot 3.1.0和Spring OAuth2 Authorization Server 1.1.0创建了一个Spring OAuth2 Authorization Server。这工作正常,我收到一个访问令牌使用授权授予类型,看起来像下面这样:

{
"access_token": "eyJraWQiOiJiZGM5MTVlMy01ZjliLTQzYWItOTU5Yi0zYzJmNTE3YTI0NDgiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6ImNsaWVudDEiLCJuYmYiOjE2ODU1NDEwOTYsInNjb3BlIjpbInJlYWQiLCJvcGVuaWQiXSwiaXNzIjoiaHR0cDovL2F1dGgtc2VydmVyOjgwMDAiLCJleHAiOjE2ODU1NDEzOTYsImlhdCI6MTY4NTU0MTA5Nn0.JtQSGR4LlYIZU0NV16ht4-LU0fRDUs9yD33NHFY1nItEc3NUs6vbV8SeSPGbmdMpxUMcr1_Xd1FpSkKrWbPPBZC10hortVrA1k550wGLVrZcknsc7sW10G718dLlJvL7qJGj4sqrqLIP1vVR8Ft3M7CdoT34Z7z6-JcHKRgmnXOP-tyvdWhRtn_OVb1o_29pTumJQ9GPSHU_Z6miOrDvOgUllWUwypw9Cg6aJJyl403P0Cl2wYye4HvP0gfosq6qbNy5OTZ4yiG0HrxrsYvNux9JIvYGbxMUhp9pNF84d3NOzvc24aDxD_VkerBV3zlfrOgLOtSstRdwLxaJ7dc-4Q",
"refresh_token": "VIAvsVUef8ljKBBPvv9gi1-DU48U77h8lZ0OBh0HO57fyGxJTppazUMOlAfnAsCrvMGc5XFVlX1Lii04YltVjF4dk-vrJWHEplhKtIehxxZXEX3HmTqaSL63pYQq9cGr",
"scope": "read openid",
"id_token": "eyJraWQiOiJiZGM5MTVlMy01ZjliLTQzYWItOTU5Yi0zYzJmNTE3YTI0NDgiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6ImNsaWVudDEiLCJhenAiOiJjbGllbnQxIiwiYXV0aF90aW1lIjoxNjg1NTQxMDY3LCJpc3MiOiJodHRwOi8vYXV0aC1zZXJ2ZXI6ODAwMCIsImV4cCI6MTY4NTU0Mjg5NiwiaWF0IjoxNjg1NTQxMDk2LCJzaWQiOiJrUllCUjFBNWJPbDZJR0FGYnZTNHJZSDc0dG5ncHhCemh2eWNCV0ZLR1dnIn0.LIjMH6ONDGSBE2pO3sDUsPmDsstJhvQb6NPRrDZO8TAClyNpwMMRkCmPociU2Jv_rjQq8Y-zXrj016WchkGgCeakCyItzCvpTmqUDjM9tHwpG7FWuDC_GBsFstLwHqussVOG23vvy2KyNi6h8EMtbIR_aqFbDfzvknXQkAK-8Hl2ICqPfbzDkcZeomvV9J07ScqCL6iMkWw3g8ISJfvmWtiymuQ3tGa_9qJXA-JcgcZJhYGpSCbd052AxerZTMpJC4tN1afJDCfJy0HrgnChdX1wp_r9QXLKbNb1SEGRd8IUWzOLRHkOiJKqlgFx-AzuQ7sVINYjHHE1A8yHSGqGSQ",
"token_type": "Bearer",
"expires_in": 299

}
现在,正如你看到的,这是一个令牌与非常小的信息.例如,用户角色、用户电子邮件等等。为此,我想定制令牌的创建。为了实现这一点,我尝试使用OAuth2TokenCustomizer类。但由于某种原因,它不起作用。
我创建了以下Bean

@Bean
   public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer() {
      return context -> {
         var userName = context.getPrincipal().getName();
         StringBuilder roles = new StringBuilder();
         var authorities =  context.getPrincipal().getAuthorities();
         if(OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
            context.getClaims().claims(claims -> {
               claims.put("userName", userName);
               claims.put("testVal", "This is a test string");
               for (var auth:authorities) {
                  if(roles.isEmpty()){
                     roles.append(auth.getAuthority());
                  } else {
                     roles.append(" ").append(auth.getAuthority());
                  }
               }
               claims.put("roles", roles);
            });
         } };
   }

此Bean在OAuth2TokenGenerator Bean中使用,如下所示

@Bean
   public OAuth2TokenGenerator<OAuth2Token> tokenGenerator() {
      JwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource());
      JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
      OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
      accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer());
      OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
      return new DelegatingOAuth2TokenGenerator(
         jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
   }

这样不行我尝试了一些其他选项,比如这里给出的https://github.com/spring-projects/spring-authorization-server/issues/925(jgrandja在2022年10月25日给出的答案),但当我这样做时,我的应用无法自动连接OAuth2AuthorizationService Bean
我不知道我在这里错过了什么。如果有人能帮上忙,我会非常感激。我的两个配置类文件如下:

AuthorizationServerConfig.java

package com.auth.config;

import java.util.UUID;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.smarc.auth.config.keys.KeyManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
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.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
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.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class AuthorizationServerConfig
{
   private final KeyManager keyManager;

   public AuthorizationServerConfig(KeyManager keyManager)
   {
      this.keyManager = keyManager;
   }

   @Bean
   @Order(Ordered.HIGHEST_PRECEDENCE)
   public SecurityFilterChain customSecurityFilterChainOAuth(HttpSecurity http) throws Exception {
      OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

      http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());
      http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).tokenGenerator(tokenGenerator());
      return http.formLogin(Customizer.withDefaults()).build();
   }

   @Bean
   public RegisteredClientRepository registeredClientRepository() {
      RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
         .clientId("client1")
         .clientSecret("{noop}myClientSecretValue")
         .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
         .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
         .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
         .redirectUri("http://127.0.0.1:8080/login/oauth2/code/users-client-oidc")
         .redirectUri("http://127.0.0.1:8080/authorized")
         .scope(OidcScopes.OPENID)
         .scope("read")
         .build();

      return new InMemoryRegisteredClientRepository(registeredClient);
   }

   @Bean
   public ClientSettings clientSettings() {
      return ClientSettings.builder()
         .requireAuthorizationConsent(false)
         .requireProofKey(false)
         .build();
   }

   @Bean
   public AuthorizationServerSettings authorizationServerSettings() {
      return AuthorizationServerSettings.builder()
         .issuer("http://localhost:8080").build();
   }

   @Bean
   public JWKSource<SecurityContext> jwkSource() {
      JWKSet set = new JWKSet(keyManager.rsaKey());
      return (j, jc) -> j.select(set);
   }

   @Bean
   public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
      return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
   }

   @Bean
   public OAuth2TokenGenerator<OAuth2Token> tokenGenerator() {
      JwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource());
      JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
      OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
      accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer());
      OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
      return new DelegatingOAuth2TokenGenerator(
         jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
   }

   @Bean
   public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer() {
      return context -> {
         var userName = context.getPrincipal().getName();
         StringBuilder roles = new StringBuilder();
         var authorities =  context.getPrincipal().getAuthorities();
         if(OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
            context.getClaims().claims(claims -> {
               claims.put("userName", userName);
               claims.put("testVal", "This is a test string");
               for (var auth:authorities) {
                  if(roles.isEmpty()){
                     roles.append(auth.getAuthority());
                  } else {
                     roles.append(" ").append(auth.getAuthority());
                  }
               }
               claims.put("roles", roles);
            });
         } };
   }

}

WebSecurityConfig.java

package com.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig
{
   @Bean
   public SecurityFilterChain customSecurityFilterChain(HttpSecurity http) throws Exception{
      return http
      .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
      .formLogin(Customizer.withDefaults()).build();
   }

   @Bean
   public UserDetailsService userDetailsService() {
      var externalUser = User.withUsername("external").password(passwordEncoder().encode("12345"))
         .roles("read").build();
      var internalUser = User.withUsername("internal").password(passwordEncoder().encode("12345"))
         .roles("read", "write").build();
      var admin = User.withUsername("admin").password(passwordEncoder().encode("12345"))
         .roles("read").build();
      var userDetailsService = new InMemoryUserDetailsManager();
      userDetailsService.createUser(externalUser);
      userDetailsService.createUser(internalUser);
      userDetailsService.createUser(admin);
      return userDetailsService;
   }

   @Bean
   public PasswordEncoder passwordEncoder() {
      return PasswordEncoderFactories.createDelegatingPasswordEncoder();
   }

}

请让我知道如果需要更多的信息。先谢谢你了。

6mzjoqzu

6mzjoqzu1#

您注册的bean(OAuth2TokenCustomizer<OAuth2TokenClaimsContext>)用于自定义不透明令牌(token format = reference),但看起来您使用的是默认令牌格式jwt(token format = self contained)。如果您想使用不透明令牌(我假设您不想),请查看文档了解如何为每个客户端定制令牌格式。
如参考中所述,对于您的情况,您可以简单地注册一个OAuth2TokenCustomizer<JwtEncodingContext>类型的bean来定制JWT。
此外,文档还指出:
如果OAuth2TokenGenerator未作为@Bean提供或未通过OAuth2AuthorizationServerConfigurer进行配置,则OAuth2TokenCustomizer<JwtEncodingContext>@Bean将自动配置为JwtGenerator
这意味着,如果您只想自定义令牌声明,而不是生成令牌的整个过程,您可以简单地发布OAuth2TokenCustomizer@Bean,并省略OAuth2TokenGenerator@Bean
因此,您的配置应该简单地具有:

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> accessTokenCustomizer() {
    return (context) -> {
        ...
    };
}

我认为这一点可以在文档中更清楚一点,因为示例演示了同时定制两者,但注解指定了两者都不是必需的。

相关问题