目标:
SpringBoot3+Webflux+ Spring Data R2dbc + Spring Security
导入相关依赖
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<r2dbc-mysql.version>1.0.5</r2dbc-mysql.version>
</properties>
<dependencies>
<dependency>
<groupId>io.asyncer</groupId>
<artifactId>r2dbc-mysql</artifactId>
<version>${r2dbc-mysql.version}</version>
</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-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
防止攻击:
DDos,CSRF,XSSSQL注入
控制权限:
登录用户能干什么
用户登录系统以后要控制用户所有的行为,防止越权
传输加密:
https
X509
认证:
OAuth2.0
JWT
Role Based Access Control:基于角色的访问控制
一个网站有很多用户
每个用户可以有很多角色
一个角色可以关联很多权限
一个人能干什么?(权限控制)
找到这个人,看他有哪些角色,每个角色拥有哪些权限
这个人就拥有一堆的角色或者权限
这个人在执行方法时,给方法规定好权限
由权限框架负责判断这个人是否有指定的权限
所有权限框架:
1.让用户登录进来(认证:各种方式让用户进来)
2.查询用户拥有的所有角色和权限(授权:每个方法执行的时候,匹配角色或者权限来判定用户是否可以执行这个方法)
导入spring security默认行为:所有请求都需要登录才能访问
1.SecurityAutoConfiguration:
导入SecurityFilterChain组件:默认所有请求都需要登录(之前)
2.SecurityFilterAutoConfiguration
3.ReactiveSecurityAutoConfiguration:
导入ServerHttpSecurityConfiguration配置:
注解导入ServerHttpSecurityConfiguration
4.MethodSecurityAspectJAutoProxyRegistrar
controller
@RestController
@RequestMapping
public class HelloController {
@GetMapping("/sayHello")
public Mono<String> sayHello() {
return Mono.just("hello,world!");
}
}
默认效果:
登录行为:
1.静态资源放行
2.其他请求需要登录
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
//1.定义哪些请求需要认证,哪些请求不需要
http.authorizeExchange(authorize -> {
//1.1.允许所有人访问静态资源
authorize.matchers(PathRequest.toStaticResources().atCommonLocations())
.permitAll();
//1.2.剩下的所有请求都需要认证(登录)
authorize.anyExchange().authenticated();
});
//2.开启默认的表单登录
http.formLogin();
//3.安全控制
http.csrf(ServerHttpSecurity.CsrfSpec::disable);
//构建出安全配置
return http.build();
}
}
目前认证:
用户名user,密码是默认生成的
期望:去数据库查
这个界面点击登录,最终spring security框架会按照ReactiveUserDetailservice组件,
按照表单提交的用户名,去数据库查询用户详情
基本信息:账号密码,角色,权限
把数据库中返回的用户详情中密码和表单提交的密码进行比较
比较成功则登录成功
原始sql
SELECT user.username,user.password,user_role.role_id,roles.name,perm.uri
FROM index_demo.t_user user
LEFT JOIN t_user_role user_role ON user.id = user_role.user_id
LEFT JOIN index_demo.t_roles roles ON user_role.role_id = roles.id
LEFT JOIN index_demo.t_role_perm role_perm ON role_perm.role_id = roles.id
LEFT JOIN index_demo.t_perm perm ON role_perm.perm_id = perm.id
WHERE user.username = 'zyl';
配置认证规则:如何去数据库中查询用户
配置默认密码加密器
@Configuration
public class SecurityConfiguration {
@Autowired
private ReactiveUserDetailsService reactiveUserDetailsService;
@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
//1.定义哪些请求需要认证,哪些请求不需要
http.authorizeExchange(authorize -> {
//1.1.允许所有人访问静态资源
authorize.matchers(PathRequest.toStaticResources().atCommonLocations())
.permitAll();
//1.2.剩下的所有请求都需要认证(登录)
authorize.anyExchange().authenticated();
});
//2.开启默认的表单登录
http.formLogin();
//3.安全控制
http.csrf(ServerHttpSecurity.CsrfSpec::disable);
//4.配置认证规则:如何去数据库中查询用户
//
http.authenticationManager(
new UserDetailsRepositoryReactiveAuthenticationManager(reactiveUserDetailsService)
);
//构建出安全配置
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder () {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
自定义登录校验
@Service
public class MyReactiveUserDetailService implements ReactiveUserDetailsService {
@Autowired
private DatabaseClient databaseClient;
/**
* 自定义如何按照用户名去数据库查用户信息
*/
@Override
public Mono<UserDetails> findByUsername(String username) {
return databaseClient.sql("SELECT user.username,user.password,user_role.role_id,roles.name,perm.uri " +
"FROM index_demo.t_user user " +
"LEFT JOIN index_demo.t_user_role user_role ON user.id = user_role.user_id " +
"LEFT JOIN index_demo.t_roles roles ON user_role.role_id = roles.id " +
"LEFT JOIN index_demo.t_role_perm role_perm ON role_perm.role_id = roles.id " +
"LEFT JOIN index_demo.t_perm perm ON role_perm.perm_id = perm.id " +
"WHERE user.username = ?")
.bind(0,username)
.fetch()
.first()
.map(rowMap ->
User.builder()
.username(String.valueOf(rowMap.get("username")))
.password(String.valueOf(rowMap.get("password")))
.authorities(String.valueOf(rowMap.get("uri")))//权限
.roles(String.valueOf(rowMap.get("name")))
.build()
);
}
}
角色和权限都被封装成SimpleGrantedauthority
@RestController
@RequestMapping
public class HelloController {
@PreAuthorize("hasRole('admin')")
@GetMapping("/hello")
public Mono<String> sayHello() {
return Mono.just("hello,world!");
}
@PreAuthorize("hasAuthority('list')")
@GetMapping("/word")
public Mono<String> word() {
return Mono.just("word!");
}
}
对应权限: