Spring Authorization Server中Client Credentials模式无法获取AccessToken
环境信息
- java: 17
- spring boot: 3.3.4
- spring security: 6.3.3
- spring authorization server: 1.3.2
问题描述
使用新版spring-authorization-server库配置授权服务器,通过Postman发送携带正确client id和client secret的请求,但无法获取accessToken。相关参考资料较少,难以定位问题原因。请求时Spring Security返回302重定向,最终跳转到默认登录页并返回200状态码,始终无法获取目标accessToken。
配置代码
package authorization.demo.config; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 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.core.annotation.Order; import org.springframework.http.MediaType; 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.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.jwt.JwtDecoder; 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.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.settings.TokenSettings; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.session.DisableEncodeUrlFilter; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.UUID; import static org.springframework.security.config.Customizer.withDefaults; @Configuration @EnableWebSecurity(debug = true) public class SecurityConfig { /** * Protocol endpoints 를 위한 설정 * Authorization Endpoint /oauth2/authorize * Token Endpoint /oauth2/token * Token Revocation /oauth2/revoke * Token Introspection /oauth2/introspect * JWK Set Endpoint /oauth2/jwks * Authorization Server Metadata /.well-known/oauth-authorization-server * OIDC Provider Configuration /.well-known/openid-configuration */ @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); //OAuth2AuthorizationServer를 구동한다. http.getConfigurer(OAuth2AuthorizationServerConfigurer.class); http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor( // 인가 실패에 대한 처리를 정의 new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) )); http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults())); // '토큰 검증'에 대한 설정 http.formLogin().disable(); return http.build(); } /** * 인증(Authentication)을 위한 설정 */ @Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { // Optional: Add specific matcher for the token endpoint to ensure no redirects http.authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() ); http.formLogin(withDefaults()); return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails userDetails = User.withUsername("user") .username("user") .password("{noop}1234") .roles("USER") .build(); return new InMemoryUserDetailsManager(userDetails); } /** * 클라이언트의 정보를 등록하고 관리하는 역할을 한다. */ @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString()) .clientName("Your client name") .clientId("client-id") .clientSecret("{noop}secret") // 실제 운영환경에서는 임의의 문자열을 사용하고, 코드에 올리면 안됨 .clientAuthenticationMethods(methods -> { methods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); }) .authorizationGrantTypes(types -> { types.add(AuthorizationGrantType.CLIENT_CREDENTIALS); // Client Credentials 방식 사용 types.add(AuthorizationGrantType.JWT_BEARER); // JWT token 사용 types.add(AuthorizationGrantType.REFRESH_TOKEN); // refresh Token 사용 }) // .scopes(scope -> { scope.add("message.read"); }) .tokenSettings(TokenSettings.builder().build()) .build(); return new InMemoryRegisteredClientRepository(client); } /** * jwt 생성에 필요한 RSA키 generate, 실제 운영에 사용하려면 KeyStore에 저장해야한다. */ @Bean public JWKSource<SecurityContext> jwkSource() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build(); JWKSet jwkSet = new JWKSet(rsaKey); return new ImmutableJWKSet<>(jwkSet); } private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception e) { throw new IllegalStateException(e); } return keyPair; } /** * 토큰 검증을 위한 디코더 */ @Bean public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } /** * Authorization server를 구성하기 위한 여러 EndPoint를 설정하는 객체 */ @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() .tokenEndpoint("oauth2/token") .build(); } @Bean public OAuth2AuthorizationService oAuth2AuthorizationService() { return new InMemoryOAuth2AuthorizationService(); } }
Postman请求方式
- 请求方法:POST
- 请求端点:
/oauth2/token - 认证方式:Basic Auth,使用client-id(client-id)和client-secret(secret)
- 请求参数:
grant_type=client_credentials、scope=message.read
内容的提问来源于stack exchange,提问作者codingcodingman
相关产品推荐
相关产品推荐

