此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Authorization Server 1.3.1! |
此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Authorization Server 1.3.1! |
本指南介绍如何配置Spring Authorization Server以支持具有代码交换证明密钥(PKCE)的单页应用程序(SPA)。 本指南的目的是演示如何支持公共客户端,并要求 PKCE 进行客户端身份验证。
Spring Authorization Server 不会为公共客户端颁发刷新令牌。建议使用前端后端 (BFF) 模式作为公开公共客户端的替代方法。有关详细信息,请参见 gh-297。 |
Spring Authorization Server 不会为公共客户端颁发刷新令牌。建议使用前端后端 (BFF) 模式作为公开公共客户端的替代方法。有关详细信息,请参见 gh-297。 |
启用 CORS
SPA 由静态资源组成,可以通过多种方式进行部署。 它可以与后端分开部署,例如使用 CDN 或单独的 Web 服务器,也可以使用 Spring Boot 与后端一起部署。
当 SPA 托管在其他域下时,可以使用跨域资源共享 (CORS) 来允许应用程序与后端通信。
例如,如果您有一个 Angular 开发服务器在本地运行在端口上,您可以定义一个并配置 Spring Security 以允许使用 DSL 的预检请求,如以下示例所示:4200
CorsConfigurationSource
@Bean
cors()
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.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
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)
)
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
return http.cors(Customizer.withDefaults()).build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());
return http.cors(Customizer.withDefaults()).build();
}
@Bean
public 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;
}
}
单击上面代码示例中的“展开折叠文本”图标以显示完整示例。 |
单击上面代码示例中的“展开折叠文本”图标以显示完整示例。 |
配置公共客户端
继续前面的示例,您可以将 Spring Authorization Server 配置为使用客户端身份验证方法支持公共客户端,并要求 PKCE,如以下示例所示:none
-
Yaml
-
Java
spring:
security:
oauth2:
authorizationserver:
client:
public-client:
registration:
client-id: "public-client"
client-authentication-methods:
- "none"
authorization-grant-types:
- "authorization_code"
redirect-uris:
- "http://127.0.0.1:4200"
scopes:
- "openid"
- "profile"
require-authorization-consent: true
require-proof-key: true
@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(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true)
.requireProofKey(true)
.build()
)
.build();
return new InMemoryRegisteredClientRepository(publicClient);
}
在忘记包含 和 查询参数的情况下,此设置非常有用,因为您将在授权请求期间收到指示需要 PKCE 的错误,而不是在令牌请求期间收到常规客户端身份验证错误。requireProofKey code_challenge code_challenge_method |
向客户端进行身份验证
将服务器配置为支持公共客户端后,一个常见的问题是:如何对客户端进行身份验证并获取访问令牌?简短的回答是:与任何其他客户一样。
SPA 是基于浏览器的应用程序,因此使用与任何其他客户端相同的基于重定向的流。此问题通常与可以通过 REST API 执行身份验证的期望有关,而 OAuth2 则不然。 |
更详细的答案需要了解 OAuth2 和 OpenID Connect 中涉及的流程,在本例中为授权代码流程。 授权代码流程的步骤如下:
-
客户端通过重定向到授权终结点来启动 OAuth2 请求。对于公共客户端,此步骤包括生成和计算 ,然后将其作为查询参数发送。
code_verifier
code_challenge
-
如果用户未通过身份验证,授权服务器将重定向到登录页面。身份验证后,用户将再次重定向回授权终结点。
-
如果用户未同意请求的范围,并且需要同意,则会显示同意页面。
-
用户同意后,授权服务器将生成一个 并通过 重定向回客户端。
authorization_code
redirect_uri
-
客户端获取 via 查询参数,并执行对令牌终结点的请求。对于公共客户端,此步骤包括发送参数而不是用于身份验证的凭据。
authorization_code
code_verifier
正如你所看到的,流程相当复杂,这个概述只是触及了表面。
建议使用单页应用框架支持的可靠客户端库来处理授权代码流。 |
SPA 是基于浏览器的应用程序,因此使用与任何其他客户端相同的基于重定向的流。此问题通常与可以通过 REST API 执行身份验证的期望有关,而 OAuth2 则不然。 |
建议使用单页应用框架支持的可靠客户端库来处理授权代码流。 |