高階配置

OAuth 2.0 授權框架將 協議端點 定義如下

授權過程使用兩個授權伺服器端點(HTTP 資源)

  • 授權端點:客戶端透過使用者代理重定向從資源所有者獲取授權。

  • 令牌端點:客戶端用於交換授權許可以獲取訪問令牌,通常需要客戶端身份驗證。

以及一個客戶端端點

  • 重定向端點:授權伺服器透過資源所有者使用者代理將包含授權憑據的響應返回給客戶端。

OpenID Connect Core 1.0 規範將 UserInfo 端點 定義如下

UserInfo 端點是一個 OAuth 2.0 保護資源,它返回有關已認證的終端使用者的資訊。為了獲取有關終端使用者的請求資訊,客戶端透過使用透過 OpenID Connect 認證獲得的訪問令牌向 UserInfo 端點發出請求。這些資訊通常以 JSON 物件表示,其中包含一系列鍵值對。

ServerHttpSecurity.oauth2Login() 提供了許多配置選項,用於自定義 OAuth 2.0 登入。

以下程式碼展示了 oauth2Login() DSL 的所有可用配置選項

OAuth2 登入配置選項
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

	@Bean
	SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
		http
			.oauth2Login((oauth2) -> oauth2
				.authenticationConverter(this.authenticationConverter())
				.authenticationMatcher(this.authenticationMatcher())
				.authenticationManager(this.authenticationManager())
				.authenticationSuccessHandler(this.authenticationSuccessHandler())
				.authenticationFailureHandler(this.authenticationFailureHandler())
				.clientRegistrationRepository(this.clientRegistrationRepository())
				.authorizedClientRepository(this.authorizedClientRepository())
				.authorizedClientService(this.authorizedClientService())
				.authorizationRequestResolver(this.authorizationRequestResolver())
				.authorizationRequestRepository(this.authorizationRequestRepository())
				.securityContextRepository(this.securityContextRepository())
			);

		return http.build();
	}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            oauth2Login {
                authenticationConverter = authenticationConverter()
                authenticationMatcher = authenticationMatcher()
                authenticationManager = authenticationManager()
                authenticationSuccessHandler = authenticationSuccessHandler()
                authenticationFailureHandler = authenticationFailureHandler()
                clientRegistrationRepository = clientRegistrationRepository()
                authorizedClientRepository = authorizedClientRepository()
                authorizedClientService = authorizedClientService()
                authorizationRequestResolver = authorizationRequestResolver()
                authorizationRequestRepository = authorizationRequestRepository()
                securityContextRepository = securityContextRepository()
            }
        }

        return http.build()
    }
}

以下部分將更詳細地介紹每個可用配置選項

OAuth 2.0 登入頁面

預設情況下,OAuth 2.0 登入頁面由 LoginPageGeneratingWebFilter 自動生成。預設登入頁面顯示每個配置的 OAuth 客戶端,並將其 ClientRegistration.clientName 作為連結,該連結能夠啟動授權請求(或 OAuth 2.0 登入)。

為了讓 LoginPageGeneratingWebFilter 顯示配置的 OAuth 客戶端的連結,註冊的 ReactiveClientRegistrationRepository 也需要實現 Iterable<ClientRegistration>。請參閱 InMemoryReactiveClientRegistrationRepository 以供參考。

每個 OAuth 客戶端的連結目標預設為以下內容

"/oauth2/authorization/{registrationId}"

以下行顯示了一個示例

<a href="/oauth2/authorization/google">Google</a>

要覆蓋預設登入頁面,請配置 exceptionHandling().authenticationEntryPoint() 和(可選)oauth2Login().authorizationRequestResolver()

以下列表顯示了一個示例

OAuth2 登入頁面配置
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.exceptionHandling((exceptionHandling) -> exceptionHandling
				.authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/login/oauth2"))
			)
			.oauth2Login((oauth2) -> oauth2
				.authorizationRequestResolver(this.authorizationRequestResolver())
			);

		return http.build();
	}

	private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver() {
		ServerWebExchangeMatcher authorizationRequestMatcher =
				new PathPatternParserServerWebExchangeMatcher(
						"/login/oauth2/authorization/{registrationId}");

		return new DefaultServerOAuth2AuthorizationRequestResolver(
				this.clientRegistrationRepository(), authorizationRequestMatcher);
	}

	...
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            exceptionHandling {
                authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/login/oauth2")
            }
            oauth2Login {
                authorizationRequestResolver = authorizationRequestResolver()
            }
        }

        return http.build()
    }

    private fun authorizationRequestResolver(): ServerOAuth2AuthorizationRequestResolver {
        val authorizationRequestMatcher: ServerWebExchangeMatcher = PathPatternParserServerWebExchangeMatcher(
            "/login/oauth2/authorization/{registrationId}"
        )

        return DefaultServerOAuth2AuthorizationRequestResolver(
            clientRegistrationRepository(), authorizationRequestMatcher
        )
    }

    ...
}
您需要提供一個帶有 @RequestMapping("/login/oauth2")@Controller,它能夠渲染自定義登入頁面。

