此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Authorization Server 1.4.2spring-doc.cadn.net.cn

作方法:通过 PKCE 使用单页应用程序进行身份验证

本指南介绍如何配置 Spring Authorization Server 以支持具有代码交换证明密钥 (PKCE) 的单页应用程序 (SPA)。 本指南的目的是演示如何支持公共客户端并要求 PKCE 进行客户端身份验证。spring-doc.cadn.net.cn

Spring Authorization Server 不会为公共客户端颁发刷新令牌。我们建议使用前端后端 (BFF) 模式作为公开公共客户端的替代方法。有关更多信息,请参阅 gh-297

启用 CORS

SPA 由静态资源组成,这些资源可以通过多种方式进行部署。 它可以与后端分开部署,例如使用 CDN 或单独的 Web 服务器,也可以使用 Spring Boot 与后端一起部署。spring-doc.cadn.net.cn

当 SPA 托管在不同的域下时,可以使用跨域资源共享 (CORS) 来允许应用程序与后端通信。spring-doc.cadn.net.cn

例如,如果你有一个 Angular 开发服务器在端口4200中,您可以定义CorsConfigurationSource @Bean并将 Spring Security 配置为允许使用cors()DSL 的调用,如以下示例所示:spring-doc.cadn.net.cn

启用 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;
	}

}
Click on the "Expand folded text" icon in the code sample above to display the full example.

Configure a Public Client

A SPA cannot securely store credentials and therefore must be treated as a public client. Public clients should be required to use Proof Key for Code Exchange (PKCE).spring-doc.cadn.net.cn

Continuing the earlier example, you can configure Spring Authorization Server to support a public client using the Client Authentication Method none and require PKCE as in the following example:spring-doc.cadn.net.cn

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);
}
The requireProofKey setting is important to prevent the PKCE Downgrade Attack.

Authenticate with the Client

Once the server is configured to support a public client, a common question is: How do I authenticate the client and get an access token? The short answer is: The same way you would with any other client.spring-doc.cadn.net.cn

A SPA is a browser-based application and therefore uses the same redirection-based flow as any other client. This question is usually related to an expectation that authentication can be performed via a REST API, which is not the case with OAuth2.

A more detailed answer requires an understanding of the flow(s) involved in OAuth2 and OpenID Connect, in this case the Authorization Code flow. The steps of the Authorization Code flow are as follows:spring-doc.cadn.net.cn

  1. The client initiates an OAuth2 request via a redirect to the Authorization Endpoint. For a public client, this step includes generating the code_verifier and calculating the code_challenge, which is then sent as a query parameter.spring-doc.cadn.net.cn

  2. If the user is not authenticated, the authorization server will redirect to the login page. After authentication, the user is redirected back to the Authorization Endpoint again.spring-doc.cadn.net.cn

  3. If the user has not consented to the requested scope(s) and consent is required, the consent page is displayed.spring-doc.cadn.net.cn

  4. Once the user has consented, the authorization server generates an authorization_code and redirects back to the client via the redirect_uri.spring-doc.cadn.net.cn

  5. The client obtains the authorization_code via a query parameter and performs a request to the Token Endpoint. For a public client, this step includes sending the code_verifier parameter instead of credentials for authentication.spring-doc.cadn.net.cn

As you can see, the flow is fairly involved and this overview only scratches the surface.spring-doc.cadn.net.cn

It is recommended that you use a robust client-side library supported by your single-page app framework to handle the Authorization Code flow.