一、主要pom依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<!-- 使用redis存储session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
二、application root配置
@SpringBootApplication
//开启webflux
@EnableWebFlux
//使用redis存储session信息
@EnableRedisWebSession
public class AuthorityParentApplication {
public static void main(String[] args) {
Hooks.onOperatorDebug();
SpringApplication.run(AuthorityParentApplication.class, args);
}
}
三、权限校验配置
先把主要的配置类信息分别说一下,后面会有组合后的configuration类,类名为:SecurityConfig
1、过滤器配置
webflux security配置调用链信息的主类为:ServerHttpSecurity,里面有OAuth2,formlogin等登陆方式的默认配置
管理系统的话我们启用FormLoginSpec即刻,就是传统的cookie配session的登陆验证机制
/**
* 此处的代码会放在SecurityConfig类中,此处只是摘要下,里面的handler在下文会详细介绍
* @param http
* @return
*/
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
ServerHttpSecurity.FormLoginSpec formLoginSpec = http.formLogin();
formLoginSpec.authenticationSuccessHandler(createAuthenticationSuccessHandler())
.loginPage("/login")
.authenticationFailureHandler(createAuthenticationFailureHandler());
return formLoginSpec.and()
.csrf().disable()
.httpBasic().disable()
.authorizeExchange()
.pathMatchers(AUTH_WHITELIST).permitAll()
.anyExchange().authenticated()
.and().build();
}
最后调用ServerHttpSecurity的build方法,从源码中可以查看构造的filter调用链,及配置信息等
public SecurityWebFilterChain build() {
if (this.built != null) {
throw new IllegalStateException("This has already been built with the following stacktrace. " + buildToString());
}
this.built = new RuntimeException("First Build Invocation").fillInStackTrace();
if (this.headers != null) {
this.headers.configure(this);
}
WebFilter securityContextRepositoryWebFilter = securityContextRepositoryWebFilter();
if (securityContextRepositoryWebFilter != null) {
this.webFilters.add(securityContextRepositoryWebFilter);
}
if (this.httpsRedirectSpec != null) {
this.httpsRedirectSpec.configure(this);
}
if (this.csrf != null) {
this.csrf.configure(this);
}
if (this.cors != null) {
this.cors.configure(this);
}
if (this.httpBasic != null) {
this.httpBasic.authenticationManager(this.authenticationManager);
this.httpBasic.configure(this);
}
if (this.formLogin != null) {
this.formLogin.authenticationManager(this.authenticationManager);
if (this.securityContextRepository != null) {
this.formLogin.securityContextRepository(this.securityContextRepository);
}
this.formLogin.configure(this);
}
if (this.oauth2Login != null) {
this.oauth2Login.configure(this);
}
if (this.resourceServer != null) {
this.resourceServer.configure(this);
}
if (this.client != null) {
this.client.configure(this);
}
this.loginPage.configure(this);
if (this.logout != null) {
this.logout.configure(this);
}
this.requestCache.configure(this);
this.addFilterAt(new SecurityContextServerWebExchangeWebFilter(), SecurityWebFiltersOrder.SECURITY_CONTEXT_SERVER_WEB_EXCHANGE);
if (this.authorizeExchange != null) {
ServerAuthenticationEntryPoint authenticationEntryPoint = getAuthenticationEntryPoint();
ExceptionTranslationWebFilter exceptionTranslationWebFilter = new ExceptionTranslationWebFilter();
if (authenticationEntryPoint != null) {
exceptionTranslationWebFilter.setAuthenticationEntryPoint(
authenticationEntryPoint);
}
ServerAccessDeniedHandler accessDeniedHandler = getAccessDeniedHandler();
if (accessDeniedHandler != null) {
exceptionTranslationWebFilter.setAccessDeniedHandler(
accessDeniedHandler);
}
this.addFilterAt(exceptionTranslationWebFilter, SecurityWebFiltersOrder.EXCEPTION_TRANSLATION);
this.authorizeExchange.configure(this);
}
AnnotationAwareOrderComparator.sort(this.webFilters);
List<WebFilter> sortedWebFilters = new ArrayList<>();
this.webFilters.forEach( f -> {
if (f instanceof OrderedWebFilter) {
f = ((OrderedWebFilter) f).webFilter;
}
sortedWebFilters.add(f);
});
//sortedWebFilters中保存了登陆权限校验的所有filter信息
sortedWebFilters.add(0, new ServerWebExchangeReactorContextWebFilter());
return new MatcherSecurityWebFilterChain(getSecurityMatcher(), sortedWebFilters);
}
2、自定义用户信息获取方式
获取用户信息的接口定义是:ReactiveUserDetailsService,官网的实现是:MapReactiveUserDetailsService,给出demo中是代码写死用户名密码、角色,并用此类存储。但实际中更多的从数据库中获取配置的用户角色信息,所以我们实现ReactiveUserDetailsService接口,自定义从mysql中获取用户信息的类,代码如下:
import com.sisheng.authority.common.enums.ErrorEnum;
import com.sisheng.authority.common.exception.BusinessException;
import com.sisheng.authority.repository.user.entity.SystemUser;
import com.sisheng.authority.servi