在〈簡介 WebFlux〉中,曾經引用 Spring 官方網站的技術堆疊示意圖,其中看到在 Reactive Stack 中,Spring Security 提供了 Reactive 的版本,想在 WebFlux 使用 Security Reactive 的話,除了使用 Reactive Web 的 Starter 之外,還要加上 Security 的 Starter。
預設只要有 Security 的話,就會啟用頁面防護,與〈Security 設置〉中談到的一樣,預設的使用者名稱為 user
,密碼為隨機產生,可以自行設置在 application.properties 中設定使用者名稱與密碼。
如果要設定特定頁面防護,可以透過 ServerHttpSecurity
,這是個類似 HttpSecurity
的物件,你可以透過它設定防護條件,然後建立一個 SecurityWebFilterChain
:
@Bean
public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/member.html").hasRole("MEMBER")
.pathMatchers("/admin.html").hasRole("ADMIN")
.anyExchange().permitAll()
.and()
.formLogin()
.and().build();
}
在上面的設定中,請求 member.html、admin.html 會需要驗證,角色各必須是 MEMBER
、ADMIN
,其他頁面則不需要防護,從 SecurityWebFilterChain
可以看到,Reactive 版本的 Security 基本上也是由過濾器來處理,不過並不是 Servlet API 的 Filter
,而是 Spring 本身的 org.springframework.web.server.WebFilter
。
若要設置使用者、密碼、角色資訊呢?在〈UserDetailsService〉談過,可以自訂 UserDetailsService
來自訂如何取得 UserDetails
,以進一步供做驗證時使用,在 Reactive 版本的 Security 中,則是透過實作 ReactiveUserDetailsService
來處理。例如:
@Bean
public ReactiveUserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails admin = User
.withUsername("admin")
.password(passwordEncoder.encode("admin12345678"))
.roles("ADMIN", "MEMBER")
.build();
UserDetails caterpillar = User
.withUsername("caterpillar")
.password(passwordEncoder.encode("12345678"))
.roles("MEMBER")
.build();
return new MapReactiveUserDetailsService(admin, caterpillar);
}
MapReactiveUserDetailsService
是個 ReactiveUserDetailsService
的實作類別,建構時可以指定多個 UserDetails
實例,在這邊借由它來實現一個記憶體中的驗證資料來源,為了提供 PasswordEncoder
實作,別忘了加上:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
當然,你也可以實現一個 ReactiveUserDetailsService
,從資料庫中取得 UserDetails
,資料庫在 Reactive 的支援上,之後的文件再來討論。
你也許會想起〈AuthenticationProvider〉,在 Reactive 這方面有對應的版本嗎?ServerHttpSecurity
有個 authenticationManager
方法,可以接受ReactiveAuthenticationManager
實例,它有個方法必須實作:
Mono<Authentication> authenticate(Authentication authentication)
也就是說,如果你有自己的驗證實作方式,可以自行定義 ReactiveAuthenticationManager
,例如,純綷只是示範 API 的運用,故意將上頭的記憶體驗證來源,改用 ReactiveAuthenticationManager
實現的話會是:
@Bean
public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) {
return http
.authenticationManager(auth -> {
String name = auth.getName();
String password = auth.getCredentials().toString();
if(name.equals("caterpillar") && password.equals("12345678")) {
return Mono.just(
new UsernamePasswordAuthenticationToken("caterpillar", "12345678",
AuthorityUtils.createAuthorityList("ROLE_MEMBER")
)
);
}
if(name.equals("admin") && password.equals("admin12345678")) {
return Mono.just(
new UsernamePasswordAuthenticationToken("admin", "admin12345678",
AuthorityUtils.createAuthorityList("ROLE_MEMBER", "ROLE_ADMIN")
)
);
}
return null;
})
.authorizeExchange()
.pathMatchers("/member.html").hasRole("MEMBER")
.pathMatchers("/admin.html").hasRole("ADMIN")
.anyExchange().permitAll()
.and()
.formLogin()
.and().build();
}
你可以在 SecurityReactive 找到以上的範例專案。