如前所述,配置 oauth2Login().authorizationRequestResolver() 是可選的。但是,如果您選擇自定義它,請確保每個 OAuth 客戶端的連結與透過 ServerWebExchangeMatcher 提供的模式匹配。

以下行顯示了一個示例

<a href="/login/oauth2/authorization/google">Google</a>

重定向端點

重定向端點由授權伺服器用於透過資源所有者使用者代理將授權響應(其中包含授權憑據)返回給客戶端。

OAuth 2.0 登入利用授權碼授權。因此,授權憑據是授權碼。

預設的授權響應重定向端點是 /login/oauth2/code/{registrationId}

如果您想自定義授權響應重定向端點,請按照以下示例進行配置

重定向端點配置
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.oauth2Login((oauth2) -> oauth2
				.authenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}"))
			);

		return http.build();
	}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            oauth2Login {
                authenticationMatcher = PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}")
            }
        }

        return http.build()
    }
}

您還需要確保 ClientRegistration.redirectUri 與自定義的授權響應重定向端點匹配。

以下列表顯示了一個示例

  • Java

  • Kotlin

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

UserInfo 端點

UserInfo 端點包含許多配置選項,如下面的小節所述

對映使用者許可權

使用者成功透過 OAuth 2.0 提供者身份驗證後,OAuth2User.getAuthorities()(或 OidcUser.getAuthorities())包含一個從 OAuth2UserRequest.getAccessToken().getScopes() 填充並以 SCOPE_ 為字首的授權列表。這些授予的許可權可以對映到一組新的 GrantedAuthority 例項,這些例項將在完成身份驗證時提供給 OAuth2AuthenticationToken

OAuth2AuthenticationToken.getAuthorities() 用於授權請求,例如在 hasRole('USER')hasRole('ADMIN') 中。

對映使用者許可權時有幾種選擇

使用 GrantedAuthoritiesMapper

GrantedAuthoritiesMapper 被賦予一個許可權列表,其中包含一個特殊許可權,型別為 OAuth2UserAuthority,許可權字串為 OAUTH2_USER(或 OidcUserAuthority 和許可權字串 OIDC_USER)。

註冊一個 GrantedAuthoritiesMapper @Bean 以便將其自動應用於配置,如以下示例所示

Granted Authorities Mapper 配置
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			...
			.oauth2Login(withDefaults());

		return http.build();
	}

	@Bean
	public GrantedAuthoritiesMapper userAuthoritiesMapper() {
		return (authorities) -> {
			Set<GrantedAuthority> 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<String, Object> userAttributes = oauth2UserAuthority.getAttributes();

					// Map the attributes found in userAttributes
					// to one or more GrantedAuthority's and add it to mappedAuthorities

				}
			});

			return mappedAuthorities;
		};
	}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            oauth2Login { }
        }

        return http.build()
    }

    @Bean
    fun userAuthoritiesMapper(): GrantedAuthoritiesMapper = GrantedAuthoritiesMapper { authorities: Collection<GrantedAuthority> ->
        val mappedAuthorities = emptySet<GrantedAuthority>()

        authorities.forEach { authority ->
            if (authority is OidcUserAuthority) {
                val idToken = authority.idToken
                val userInfo = authority.userInfo
                // Map the claims found in idToken and/or userInfo
                // to one or more GrantedAuthority's and add it to mappedAuthorities
            } else if (authority is OAuth2UserAuthority) {
                val userAttributes = authority.attributes
                // Map the attributes found in userAttributes
                // to one or more GrantedAuthority's and add it to mappedAuthorities
            }
        }

        mappedAuthorities
    }
}

基於委託的 ReactiveOAuth2UserService 策略

與使用 GrantedAuthoritiesMapper 相比,此策略更高階,但它也更靈活,因為它允許您訪問 OAuth2UserRequestOAuth2User(在使用 OAuth 2.0 UserService 時)或 OidcUserRequestOidcUser(在使用 OpenID Connect 1.0 UserService 時)。

OAuth2UserRequest(和 OidcUserRequest)允許您訪問相關的 OAuth2AccessToken,這在委託方需要從受保護資源獲取許可權資訊才能為使用者對映自定義許可權的情況下非常有用。

以下示例展示瞭如何使用 OpenID Connect 1.0 UserService 實現和配置基於委託的策略

