多因素認證
多因素認證 (MFA) 要求使用者提供多種因素才能進行認證。OWASP 將因素分為以下幾類:
-
使用者知道的(例如密碼)
-
使用者擁有的(例如訪問簡訊或電子郵件)
-
使用者本身的(例如生物識別)
-
使用者所在的(例如地理位置)
-
使用者做的(例如行為分析)
FactorGrantedAuthority
在認證時,Spring Security 的認證機制會新增一個 FactorGrantedAuthority。例如,當用戶使用密碼進行認證時,一個 FactorGrantedAuthority 會自動新增到 Authentication 中,其 authority 為 FactorGrantedAuthority.PASSWORD_AUTHORITY。為了在 Spring Security 中要求 MFA,您必須:
-
指定需要多個因素的授權規則
-
為每個因素設定認證
@EnableMultiFactorAuthentication
@EnableMultiFactorAuthentication 使啟用多因素認證變得容易。下面是一個配置,它為每個授權規則添加了密碼和 OTT 的要求。
-
Java
-
Kotlin
@EnableMultiFactorAuthentication(authorities = {
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY })
@EnableMultiFactorAuthentication( authorities = [
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY])
我們現在能夠簡潔地建立始終需要多個因素的配置。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
(1)
.requestMatchers("/admin/**").hasRole("ADMIN")
(2)
.anyRequest().authenticated()
)
(3)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
(1)
authorize("/admin/**", hasRole("ADMIN"))
(2)
authorize(anyRequest, authenticated)
}
(3)
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 以 /admin/** 開頭的 URL 需要 FACTOR_OTT、FACTOR_PASSWORD、ROLE_ADMIN 許可權。 |
| 2 | 所有其他 URL 都需要 FACTOR_OTT、FACTOR_PASSWORD 許可權。 |
| 3 | 設定可以提供所需因素的認證機制。 |
Spring Security 在幕後根據缺少哪個許可權來知道要跳轉到哪個端點。如果使用者最初使用他們的使用者名稱和密碼登入,那麼 Spring Security 將重定向到一次性令牌登入頁面。如果使用者最初使用令牌登入,那麼 Spring Security 將重定向到使用者名稱/密碼登入頁面。
AuthorizationManagerFactory
@EnableMultiFactorAuthentication 的 authorities 屬性只是釋出一個 AuthorizationManagerFactory Bean 的快捷方式。當一個 AuthorizationManagerFactory Bean 可用時,Spring Security 會使用它來建立授權規則,例如在 AuthorizationManagerFactory Bean 介面上定義的 hasAnyRole(String)。@EnableMultiFactorAuthentication 釋出的實現將確保每個授權都與擁有指定因素的要求相結合。
下面這個 AuthorizationManagerFactory Bean 就是前面討論的 @EnableMultiFactorAuthentication 示例中釋出的。
-
Java
-
Kotlin
@Bean
AuthorizationManagerFactory<Object> authz() {
return AuthorizationManagerFactories.multiFactor()
.requireFactors(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
.build();
}
@Bean
fun authz(): AuthorizationManagerFactory<Object> {
return AuthorizationManagerFactories.multiFactor<Object>()
.requireFactors(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
.build()
}
選擇性地要求 MFA
我們已經演示瞭如何透過使用 @EnableMultiFactorAuthentication 的 authorities 屬性來配置整個應用程式以要求 MFA。然而,有時應用程式只希望部分應用程式要求 MFA。考慮以下要求:
-
以
/admin/開頭的 URL 應該需要FACTOR_OTT、FACTOR_PASSWORD、ROLE_ADMIN許可權。 -
以
/user/settings開頭的 URL 應該需要FACTOR_OTT、FACTOR_PASSWORD許可權。 -
所有其他 URL 都需要已認證使用者。
在這種情況下,有些 URL 需要 MFA,而有些則不需要。這意味著我們之前看到的全域性方法不起作用。幸運的是,我們可以使用在 AuthorizationManagerFactory 中學到的知識來簡潔地解決這個問題。
首先,指定不帶任何許可權的 @EnableMultiFactorAuthentication。透過這樣做,我們啟用了 MFA 支援,但沒有釋出 AuthorizationManagerFactory Bean。
-
Java
-
Kotlin
@EnableMultiFactorAuthentication(authorities = {})
@EnableMultiFactorAuthentication(authorities = [])
接下來,建立一個 AuthorizationManagerFactory 例項,但不要將其釋出為 Bean。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
(1)
var mfa = AuthorizationManagerFactories.multiFactor()
.requireFactors(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
.build();
http
.authorizeHttpRequests((authorize) -> authorize
(2)
.requestMatchers("/admin/**").access(mfa.hasRole("ADMIN"))
(3)
.requestMatchers("/user/settings/**").access(mfa.authenticated())
(4)
.anyRequest().authenticated()
)
(5)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
@Throws(Exception::class)
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
(1)
val mfa = AuthorizationManagerFactories.multiFactor<Any>()
.requireFactors(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
.build()
http {
authorizeHttpRequests {
(2)
authorize("/admin/**", mfa.hasRole("ADMIN"))
(3)
authorize("/user/settings/**", mfa.authenticated())
(4)
authorize(anyRequest, authenticated)
}
(5)
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 像之前一樣建立一個 DefaultAuthorizationManagerFactory,但不要將其釋出為 Bean。透過不將其釋出為 Bean,我們可以選擇性地使用 AuthorizationManagerFactory,而不是將其用於每個授權規則。 |
| 2 | 明確使用 AuthorizationManagerFactory,以便以 /admin/** 開頭的 URL 需要 FACTOR_OTT、FACTOR_PASSWORD 和 ROLE_ADMIN。 |
| 3 | 明確使用 AuthorizationManagerFactory,以便以 /user/settings 開頭的 URL 需要 FACTOR_OTT 和 FACTOR_PASSWORD。 |
| 4 | 否則,請求必須經過認證。沒有 MFA 要求,因為未使用 AuthorizationManagerFactory。 |
| 5 | 設定可以提供所需因素的認證機制。 |
指定有效時長
有時,我們可能希望根據最近的認證時間來定義授權規則。例如,應用程式可能希望要求使用者在過去一小時內進行過認證,才能允許訪問 /user/settings 端點。
請記住,在認證時,一個 FactorGrantedAuthority 被新增到 Authentication 中。FactorGrantedAuthority 指定了它的 issuedAt 時間,但沒有描述它的有效期。這是故意的,因為它允許一個 FactorGrantedAuthority 用於不同的 validDuration。
讓我們看一個示例,說明如何滿足以下要求:
-
以
/admin/開頭的 URL 應要求在過去 30 分鐘內提供了密碼。 -
以
/user/settings開頭的 URL 應要求在過去一小時內提供了密碼。 -
否則,需要認證,但不需要關心是否是密碼或認證發生在多久以前。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
(1)
var passwordIn30m = AuthorizationManagerFactories.multiFactor()
.requireFactor( (factor) -> factor
.passwordAuthority()
.validDuration(Duration.ofMinutes(30))
)
.build();
(2)
var passwordInHour = AuthorizationManagerFactories.multiFactor()
.requireFactor( (factor) -> factor
.passwordAuthority()
.validDuration(Duration.ofHours(1))
)
.build();
http
.authorizeHttpRequests((authorize) -> authorize
(3)
.requestMatchers("/admin/**").access(passwordIn30m.hasRole("ADMIN"))
(4)
.requestMatchers("/user/settings/**").access(passwordInHour.authenticated())
(5)
.anyRequest().authenticated()
)
(6)
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
@Throws(Exception::class)
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
(1)
val passwordIn30m = AuthorizationManagerFactories.multiFactor<Any>()
.requireFactor( { factor -> factor
.passwordAuthority()
.validDuration(Duration.ofMinutes(30))
})
.build()
(2)
val passwordInHour = AuthorizationManagerFactories.multiFactor<Any>()
.requireFactor( { factor -> factor
.passwordAuthority()
.validDuration(Duration.ofHours(1))
})
.build()
http {
authorizeHttpRequests {
(3)
authorize("/admin/**", passwordIn30m.hasRole("ADMIN"))
(4)
authorize("/user/settings/**", passwordInHour.authenticated())
(5)
authorize(anyRequest, authenticated)
}
(6)
formLogin { }
}
return http.build()
}
| 1 | 首先,我們將 passwordIn30m 定義為 30 分鐘內密碼的要求。 |
| 2 | 接下來,我們將 passwordInHour 定義為一小時內密碼的要求。 |
| 3 | 我們使用 passwordIn30m 來要求以 /admin/ 開頭的 URL 必須在過去 30 分鐘內提供了密碼,並且使用者具有 ROLE_ADMIN 許可權。 |
| 4 | 我們使用 passwordInHour 來要求以 /user/settings 開頭的 URL 必須在過去一小時內提供了密碼。 |
| 5 | 否則,需要認證,但不需要關心是否是密碼或認證發生在多久以前。 |
| 6 | 設定可以提供所需因素的認證機制。 |
程式設計式 MFA
在我們之前的示例中,MFA 是一個靜態的每個請求的決定。有時我們可能希望對某些使用者要求 MFA,而對其他使用者不要求。透過建立一個自定義的 AuthorizationManager,根據 Authentication 有條件地要求因素,可以實現按使用者啟用 MFA。
-
Java
-
Kotlin
@Component
class AdminMfaAuthorizationManager implements AuthorizationManager<Object> {
@Override
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, Object context) {
if ("admin".equals(authentication.get().getName())) {
AuthorizationManager<Object> admins =
AllAuthoritiesAuthorizationManager.hasAllAuthorities(
FactorGrantedAuthority.OTT_AUTHORITY,
FactorGrantedAuthority.PASSWORD_AUTHORITY
);
(1)
return admins.authorize(authentication, context);
} else {
(2)
return new AuthorizationDecision(true);
}
}
}
@Component
internal open class AdminMfaAuthorizationManager : AuthorizationManager<Object> {
override fun authorize(
authentication: Supplier<out Authentication?>, context: Object): AuthorizationResult {
return if ("admin" == authentication.get().name) {
var admins =
AllAuthoritiesAuthorizationManager.hasAllAuthorities<Any>(
FactorGrantedAuthority.OTT_AUTHORITY,
FactorGrantedAuthority.PASSWORD_AUTHORITY)
(1)
admins.authorize(authentication, context)
} else {
(2)
AuthorizationDecision(true)
}
}
}
| 1 | 對於使用者名稱為 admin 的使用者,需要 MFA。 |
| 2 | 否則,不需要 MFA。 |
為了全域性啟用 MFA 規則,我們可以釋出一個 AuthorizationManagerFactory Bean。
-
Java
-
Kotlin
@Bean
AuthorizationManagerFactory<Object> authorizationManagerFactory(
AdminMfaAuthorizationManager admins) {
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
(1)
defaults.setAdditionalAuthorization(admins);
(2)
return defaults;
}
@Bean
fun authorizationManagerFactory(admins: AdminMfaAuthorizationManager): AuthorizationManagerFactory<Object> {
val defaults = DefaultAuthorizationManagerFactory<Object>()
(1)
defaults.setAdditionalAuthorization(admins)
(2)
return defaults
}
| 1 | 將自定義 AuthorizationManager 注入為 DefaultAuthorization.additionalAuthorization。這指示 DefaultAuthorizationManagerFactory 任何授權規則都應應用我們的自定義 AuthorizationManager 以及應用程式定義的任何授權要求(例如 `hasRole("ADMIN")`)。 |
| 2 | 將 DefaultAuthorizationManagerFactory 釋出為 Bean,以便它全域性使用。 |
這應該與我們在 AuthorizationManagerFactory 中的前一個示例非常相似。不同之處在於,在前一個示例中,AuthorizationManagerFactories 將 DefaultAuthorization.additionalAuthorization 設定為內建的 AuthorizationManager,該管理器始終要求相同的許可權。
我們現在可以定義與 AdminMfaAuthorizationManager 結合的授權規則。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
(1)
authorize("/admin/**", hasRole("ADMIN"))
(2)
authorize(anyRequest, authenticated)
}
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 以 /admin/** 開頭的 URL 需要 ROLE_ADMIN。如果使用者名稱是 admin,則還需要 FACTOR_OTT 和 FACTOR_PASSWORD。 |
| 2 | 否則,請求必須經過認證。如果使用者名稱是 admin,則還需要 FACTOR_OTT 和 FACTOR_PASSWORD。 |
MFA 是按使用者名稱而不是按角色啟用的,因為我們是這樣實現 RequiredAuthoritiesAuthorizationManagerConfiguration 的。如果我們願意,我們可以更改邏輯,根據角色而不是使用者名稱啟用 MFA。 |
RequiredAuthoritiesAuthorizationManager
我們已經演示瞭如何在 程式設計式 MFA 中使用自定義 AuthorizationManager 動態確定特定使用者的許可權。然而,這是一個非常常見的場景,因此 Spring Security 使用 RequiredAuthoritiesAuthorizationManager 和 RequiredAuthoritiesRepository 提供了內建支援。
讓我們使用內建支援來實現與 程式設計式 MFA 中相同的要求。
我們首先建立要使用的 RequiredAuthoritiesAuthorizationManager Bean。
-
Java
-
Kotlin
@Bean
RequiredAuthoritiesAuthorizationManager<Object> adminAuthorization() {
(1)
MapRequiredAuthoritiesRepository authorities = new MapRequiredAuthoritiesRepository();
authorities.saveRequiredAuthorities("admin", List.of(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY)
);
(2)
return new RequiredAuthoritiesAuthorizationManager<>(authorities);
}
@Bean
fun adminAuthorization(): RequiredAuthoritiesAuthorizationManager<Object> {
(1)
val authorities = MapRequiredAuthoritiesRepository()
authorities.saveRequiredAuthorities("admin", List.of(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY)
)
(2)
return RequiredAuthoritiesAuthorizationManager(authorities)
}
| 1 | 建立一個 MapRequiredAuthoritiesRepository,它將使用者名稱為 admin 的使用者對映到需要 MFA。 |
| 2 | 返回一個注入了 MapRequiredAuthoritiesRepository 的 RequiredAuthoritiesAuthorizationManager。 |
接下來,我們可以定義一個使用 RequiredAuthoritiesAuthorizationManager 的 AuthorizationManagerFactory。
-
Java
-
Kotlin
@Bean
AuthorizationManagerFactory<Object> authorizationManagerFactory(
RequiredAuthoritiesAuthorizationManager admins) {
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
(1)
defaults.setAdditionalAuthorization(admins);
(2)
return defaults;
}
@Bean
fun authorizationManagerFactory(admins: RequiredAuthoritiesAuthorizationManager<Object>): AuthorizationManagerFactory<Object> {
val defaults = DefaultAuthorizationManagerFactory<Object>()
(1)
defaults.setAdditionalAuthorization(admins)
(2)
return defaults
}
| 1 | 將 RequiredAuthoritiesAuthorizationManager 注入為 DefaultAuthorization.additionalAuthorization。這指示 DefaultAuthorizationManagerFactory 任何授權規則都應應用 RequiredAuthoritiesAuthorizationManager 以及應用程式定義的任何授權要求(例如 `hasRole("ADMIN")`)。 |
| 2 | 將 DefaultAuthorizationManagerFactory 釋出為 Bean,以便它全域性使用。 |
我們現在可以定義與 RequiredAuthoritiesAuthorizationManager 結合的授權規則。include-code::./RequiredAuthoritiesAuthorizationManagerConfiguration[tag=httpSecurity,indent=0] <1> 以 /admin/** 開頭的 URL 需要 ROLE_ADMIN。如果使用者名稱是 admin,則還需要 FACTOR_OTT 和 FACTOR_PASSWORD。 <2> 否則,請求必須經過認證。如果使用者名稱是 admin,則還需要 FACTOR_OTT 和 FACTOR_PASSWORD。
我們的示例使用使用者名稱到額外所需許可權的記憶體對映。對於可以透過使用者名稱確定的更動態的用例,可以建立 RequiredAuthoritiesRepository 的自定義實現。可能的示例包括查詢使用者是否在顯式設定中啟用了 MFA,確定使用者是否註冊了金鑰等。
對於需要根據 Authentication 確定 MFA 的情況,可以使用自定義 AuthorizationManger,如 程式設計式 MFA 中所示。
使用 hasAllAuthorities
我們展示了許多用於支援 MFA 的額外基礎設施。然而,對於簡單的 MFA 用例,使用 hasAllAuthorities 要求多個因素是有效的。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
(1)
.anyRequest().hasAllAuthorities(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
)
(2)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
(1)
authorize(anyRequest, hasAllAuthorities(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
))
}
(2)
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 對每個請求都要求 FACTOR_PASSWORD 和 FACTOR_OTT |
| 2 | 設定可以提供所需因素的認證機制。 |
上面的配置僅適用於最簡單的用例。如果有很多端點,您可能不想在每個授權規則中重複 MFA 的要求。
例如,考慮以下配置:
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
(1)
.requestMatchers("/admin/**").hasAllAuthorities(
"ROLE_ADMIN",
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
(2)
.anyRequest().hasAllAuthorities(
"ROLE_USER",
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
)
(3)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
(1)
authorize("/admin/**", hasAllAuthorities(
"ROLE_ADMIN",
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
))
(2)
authorize(anyRequest, hasAllAuthorities(
"ROLE_USER",
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
))
}
(3)
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 對於以 /admin/** 開頭的 URL,需要以下許可權:FACTOR_OTT、FACTOR_PASSWORD、ROLE_ADMIN。 |
| 2 | 對於所有其他 URL,需要以下許可權:FACTOR_OTT、FACTOR_PASSWORD、ROLE_USER。 |
| 3 | 設定可以提供所需因素的認證機制。 |
此配置只指定了兩個授權規則,但這足以看出重複是不合乎需要的。你能想象宣告數百個這樣的規則會是什麼樣子嗎?
更重要的是,表達更復雜的授權規則變得困難。例如,您如何要求兩個因素以及 ROLE_ADMIN 或 ROLE_USER?
這些問題的答案,正如我們已經看到的,是使用 [egmfa]
重新認證
其中最常見的是重新認證。設想一個按以下方式配置的應用程式:
-
Java
-
Kotlin
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
預設情況下,此應用程式允許兩種認證機制,這意味著使用者可以使用其中任何一種並完全認證。
如果有一組端點需要特定的因素,我們可以在 authorizeHttpRequests 中指定,如下所示:
-
Java
-
Kotlin
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/profile/**").hasAuthority(FactorGrantedAuthority.OTT_AUTHORITY) (1)
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
authorize("/profile/**", hasAuthority(FactorGrantedAuthority.OTT_AUTHORITY)) (1)
authorize(anyRequest, authenticated)
}
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | - 指出所有 /profile/** 端點都需要一次性令牌登入才能獲得授權。 |
在上述配置下,使用者可以使用您支援的任何機制登入。如果他們想訪問個人資料頁面,Spring Security 將重定向他們到一次性令牌登入頁面以獲取它。
透過這種方式,賦予使用者的許可權與提供的證明數量成正比。這種自適應方法允許使用者只提供執行其預期操作所需的證明。
授權更多範圍
您還可以配置異常處理,以指導 Spring Security 如何獲取缺失的範圍。
考慮一個應用程式,它對給定端點需要特定的 OAuth 2.0 範圍
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/profile/**").hasAuthority("SCOPE_profile:read")
.anyRequest().authenticated()
)
.x509(Customizer.withDefaults())
.oauth2Login(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
authorize("/profile/**", hasAuthority("SCOPE_profile:read"))
authorize(anyRequest, authenticated)
}
x509 { }
oauth2Login { }
}
return http.build()
}
如果這也配置了一個 AuthorizationManagerFactory bean,像這樣:
-
Java
-
Kotlin
@Bean
AuthorizationManagerFactory<Object> authz() {
return AuthorizationManagerFactories.multiFactor()
.requireFactors(FactorGrantedAuthority.X509_AUTHORITY, FactorGrantedAuthority.AUTHORIZATION_CODE_AUTHORITY)
.build();
}
@Bean
fun authz(): AuthorizationManagerFactory<Object> {
return AuthorizationManagerFactories.multiFactor<Object>()
.requireFactors(
FactorGrantedAuthority.X509_AUTHORITY,
FactorGrantedAuthority.AUTHORIZATION_CODE_AUTHORITY
)
.build()
}
那麼應用程式將需要 X.509 證書以及來自 OAuth 2.0 授權伺服器的授權。
如果使用者不同意 profile:read,此應用程式目前將發出 403 錯誤。但是,如果您有一種方法讓應用程式重新請求同意,那麼您可以在一個 AuthenticationEntryPoint 中實現這一點,如下所示:
-
Java
-
Kotlin
@Component
class ScopeRetrievingAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
response.sendRedirect("https://authz.example.org/authorize?scope=profile:read");
}
}
@Component
internal class ScopeRetrievingAuthenticationEntryPoint : AuthenticationEntryPoint {
override fun commence(request: HttpServletRequest, response: HttpServletResponse, authException: AuthenticationException) {
response.sendRedirect("https://authz.example.org/authorize?scope=profile:read")
}
}
然後,您的過濾器鏈宣告可以將此入口點繫結到給定的許可權,如下所示:
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, ScopeRetrievingAuthenticationEntryPoint oauth2) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/profile/**").hasAuthority("SCOPE_profile:read")
.anyRequest().authenticated()
)
.x509(Customizer.withDefaults())
.oauth2Login(Customizer.withDefaults())
.exceptionHandling((exceptions) -> exceptions
.defaultDeniedHandlerForMissingAuthority(oauth2, "SCOPE_profile:read")
);
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity, oauth2: ScopeRetrievingAuthenticationEntryPoint): DefaultSecurityFilterChain? {
http {
authorizeHttpRequests {
authorize("/profile/**", hasAuthority("SCOPE_profile:read"))
authorize(anyRequest, authenticated)
}
x509 { }
oauth2Login { }
}
http.exceptionHandling { e: ExceptionHandlingConfigurer<HttpSecurity> -> e
.defaultDeniedHandlerForMissingAuthority(oauth2, "SCOPE_profile:read")
}
return http.build()
}