Spring Security Reactive
- RBAC权限模型
- WebFlux配置:@EnableWebFluxSecurity、@EnableReactiveMethodSecurity
- SecurityFilterChain 组件
- AuthenticationManager 组件
- UserDetailsService 组件
- 基于注解的方法级别授权
整合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <dependencies> <dependency> <groupId>io.asyncer</groupId> <artifactId>r2dbc-mysql</artifactId> <version>1.0.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
|
开发
应用安全
防止攻击:
控制权限
- 登录的用户能干什么。
- 用户登录系统以后要控制住用户的所有行为,防止越权;
传输加密
认证:
RBAC权限模型
Role Based Access Controll: 基于角色的访问控制
一个网站有很多用户,每个用户可以有很多角色,一个角色可以关联很多权限。一个人到底能干什么?
权限控制:
- 找到这个人,看他有哪些角色,每个角色能拥有哪些权限。 这个人就拥有一堆的 角色 或者 权限
- 这个人执行方法的时候,我们给方法规定好权限,由权限框架负责判断,这个人是否有指定的权限
所有权限框架:
- 让用户登录进来: 认证(authenticate):用账号密码、各种其他方式,先让用户进来
- 查询用户拥有的所有角色和权限: 授权(authorize): 每个方法执行的时候,匹配角色或者权限来判定用户是否可以执行这个方法
认证
登录行为
静态资源放行
其他请求需要登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package com.yuanyuan.security.config;
import blog.yuanyuan.security.component.AppReactiveUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.reactive.PathRequest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration @EnableReactiveMethodSecurity public class AppSecurityConfiguration {
@Autowired ReactiveUserDetailsService appReactiveUserDetailsService;
@Bean SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.authorizeExchange(authorize -> { authorize.matchers(PathRequest.toStaticResources() .atCommonLocations()).permitAll();
authorize.anyExchange().authenticated(); });
http.formLogin(formLoginSpec -> {
});
http.csrf(csrfSpec -> { csrfSpec.disable(); });
http.authenticationManager( new UserDetailsRepositoryReactiveAuthenticationManager( appReactiveUserDetailsService) );
return http.build(); }
@Primary @Bean PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); return encoder; } }
|

这个界面点击登录,最终Spring Security 框架会使用 ReactiveUserDetailsService 组件,按照 表单提交的用户名 去数据库查询这个用户详情(基本信息[账号、密码],角色,权限)。把数据库中返回的 用户详情 中的密码 和 表单提交的密码进行比对。比对成功则登录成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package blog.yuanyuan.security.component;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono;
@Component public class AppReactiveUserDetailsService implements ReactiveUserDetailsService {
@Autowired DatabaseClient databaseClient;
@Autowired PasswordEncoder passwordEncoder; @Override public Mono<UserDetails> findByUsername(String username) {
Mono<UserDetails> userDetailsMono = databaseClient.sql("select u.*,r.id rid,r.name,r.value,pm.id pid,pm.value pvalue,pm.description " + "from t_user u " + "left join t_user_role ur on ur.user_id=u.id " + "left join t_roles r on r.id = ur.role_id " + "left join t_role_perm rp on rp.role_id=r.id " + "left join t_perm pm on rp.perm_id=pm.id " + "where u.username = ? limit 1") .bind(0, username) .fetch() .one() .map(map -> { UserDetails details = User.builder() .username(username) .password(map.get("password").toString())
.roles("admin", "sale","haha","delete") .build();
return details; });
return userDetailsMono; } }
|
授权
@EnableReactiveMethodSecurity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package blog.yuanyuan.security.controller;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono;
@RestController public class HelloController {
@PreAuthorize("hasRole('admin')") @GetMapping("/hello") public Mono<String> hello(){
return Mono.just("hello world!"); }
@PreAuthorize("hasRole('delete')") @GetMapping("/world") public Mono<String> world(){ return Mono.just("world!!!"); } }
|
配置: SecurityWebFilterChain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| package blog.yuanyuan.security.config;
import blog.yuanyuan.security.component.AppReactiveUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.reactive.PathRequest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration @EnableReactiveMethodSecurity public class AppSecurityConfiguration {
@Autowired ReactiveUserDetailsService appReactiveUserDetailsService;
@Bean SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.authorizeExchange(authorize -> { authorize.matchers(PathRequest.toStaticResources() .atCommonLocations()).permitAll(); authorize.anyExchange().authenticated(); });
http.formLogin(formLoginSpec -> {
});
http.csrf(csrfSpec -> { csrfSpec.disable(); });
http.authenticationManager( new UserDetailsRepositoryReactiveAuthenticationManager( appReactiveUserDetailsService) );
return http.build(); }
@Primary @Bean PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); return encoder; } }
|