ReactiveOAuth2UserService 配置
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			...
			.oauth2Login(withDefaults());

		return http.build();
	}

	@Bean
	public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
		final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService();

		return (userRequest) -> {
			// Delegate to the default implementation for loading a user
			return delegate.loadUser(userRequest)
					.flatMap((oidcUser) -> {
						OAuth2AccessToken accessToken = userRequest.getAccessToken();
						Set<GrantedAuthority> 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 Mono.just(oidcUser);
					});
		};
	}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            oauth2Login { }
        }

        return http.build()
    }

    @Bean
    fun oidcUserService(): ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
        val delegate = OidcReactiveOAuth2UserService()

        return ReactiveOAuth2UserService { userRequest ->
            // Delegate to the default implementation for loading a user
            delegate.loadUser(userRequest)
                .flatMap { oidcUser ->
                    val accessToken = userRequest.accessToken
                    val mappedAuthorities = mutableSetOf<GrantedAuthority>()

                    // 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
                    val providerDetails = userRequest.getClientRegistration().getProviderDetails()
                    val userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName()
                    val mappedOidcUser = if (StringUtils.hasText(userNameAttributeName)) {
                        DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo, userNameAttributeName)
                    } else {
                        DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
                    }

                    Mono.just(mappedOidcUser)
                }
        }
    }
}

OAuth 2.0 UserService

DefaultReactiveOAuth2UserServiceReactiveOAuth2UserService 的一個實現,它支援標準的 OAuth 2.0 提供者。

ReactiveOAuth2UserService 從 UserInfo 端點獲取終端使用者(資源所有者)的使用者屬性(透過使用授權流程中授予給客戶端的訪問令牌),並以 OAuth2User 的形式返回 AuthenticatedPrincipal

DefaultReactiveOAuth2UserService 在 UserInfo 端點請求使用者屬性時使用 WebClient

如果您需要自定義 UserInfo 請求的預處理和/或 UserInfo 響應的後處理,您需要為 DefaultReactiveOAuth2UserService.setWebClient() 提供一個自定義配置的 WebClient

無論您是自定義 DefaultReactiveOAuth2UserService 還是提供您自己的 ReactiveOAuth2UserService 實現,您都需要按照以下示例進行配置

  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			...
			.oauth2Login(withDefaults());

		return http.build();
	}

	@Bean
	public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
		...
	}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            oauth2Login { }
        }

        return http.build()
    }

    @Bean
    fun oauth2UserService(): ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> {
        // ...
    }
}

OpenID Connect 1.0 UserService

OidcReactiveOAuth2UserServiceReactiveOAuth2UserService 的一個實現,支援 OpenID Connect 1.0 提供者。

OidcReactiveOAuth2UserService 在 UserInfo 端點請求使用者屬性時利用 DefaultReactiveOAuth2UserService

如果您需要自定義 UserInfo 請求的預處理和/或 UserInfo 響應的後處理,您將需要為 OidcReactiveOAuth2UserService.setOauth2UserService() 提供一個自定義配置的 ReactiveOAuth2UserService

無論您是自定義 OidcReactiveOAuth2UserService 還是為 OpenID Connect 1.0 提供者提供您自己的 ReactiveOAuth2UserService 實現,您都需要按照以下示例進行配置

  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			...
			.oauth2Login(withDefaults());

		return http.build();
	}

	@Bean
	public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
		...
	}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            oauth2Login { }
        }

        return http.build()
    }

    @Bean
    fun oidcUserService(): ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
        // ...
    }
}

ID 令牌簽名驗證

OpenID Connect 1.0 認證引入了 ID 令牌,它是一種安全令牌,包含客戶端使用授權伺服器對終端使用者進行認證的宣告。

ID 令牌以 JSON Web Token (JWT) 的形式表示,並且必須使用 JSON Web Signature (JWS) 進行簽名。

ReactiveOidcIdTokenDecoderFactory 提供了一個 ReactiveJwtDecoder,用於 OidcIdToken 簽名驗證。預設演算法是 RS256,但在客戶端註冊期間分配時可能會有所不同。對於這些情況,可以配置一個解析器來返回為特定客戶端分配的預期 JWS 演算法。

JWS 演算法解析器是一個 Function,它接受一個 ClientRegistration 並返回客戶端預期的 JwsAlgorithm,例如 SignatureAlgorithm.RS256MacAlgorithm.HS256

以下程式碼顯示瞭如何配置 OidcIdTokenDecoderFactory @Bean,使其對所有 ClientRegistration 預設使用 MacAlgorithm.HS256

  • Java

  • Kotlin

@Bean
public ReactiveJwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
	ReactiveOidcIdTokenDecoderFactory idTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
	idTokenDecoderFactory.setJwsAlgorithmResolver((clientRegistration) -> clientRegistration.HS256);
	return idTokenDecoderFactory;
}
@Bean
fun idTokenDecoderFactory(): ReactiveJwtDecoderFactory<ClientRegistration> {
    val idTokenDecoderFactory = ReactiveOidcIdTokenDecoderFactory()
    idTokenDecoderFactory.setJwsAlgorithmResolver { MacAlgorithm.HS256 }
    return idTokenDecoderFactory
}
對於基於 MAC 的演算法,例如 HS256HS384HS512,與 client-id 對應的 client-secret 用作簽名驗證的對稱金鑰。
如果為 OpenID Connect 1.0 認證配置了多個 ClientRegistration,JWS 演算法解析器可以評估提供的 ClientRegistration 以確定要返回的演算法。

然後,您可以繼續配置 退出登入

© . This site is unofficial and not affiliated with VMware.