我正在尝试使用 Spring Security 和 OpenID Connect 来实现 OAuth2 授权服务器。 为此,我使用带有刷新令牌和 JWT 的授权代码流程。这是我的配置代码-
@Configuration
public class SecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain asSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.authorizationEndpoint(
a -> a.authenticationProviders(getAuthorizationEndpointProviders()))
.oidc(Customizer.withDefaults());
http.exceptionHandling(
e -> e.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login")));
return http.build();
}
private Consumer<List<AuthenticationProvider>> getAuthorizationEndpointProviders() {
return providers -> {
for (AuthenticationProvider p : providers) {
if (p instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider x) {
x.setAuthenticationValidator(new CustomRedirectUriValidator());
}
}
};
}
@Bean
@Order(2)
public SecurityFilterChain appSecurityFilterChain(HttpSecurity http) throws Exception {
http.formLogin()
.and()
.authorizeHttpRequests().anyRequest().authenticated();
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
var u1 = User.withUsername("user")
.password("password")
.authorities("read")
.build();
return new InMemoryUserDetailsManager(u1);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient r1 = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.redirectUri("https://springone.io/authorized")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.tokenSettings(
TokenSettings.builder()
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
.accessTokenTimeToLive(Duration.ofSeconds(900))
.build())
.build();
return new InMemoryRegisteredClientRepository(r1);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder()
.build();
}
@Bean
public JWKSource<SecurityContext> jwkSource() throws Exception {
KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA");
kg.initialize(2048);
KeyPair kp = kg.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) kp.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) kp.getPrivate();
RSAKey key = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet set = new JWKSet(key);
return new ImmutableJWKSet(set);
}
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> oAuth2TokenCustomizer() {
return context -> {
context.getClaims().claim("test", "test");
};
}
}
CustomRedirectUrlValidator
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import java.util.function.Consumer;
public class CustomRedirectUriValidator implements Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> {
@Override
public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext context) {
OAuth2AuthorizationCodeRequestAuthenticationToken a = context.getAuthentication();
RegisteredClient registeredClient = context.getRegisteredClient();
String uri = a.getRedirectUri();
if (!registeredClient.getRedirectUris().contains(uri)) {
var error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
}
}
}
PKCE 代码验证器和代码质询生成为-
SecureRandom sr = new SecureRandom();
byte[] code = new byte[32];
sr.nextBytes(code);
String codeVerifier = Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(code);
MessageDigest md;
{
try {
md = MessageDigest.getInstance("SHA-256");
byte[] digested = md.digest(codeVerifier.getBytes());
String code_challenge = Base64.getUrlEncoder().withoutPadding().encodeToString(digested);
log.info("Code verifier: {}", codeVerifier);
log.info("Code challenge: {}", code_challenge);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
现在,这是我获取访问令牌所遵循的步骤-
http://localhost:8080/oauth2/authorize?response_type=code&client_id=client&scope=openid&redirect_uri=https://springone.io/authorized&code_challenge=z5f7uuzQ2f0c1CNpuY0UoQE5jSN30YpcxS2s6wmoPq0&code_challenge_method=S256
https://springone.io/authorized?code=TfzC56cc7xwa0wS-O0VvMm1k6kOhYchOcj7sW_pXyeEaRIvw9V6N5YuXoeqwQka1Cvf0ZY9EzGg0dM9zlCXLPYU3q7_T9KVsuc1_sGTV7XBxChPxtq1VRoxuuORf g3Zx现在,如果一切正常,我必须能够在响应正文中获取令牌。但是,我却得到了错误-
{
"error_description": "OAuth 2.0 Parameter: grant_type",
"error": "unsupported_grant_type",
"error_uri": "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"
}
现在,我检查了 Spring Security 授权类型 和 Spring Security 授权服务器 授权类型 authorization_code 是否有效。但是,我不明白,为什么我会收到此错误? 请帮我解决这个问题。
插件{ id 'java' id 'org.springframework.boot' 版本 '3.1.0' id 'io.spring.dependency-management' 版本 '1.1.4' }