我尝试设置一个非常小的 Spring 授权服务器示例,作为评估它在我的一个项目中使用的一部分。授权服务器将与 SPA 应用程序一起使用,因此将使用带有 PKCE 的授权代码流。我正在使用 Spring Boot 3.2.0。
示例应用程序由 Spring Initializr 生成,由单个配置文件组成,用于设置授权服务器的最低要求,该服务器很大程度上遵循官方文档中的 “操作方法”
@SpringBootApplication
@EnableWebSecurity
public class DemoApplication
{
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
);
return http.cors(Customizer.withDefaults()).build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception
{
return httpSecurity
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/", "/**").permitAll()
)
.cors(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.build();
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient publicClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("public-client")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("http://127.0.0.1:4200")
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true)
.requireProofKey(true)
.build()
)
.build();
return new InMemoryRegisteredClientRepository(publicClient);
}
@Bean
CorsConfigurationSource corsConfigurationSource()
{
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addAllowedOrigin("http://127.0.0.1:4200");
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return source;
}
}
当向
/authorize
端点发出正确格式的请求时,我将按预期重定向到默认登录页面。
为了完整起见,将我重定向到此登录页面的示例请求看起来像
http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=public-client&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=1234xyz&code_challenge=cFeDr-CjSa6l1C3w388Qoz1GpM3Pa0qzkOF9rq0Wp8A&code_challenge_method=S256
使用由 PKCE Tools 生成的代码质询和验证器
输入有效的用户凭据(如上面配置中的配置)时,我将按预期重定向到同意屏幕。
但是,在通过选择“提交同意”按钮授予同意后,我在重定向网址中收到“access_denied”错误。重定向到的 url 是
http://127.0.0.1:4200/?error=access_denied&error_description=OAuth%202.0%20Parameter%3A%20client_id&error_uri=https%3A%2F%2Fdatatracker.ietf.org%2Fdoc%2Fhtml%2Frfc6749%23section-4.1.2.1&state=1234xyz
这包括错误消息“access_denied”和错误描述“OAuth 2.0 Parameter: client_id”,并指出RFC的一个部分,其中不包含任何进一步有用的信息来解决此特定错误消息。
表面上,错误描述表明Spring认为client_id有问题,但我看不出它可能有什么问题。
我在这里遗漏了哪一块拼图,导致我在已成功通过身份验证并选择同意的有效用户收到此错误?
编辑2024-02-19
我在重新访问时注意到我对授权服务器的请求使用的是
127.0.0.1
,而我的重定向使用的是 localhost
。我已经纠正了这个问题,以便请求和重定向都使用相同的主机,并通过尝试 127.0.0.1
和 localhost
的主机对其进行了测试。但是,以一致的方式使用任一主机时的结果与原始问题中描述的相同,并且我仍然收到“access_denied”错误。
问题已更新以显示
127.0.0.1
的用法。
我刚刚遇到了同样的问题,经过一番调查,发现需要在registeredClientRepository方法中添加额外的作用域(我之前只有作用域(OidcScopes.OPENID)),例如
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient publicClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("public-client")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("http://127.0.0.1:4200")
.scope("your-scope")
.scope(OidcScopes.OPENID)
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true)
.requireProofKey(true)
.build()
)
.build();
希望对你有帮助。