SpringSecurity中文文档(Servlet OAuth 2.0 Login)

07-10 1772阅读

OAuth 2.0 Login

OAuth 2.0 Login 功能允许应用程序让用户通过使用 OAuth 2.0 Provider (如 GitHub)或 OpenID Connect 1.0 Provider (如 Google)的现有帐户登录到应用程序。OAuth 2.0 Login 实现了两个用例: “ Login with Google”或“ Login with GitHub”。

SpringSecurity中文文档(Servlet OAuth 2.0 Login)
(图片来源网络,侵删)

OAuth 2.0 Login 是通过使用授权代码授权(Authorization Code Grant)实现的,正如 OAuth 2.0 Authorization Framework 和 OpenID Connect Core 1.0中指定的那样。

本节摘要

  • Core Configuration
  • Advanced Configuration
  • OIDC Logout

    Core Configuration

    Spring Boot Sample

    Spring Boot 为 OAuth 2.0 Login 带来了完整的自动配置功能。

    本节展示如何使用 Google 作为身份验证提供者来配置 OAuth 2.0 Login 示例,并涵盖以下主题:

    • Initial Setup
    • Setting the Redirect URI
    • Configure application.yml
    • Boot up the Application

      Initial Setup

      要使用 Google 的 OAuth 2.0身份验证系统进行登录,必须在 Google API Console 中设置一个项目,以获得 OAuth 2.0凭据。

      Google 用于身份验证的 OAuth 2.0实现符合 OpenID Connect 1.0规范,并通过了 OpenID 认证。

      按照 OpenID Connect 页面上的说明进行操作,从“设置 OAuth 2.0”部分开始。

      在完成“获取 OAuth 2.0凭据”说明之后,您应该拥有新的 OAuth Client,其凭据包括客户端 ID 和客户端秘钥。

      Setting the Redirect URI

      重定向 URI 是应用程序中的路径,最终用户的用户代理在通过 Google 身份验证并授予对 Consent 页面上的 OAuth Client (在前一步中创建)的访问权限之后,将重定向回该路径。

      在“ Set a redirect URI”小节中,确保 Authorized redirect URI 字段设置为 localhost: 8080/login/oauth2/code/google。

      默认的重定向 URI 模板是{ baseUrl }/login/oauth2/code/{ registrationId }。 registrationId 是 ClientRegistry 的唯一标识符。

      如果 OAuth 客户端运行在代理服务器之后,您应该检查代理服务器配置以确保应用程序配置正确。另外,请参阅 redirect-URI 支持的 URI 模板变量。

      Configure application.yml

      现在您已经有了一个新的 OAuth Client 和 Google,您需要将应用程序配置为使用 OAuth Client 进行身份验证流。这样做:

      1. 转到 application.yml 并设置以下配置:

        spring:
          security:
            oauth2:
              client:
                registration:	
                  google:	
                    client-id: google-client-id
                    client-secret: google-client-secret
        

        OAuth Client properties

        1. spring.security.oauth2.client.registration 是 OAuth Client 属性的基本属性前缀。
        2. 在基本属性前缀之后是 ClientRegistration的 ID,例如 Google。
      2. 用前面创建的 OAuth 2.0凭据替换 client-id 和 client-secret 属性中的值。

      Boot up the Application

      启动 Spring Boot 示例并转到 localhost: 8080。然后重定向到默认的自动生成登录页面,该页面显示 Google 的链接。

      点击 Google 链接,然后你会被重定向到 Google 进行身份验证。

      在使用您的 Google 帐户凭据进行身份验证之后,您将看到“同意”屏幕。Consent 屏幕要求您允许或拒绝访问先前创建的 OAuth Client。单击“允许”授权 OAuth 客户端访问您的电子邮件地址和基本配置文件信息。

      此时,OAuth 客户机将从 UserInfo 端点检索您的电子邮件地址和基本配置文件信息,并建立一个经过身份验证的会话。

      Spring Boot Property Mappings

      下表概述了 SpringBootOAuthClient 属性到 ClientRegistry 属性的映射。

      Spring BootClientRegistration
      spring.security.oauth2.client.registration.*[registrationId]*registrationId
      spring.security.oauth2.client.registration.*[registrationId]*.client-idclientId
      spring.security.oauth2.client.registration.*[registrationId]*.client-secretclientSecret
      spring.security.oauth2.client.registration.*[registrationId]*.client-authentication-methodclientAuthenticationMethod
      spring.security.oauth2.client.registration.*[registrationId]*.authorization-grant-typeauthorizationGrantType
      spring.security.oauth2.client.registration.*[registrationId]*.redirect-uriredirectUri
      spring.security.oauth2.client.registration.*[registrationId]*.scopescopes
      spring.security.oauth2.client.registration.*[registrationId]*.client-nameclientName
      spring.security.oauth2.client.provider.*[providerId]*.authorization-uriproviderDetails.authorizationUri
      spring.security.oauth2.client.provider.*[providerId]*.token-uriproviderDetails.tokenUri
      spring.security.oauth2.client.provider.*[providerId]*.jwk-set-uriproviderDetails.jwkSetUri
      spring.security.oauth2.client.provider.*[providerId]*.issuer-uriproviderDetails.issuerUri
      spring.security.oauth2.client.provider.*[providerId]*.user-info-uriproviderDetails.userInfoEndpoint.uri
      spring.security.oauth2.client.provider.*[providerId]*.user-info-authentication-methodproviderDetails.userInfoEndpoint.authenticationMethod
      spring.security.oauth2.client.provider.*[providerId]*.user-name-attributeproviderDetails.userInfoEndpoint.userNameAttributeName

      通过指定 spring.security.oauth2.client.Provider,可以通过发现 OpenID Connect Provider 的 Configuration 端点或 Authorization Server 的 Metadata 端点来最初配置 spring.security.oauth2.client.provider.[providerId].issuer-uri属性。

      CommonOAuth2Provider

      CommonOAuth2Provider 为许多知名的提供商(Google、 gitHub、 Facebook 和 Okta)预先定义了一组默认客户端属性。

      例如,对于提供程序,authority-uri、 token-uri 和 user-info-uri 不会经常更改。因此,提供默认值以减少所需的配置是有意义的。

      如前所述,当我们配置 Google 客户机时,只需要client-id 和client-secret属性。

      下面的清单显示了一个示例:

      spring:
        security:
          oauth2:
            client:
              registration:
                google:
                  client-id: google-client-id
                  client-secret: google-client-secret
      

      在这里,客户端属性的自动默认无缝工作,因为 registrationId (GOOGLE)匹配 CommonOAuth2Provider 中的 GOOGLE 枚举(不区分大小写)。

      对于希望指定不同的 registrationId (如 google-login)的情况,您仍然可以通过配置 Provider 属性来利用客户端属性的自动默认。

      spring:
        security:
          oauth2:
            client:
              registration:
                google-login:	
                  provider: google	
                  client-id: google-client-id
                  client-secret: google-client-secret
      
      • RegistrationId 被设置为 google-login。
      • Provider 属性设置为 google,它将利用 CommonOAuth2Provider.GOOGLE.getBuilder ()中设置的客户端属性的自动默认。

        Configuring Custom Provider Properties

        有一些 OAuth 2.0提供程序支持多租户,这导致每个租户(或子域)有不同的协议端点。

        例如,一个在 Okta 注册的 OAuth 客户端被分配到一个特定的子域,并且拥有自己的协议端点。

        对于这些情况,Spring Boot 为配置自定义提供程序属性提供了以下基本属性: spring.security.oauth2.client.provider.*[providerId]*.

        下面的清单显示了一个示例:

        spring:
          security:
            oauth2:
              client:
                registration:
                  okta:
                    client-id: okta-client-id
                    client-secret: okta-client-secret
                provider:
                  okta:	
                    authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
                    token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
                    user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
                    user-name-attribute: sub
                    jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
        

        基本属性(spring.security.oauth2.client.provision. okta)允许对协议端点位置进行自定义配置。

        Overriding Spring Boot Auto-configuration

        OAuth 客户端支持的 Spring 引导自动配置类是 OAuth2ClientAutoConfiguration。

        它执行下列任务:

        • 从配置的 OAuth Client 属性注册一个由 ClientRegistration组成的 ClientRegistrationRepository@Bean。
        • 注册一个 SecurityFilterChain@Bean 并通过 httpSecurity.oauth2Login()启用 OAuth 2.0登录。

          如果您需要根据您的具体要求覆盖自动配置,您可以通过以下方式进行:

          • Register a ClientRegistrationRepository @Bean
          • Register a SecurityFilterChain @Bean
          • Completely Override the Auto-configuration

            Register a ClientRegistrationRepository @Bean

            下面的示例演示如何注册 ClientRegistrationRepository@Bean:

            @Configuration
            public class OAuth2LoginConfig {
            	@Bean
            	public ClientRegistrationRepository clientRegistrationRepository() {
            		return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
            	}
            	private ClientRegistration googleClientRegistration() {
            		return ClientRegistration.withRegistrationId("google")
            			.clientId("google-client-id")
            			.clientSecret("google-client-secret")
            			.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            			.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            			.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
            			.scope("openid", "profile", "email", "address", "phone")
            			.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
            			.tokenUri("https://www.googleapis.com/oauth2/v4/token")
            			.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
            			.userNameAttributeName(IdTokenClaimNames.SUB)
            			.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
            			.clientName("Google")
            			.build();
            	}
            }
            

            Register a SecurityFilterChain @Bean

            下面的示例演示如何用@EnableWebSecurity 注册 SecurityFilterChain@Bean 并通过 httpSecurity.oauth2Login()启用 OAuth2.0登录:

            OAuth2 Login Configuration

            @Configuration
            @EnableWebSecurity
            public class OAuth2LoginSecurityConfig {
            	@Bean
            	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            		http
            			.authorizeHttpRequests(authorize -> authorize
            				.anyRequest().authenticated()
            			)
            			.oauth2Login(withDefaults());
            		return http.build();
            	}
            }
            

            Completely Override the Auto-configuration

            下面的示例显示如何通过注册 ClientRegistrationRepository@Bean 和 SecurityFilterChain@Bean 来完全覆盖自动配置。

            Overriding the auto-configuration

            @Configuration
            public class OAuth2LoginConfig {
            	@Bean
            	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            		http
            			.authorizeHttpRequests(authorize -> authorize
            				.anyRequest().authenticated()
            			)
            			.oauth2Login(withDefaults());
            		return http.build();
            	}
            	@Bean
            	public ClientRegistrationRepository clientRegistrationRepository() {
            		return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
            	}
            	private ClientRegistration googleClientRegistration() {
            		return ClientRegistration.withRegistrationId("google")
            			.clientId("google-client-id")
            			.clientSecret("google-client-secret")
            			.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            			.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            			.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
            			.scope("openid", "profile", "email", "address", "phone")
            			.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
            			.tokenUri("https://www.googleapis.com/oauth2/v4/token")
            			.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
            			.userNameAttributeName(IdTokenClaimNames.SUB)
            			.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
            			.clientName("Google")
            			.build();
            	}
            }
            

            Java Configuration without Spring Boot

            如果您不能使用 Spring Boot,并且希望在 CommonOAuth2Provider (例如,Google)中配置一个预定义的提供程序,请应用以下配置:

            @Configuration
            @EnableWebSecurity
            public class OAuth2LoginConfig {
            	@Bean
            	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            		http
            			.authorizeHttpRequests(authorize -> authorize
            				.anyRequest().authenticated()
            			)
            			.oauth2Login(withDefaults());
            		return http.build();
            	}
            	@Bean
            	public ClientRegistrationRepository clientRegistrationRepository() {
            		return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
            	}
            	@Bean
            	public OAuth2AuthorizedClientService authorizedClientService(
            			ClientRegistrationRepository clientRegistrationRepository) {
            		return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
            	}
            	@Bean
            	public OAuth2AuthorizedClientRepository authorizedClientRepository(
            			OAuth2AuthorizedClientService authorizedClientService) {
            		return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
            	}
            	private ClientRegistration googleClientRegistration() {
            		return CommonOAuth2Provider.GOOGLE.getBuilder("google")
            			.clientId("google-client-id")
            			.clientSecret("google-client-secret")
            			.build();
            	}
            }
            

            Advanced Configuration

            Oauth2Login ()提供了许多用于定制 OAuth 2.0 Login 的配置选项。主要的配置选项被分组到它们的协议端点对应项中。

            例如,oauth2Login().authorizationEndpoint()允许配置 AuthorizationEndpoint,而 oauth2Login().tokenEndpoint()允许配置令牌端点。

            下面的代码显示了一个示例:

            Advanced OAuth2 Login Configuration

            @Configuration
            @EnableWebSecurity
            public class OAuth2LoginSecurityConfig {
            	@Bean
            	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            		http
            			.oauth2Login(oauth2 -> oauth2
            			    .authorizationEndpoint(authorization -> authorization
            			            ...
            			    )
            			    .redirectionEndpoint(redirection -> redirection
            			            ...
            			    )
            			    .tokenEndpoint(token -> token
            			            ...
            			    )
            			    .userInfoEndpoint(userInfo -> userInfo
            			            ...
            			    )
            			);
            		return http.build();
            	}
            }
            

            Oauth2Login() DSL 的主要目标是紧密地与规范中定义的命名保持一致。

            OAuth 2.0授权框架将协议端点定义如下:

            授权过程使用两个授权服务器端点(HTTP 资源) :

            • 授权端点: 客户端通过用户-代理重定向从资源所有者获得授权。
            • 令牌端点(Token Endpoint) : 客户端用来交换访问令牌的授权许可,通常使用客户端身份验证。

              授权过程还使用一个客户端端点:

              • 重定向端点: 授权服务器通过资源所有者用户代理向客户端返回包含授权凭据的响应。

                OpenID Connect Core 1.0规范将 UserInfo 端点定义如下:

                UserInfo 端点是一个 OAuth 2.0 Protected Resource,它返回关于经过身份验证的最终用户的声明。为了获得关于最终用户的请求声明,客户机使用通过 OpenID Connect Authentication 获得的访问令牌向 UserInfo 端点发出请求。这些声明通常由一个 JSON 对象表示,该对象包含声明的名称-值对的集合。

                下面的代码显示了 oauth2Login() DSL 可用的完整配置选项:

                OAuth2 Login Configuration Options

                @Configuration
                @EnableWebSecurity
                public class OAuth2LoginSecurityConfig {
                	@Bean
                	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                		http
                			.oauth2Login(oauth2 -> oauth2
                			    .clientRegistrationRepository(this.clientRegistrationRepository())
                			    .authorizedClientRepository(this.authorizedClientRepository())
                			    .authorizedClientService(this.authorizedClientService())
                			    .loginPage("/login")
                			    .authorizationEndpoint(authorization -> authorization
                			        .baseUri(this.authorizationRequestBaseUri())
                			        .authorizationRequestRepository(this.authorizationRequestRepository())
                			        .authorizationRequestResolver(this.authorizationRequestResolver())
                			    )
                			    .redirectionEndpoint(redirection -> redirection
                			        .baseUri(this.authorizationResponseBaseUri())
                			    )
                			    .tokenEndpoint(token -> token
                			        .accessTokenResponseClient(this.accessTokenResponseClient())
                			    )
                			    .userInfoEndpoint(userInfo -> userInfo
                			        .userAuthoritiesMapper(this.userAuthoritiesMapper())
                			        .userService(this.oauth2UserService())
                			        .oidcUserService(this.oidcUserService())
                			    )
                			);
                		return http.build();
                	}
                }
                

                除了 oauth2Login() DSL 之外,还支持 XML 配置。

                下面的代码显示了security namespace中可用的完整配置选项:

                OAuth2 Login XML Configuration Options

                	
                
                

                以下各节将详细介绍每个可用配置选项:

                • OAuth 2.0 Login Page
                • Redirection Endpoint
                • UserInfo Endpoint
                • ID Token Signature Verification
                • [oauth2login-advanced-oidc-logout]

                  OAuth 2.0 Login Page

                  默认情况下,OAuth 2.0登录页面是由 DefaultLoginPageGeneratingFilter 自动生成的。默认登录页面显示每个配置的 OAuth Client 及其 ClientRegistration.clientName 作为链接,该链接能够初始化授权请求(或 OAuth 2.0 Login)。

                  为了使 DefaultLoginPageGeneratingFilter 显示已配置 OAuth 客户端的链接,已注册的 ClientRegistrationRepository 还需要实现 Iterable 。有关参考资料,请参见 InmemyClientRegistrationRepository。

                  每个 OAuth 客户端的链接目标默认如下:

                  OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{registrationId}"

                  下面一行显示了一个示例:

                  Google
                  

                  要覆盖默认登录页面,请配置 oauth2Login().loginPage()和(可选) oauth2Login().authorizationEndpoint().baseUri()。

                  下面的清单显示了一个示例:

                  OAuth2 Login Page Configuration

                  @Configuration
                  @EnableWebSecurity
                  public class OAuth2LoginSecurityConfig {
                  	@Bean
                  	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                  		http
                  			.oauth2Login(oauth2 -> oauth2
                  			    .loginPage("/login/oauth2")
                  			    ...
                  			    .authorizationEndpoint(authorization -> authorization
                  			        .baseUri("/login/oauth2/authorization")
                  			        ...
                  			    )
                  			);
                  		return http.build();
                  	}
                  }
                  

                  您需要为@Controller 提供一个@RequestMapping (“/login/oauth2”) ,该映射能够呈现自定义登录页面。

                  如前所述,配置 oauth2Login().authorizationEndpoint().baseUri()是可选的。但是,如果您选择自定义它,请确保到每个 OAuth Client 的链接与 authorizationEndpoint().baseUri().匹配.

                  下面一行显示了一个示例:

                  Google
                  

                  Redirection Endpoint

                  授权服务器使用重定向端点通过资源所有者用户代理向客户端返回授权响应(其中包含授权凭据)。

                  OAuth 2.0 Login 利用了授权代码授权。

                  默认的 Authorization Response baseUri (重定向端点)是/login/oauth2/code/* ,它在 OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI中定义。

                  如果希望自定义 Authorization Response baseUri,请按以下方式配置它:

                  @Configuration
                  @EnableWebSecurity
                  public class OAuth2LoginSecurityConfig {
                      @Bean
                  	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                  		http
                  			.oauth2Login(oauth2 -> oauth2
                  			    .redirectionEndpoint(redirection -> redirection
                  			        .baseUri("/login/oauth2/callback/*")
                  			        ...
                  			    )
                  			);
                  		return http.build();
                  	}
                  }
                  

                  您还需要确保 ClientRegistration.redirectUri 与自定义 Authorization Response baseUri 匹配。

                  下面的清单显示了一个示例:

                  return CommonOAuth2Provider.GOOGLE.getBuilder("google")
                  	.clientId("google-client-id")
                  	.clientSecret("google-client-secret")
                  	.redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
                  	.build();
                  

                  UserInfo Endpoint

                  UserInfo 端点包括许多配置选项,如下面小节所述:

                  • Mapping User Authorities
                  • OAuth 2.0 UserService
                  • OpenID Connect 1.0 UserService

                    Mapping User Authorities

                    在用户成功地通过 OAuth 2.0提供程序进行身份验证之后,OAuth2User.getAuthority()(或 OidcUser.getAuthority())包含一个由 OAuth2UserRequest.getAccessToken()填充的授权列表。GetScope()并以 SCOPE_作为前缀。这些授予的权限可以映射到一组新的 GrantedAuthority 实例,这些实例在完成身份验证时提供给 OAuth2AuthenticationToken。

                    GetAuthority()用于对请求进行授权,例如 hasRole(‘USER’)或 hasRole (‘ ADMIN’)。

                    在映射用户权限时,有两个选项可供选择:

                    • Using a GrantedAuthoritiesMapper
                    • Delegation-based Strategy with OAuth2UserService
                      Using a GrantedAuthoritiesMapper

                      授予 GrantedAuthoritiesMapper 一个授权列表,其中包含 OAuth2UserAuthority 类型的特殊授权和授权字符串 OAUTH2_USER (或 OidcUserAuthority 和授权字符串 OIDC_USER)。

                      提供 GrantedAuthortiesMapper 的实现并配置它,如下所示:

                      Granted Authorities Mapper Configuration

                      @Configuration
                      @EnableWebSecurity
                      public class OAuth2LoginSecurityConfig {
                          @Bean
                      	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                      		http
                      			.oauth2Login(oauth2 -> oauth2
                      			    .userInfoEndpoint(userInfo -> userInfo
                      			        .userAuthoritiesMapper(this.userAuthoritiesMapper())
                      			        ...
                      			    )
                      			);
                      		return http.build();
                      	}
                      	private GrantedAuthoritiesMapper userAuthoritiesMapper() {
                      		return (authorities) -> {
                      			Set mappedAuthorities = new HashSet();
                      			authorities.forEach(authority -> {
                      				if (OidcUserAuthority.class.isInstance(authority)) {
                      					OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
                      					OidcIdToken idToken = oidcUserAuthority.getIdToken();
                      					OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
                      					// Map the claims found in idToken and/or userInfo
                      					// to one or more GrantedAuthority's and add it to mappedAuthorities
                      				} else if (OAuth2UserAuthority.class.isInstance(authority)) {
                      					OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
                      					Map userAttributes = oauth2UserAuthority.getAttributes();
                      					// Map the attributes found in userAttributes
                      					// to one or more GrantedAuthority's and add it to mappedAuthorities
                      				}
                      			});
                      			return mappedAuthorities;
                      		};
                      	}
                      }
                      

                      或者,您可以注册一个 GrantedAuthortiesMapper@Bean,让它自动应用到配置中,如下所示:

                      Granted Authorities Mapper Bean Configuration

                      @Configuration
                      @EnableWebSecurity
                      public class OAuth2LoginSecurityConfig {
                      	@Bean
                      	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                      		http
                      		    .oauth2Login(withDefaults());
                      		return http.build();
                      	}
                      	@Bean
                      	public GrantedAuthoritiesMapper userAuthoritiesMapper() {
                      		...
                      	}
                      }
                      
                      Delegation-based Strategy with OAuth2UserService

                      与使用 GrantedAuthortiesMapper 相比,此策略更先进。但是,它也更加灵活,因为它允许您访问 OAuth2UserRequest 和 OAuth2User (当使用 OAuth 2.0 UserService 时)或 OidcUserRequest 和 OidcUser (当使用 OpenID Connect 1.0 UserService 时)。

                      OAuth2UserRequest (和 OidcUserRequest)提供了对相关 OAuth2AccessToken 的访问,在委托方需要从受保护的资源获取授权信息才能为用户映射自定义权限的情况下,这非常有用。

                      下面的示例演示如何使用 OpenID Connect 1.0 UserService 实现和配置基于委托的策略:

                      @Configuration
                      @EnableWebSecurity
                      public class OAuth2LoginSecurityConfig {
                      	@Bean
                      	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                      		http
                      			.oauth2Login(oauth2 -> oauth2
                      			    .userInfoEndpoint(userInfo -> userInfo
                      			        .oidcUserService(this.oidcUserService())
                      			        ...
                      			    )
                      			);
                      		return http.build();
                      	}
                      	private OAuth2UserService oidcUserService() {
                      		final OidcUserService delegate = new OidcUserService();
                      		return (userRequest) -> {
                      			// Delegate to the default implementation for loading a user
                      			OidcUser oidcUser = delegate.loadUser(userRequest);
                      			OAuth2AccessToken accessToken = userRequest.getAccessToken();
                      			Set mappedAuthorities = new HashSet();
                      			// TODO
                      			// 1) Fetch the authority information from the protected resource using accessToken
                      			// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
                      			// 3) Create a copy of oidcUser but use the mappedAuthorities instead
                      			ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
                      			String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName();
                      			if (StringUtils.hasText(userNameAttributeName)) {
                      				oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo(), userNameAttributeName);
                      			} else {
                      				oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
                      			}
                      			return oidcUser;
                      		};
                      	}
                      }
                      

                      OAuth 2.0 UserService

                      DefaultOAuth2UserService 是支持标准 OAuth 2.0提供程序的 OAuth2UserService 的实现。

                      OAuth2UserService 从 UserInfo 端点(通过在授权流中使用授予客户端的访问令牌)获取终端用户(资源所有者)的用户属性,并以 OAuthenticatedUser 的形式返回 AuthenticatedPrime。

                      DefaultOAuth2UserService 在 UserInfo 端点请求用户属性时使用 RestOperations 实例。

                      如果需要自定义 UserInfo 请求的预处理,可以为 DefaultOAuth2UserService.setRequestEntityConverter ()提供自定义 Converter >.默认实现 OAuth2UserRequestEntityConverter 构建 UserInfo 请求的 RequestEntity 表示形式,该表示形式默认在 Authorization 头中设置 OAuth2AccessToken。

                      另一方面,如果需要自定义 UserInfo Response 的后处理,则需要为 DefaultOAuth2UserService.setRestOperations ()提供自定义配置的 RestOperations。默认的 RestOperations 配置如下:

                      RestTemplate restTemplate = new RestTemplate();
                      restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
                      

                      OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理 OAuth 2.0错误(400个错误请求)。它使用 OAuth2ErrorHttpMessageConverter 将 OAuth 2.0 Error 参数转换为 OAuth2Error。

                      无论您是自定义 DefaultOAuth2UserService 还是提供自己的 OAuth2UserService 实现,都需要按以下方式配置它:

                      @Configuration
                      @EnableWebSecurity
                      public class OAuth2LoginSecurityConfig {
                      	@Bean
                      	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                      		http
                      			.oauth2Login(oauth2 -> oauth2
                      			    .userInfoEndpoint(userInfo -> userInfo
                      			        .userService(this.oauth2UserService())
                      			        ...
                      			    )
                      			);
                      		return http.build();
                      	}
                      	private OAuth2UserService oauth2UserService() {
                      		...
                      	}
                      }
                      

                      OpenID Connect 1.0 UserService

                      OidcUserService 是支持 OpenID Connect 1.0 Provider 的 OAuth2UserService 的实现。

                      OidcUserService 在 UserInfo 端点请求用户属性时利用 DefaultOAuth2UserService。

                      如果需要自定义 UserInfo 请求的预处理或 UserInfo 响应的后处理,则需要为 OidcUserService.setOauth2UserService ()提供自定义配置的 DefaultOAuth2UserService。

                      无论您是自定义 OidcUserService,还是为 OpenID Connect 1.0 Provider 提供自己的 OAuth2UserService 实现,都需要将其配置如下:

                      @Configuration
                      @EnableWebSecurity
                      public class OAuth2LoginSecurityConfig {
                      	@Bean
                      	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                      		http
                      			.oauth2Login(oauth2 -> oauth2
                      				.userInfoEndpoint(userInfo -> userInfo
                      				    .oidcUserService(this.oidcUserService())
                      				    ...
                      			    )
                      			);
                      		return http.build();
                      	}
                      	private OAuth2UserService oidcUserService() {
                      		...
                      	}
                      }
                      

                      ID Token Signature Verification

                      OpenID Connect 1.0 Authentication 引入了 ID 令牌,这是一种安全令牌,包含授权服务器在客户端使用时对最终用户身份验证的声明。

                      ID 令牌表示为 JSON Web 令牌(JWT) ,必须使用 JSON Web 签名(JWS)进行签名。

                      OidcIdTokenDecoderFactory 提供用于 OidcIdToken 签名验证的 JwtDecder。默认算法是 RS256,但在客户端注册期间分配时可能会有所不同。对于这些情况,您可以配置解析器来返回为特定客户机分配的预期 JWS 算法。

                      JWS 算法解析器是一个函数,它接受 ClientRegistry 并为客户机返回预期的 Jws松弛算法,例如 SignatureAlobacm.RS256或 MacAlobacm.HS256

                      下面的代码显示了如何为所有 ClientRegistry 实例配置 OidcIdTokenDecoderFactory@Bean,使其默认为 MacAlobacm.HS256:

                      @Bean
                      public JwtDecoderFactory idTokenDecoderFactory() {
                      	OidcIdTokenDecoderFactory idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
                      	idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256);
                      	return idTokenDecoderFactory;
                      }
                      

                      对于基于 MAC 的算法(如 HS256、 HS384或 HS512) ,对应于客户机 ID 的客户机机密被用作签名验证的对称密钥。

                      如果为 OpenID Connect 1.0 Authentication 配置了多个 ClientRegistry,则 JWS 算法解析器可以评估所提供的 ClientRegistry,以确定返回哪个算法。

                      然后,您可以继续配置注销

                      OIDC Logout

                      一旦终端用户能够登录到您的应用程序,考虑他们将如何登出就很重要了。

                      一般来说,有三种用例供您考虑:

                      • 我只想执行本地注销
                      • 我想注销我的应用程序和由我的应用程序启动的 OIDC 提供程序
                      • 想注销我的应用程序和由 OIDC 提供程序发起的 OIDC 提供程序

                        Local Logout

                        要执行本地注销,不需要特殊的 OIDC 配置。SpringSecurity 会自动启动一个本地注销端点,您可以通过 logout () DSL 对其进行配置。

                        OpenID Connect 1.0 Client-Initiated Logout

                        OpenID Connect Session Management 1.0允许使用 Client 在提供者注销最终用户。其中一个可用的策略是 RP 启动注销。

                        如果 OpenID 提供程序同时支持会话管理和发现,那么客户端可以从 OpenID 提供程序的 Discovery 元数据中获取 end_session_endpoint URL。您可以通过使用issuer-uri配置 ClientRegistry,如下所示:

                        spring:
                          security:
                            oauth2:
                              client:
                                registration:
                                  okta:
                                    client-id: okta-client-id
                                    client-secret: okta-client-secret
                                    ...
                                provider:
                                  okta:
                                    issuer-uri: https://dev-1234.oktapreview.com
                        

                        此外,您还应该配置实现 RP-Initiated Logout 的 OidcClientInitiatedLogoutSuccess Handler,如下所示:

                        @Configuration
                        @EnableWebSecurity
                        public class OAuth2LoginSecurityConfig {
                        	@Autowired
                        	private ClientRegistrationRepository clientRegistrationRepository;
                        	@Bean
                        	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                        		http
                        			.authorizeHttpRequests(authorize -> authorize
                        				.anyRequest().authenticated()
                        			)
                        			.oauth2Login(withDefaults())
                        			.logout(logout -> logout
                        				.logoutSuccessHandler(oidcLogoutSuccessHandler())
                        			);
                        		return http.build();
                        	}
                        	private LogoutSuccessHandler oidcLogoutSuccessHandler() {
                        		OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
                        				new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
                        		// Sets the location that the End-User's User Agent will be redirected to
                        		// after the logout has been performed at the Provider
                        		oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
                        		return oidcLogoutSuccessHandler;
                        	}
                        }
                        

                        OidcClientInitiatedLogoutSuccess Handler 支持{ baseUrl }占位符。

                        OpenID Connect 1.0 Back-Channel Logout

                        OpenID Connect Session Management 1.0允许提供者向客户端发出 API 调用,从而在客户端注销最终用户。这被称为 OIDC 后台注销。

                        为了实现这一点,您可以像下面这样设置 DSL 中的 Back-Channel Logout 端点:

                        @Bean
                        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                            http
                                .authorizeHttpRequests((authorize) -> authorize
                                    .anyRequest().authenticated()
                                )
                                .oauth2Login(withDefaults())
                                .oidcLogout((logout) -> logout
                                    .backChannel(Customizer.withDefaults())
                                );
                            return http.build();
                        }
                        

                        然后,您需要一种方法来监听 Spring Security 发布的事件,以删除旧的 OidcSessionInformation 条目,如下所示:

                        @Bean
                        public HttpSessionEventPublisher sessionEventPublisher() {
                            return new HttpSessionEventPublisher();
                        }
                        

                        这样,如果调用 HttpSession # 无效,那么会话也将从内存中删除。

                        这将支持端点/logout/connect/back-channel/{ registrationId } ,OIDC 提供程序可以请求该端点/logout/connect/back-channel/{ registrationId }使应用程序中最终用户的给定会话无效。

                        Back-Channel Logout Architecture

                        考虑标识符为 registrationId 的 ClientRegistry。

                        后台注销的总体流程如下:

                        1. 在登录时,Spring Security 在其 OidcSessionStrategy 实现中将 ID 令牌、 CSRF 令牌和提供程序会话 ID (如果有的话)与应用程序的会话 ID 相关联
                        2. 然后在注销时,OIDC 提供程序对/Logout/connect/back-channel/registrationId 进行 API 调用,包括一个注销令牌,指示要注销的子(最终用户)或 sid (提供程序会话 ID)。
                        3. SpringSecurity 验证令牌的签名和声明。
                        4. 如果令牌包含 sid 声明,则只有与该提供程序会话相关联的客户端会话被终止。
                        5. 否则,如果令牌包含子声明,则终止该最终用户的所有客户端会话。

                        Customizing the OIDC Provider Session Strategy

                        默认情况下,Spring Security 在内存中存储 OIDC 提供程序会话和客户端会话之间的所有链接。

                        在许多情况下,比如集群应用程序,最好将它存储在一个单独的位置,比如数据库。

                        您可以通过配置自定义 OidcSessionStrategy 来实现这一点,如下所示:

                        @Component
                        public final class MySpringDataOidcSessionStrategy implements OidcSessionStrategy {
                            private final OidcProviderSessionRepository sessions;
                            // ...
                            @Override
                            public void saveSessionInformation(OidcSessionInformation info) {
                                this.sessions.save(info);
                            }
                            @Override
                            public OidcSessionInformation(String clientSessionId) {
                               return this.sessions.removeByClientSessionId(clientSessionId);
                            }
                            @Override
                            public Iterable removeSessionInformation(OidcLogoutToken token) {
                                return token.getSessionId() != null ?
                                    this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
                                    this.sessions.removeBySubjectAndIssuerAndAudience(...);
                            }
                        }
                        
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]