SpringSecurity使用记录
初体验
引入依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
spring-security会自动添加一个登陆页面
- 用户 user
- 密码 会在控制台打印
springSecurity基本原理
spring-security本质上是过滤器链
FilterSecurityInterceptor 是一个方法级的过滤器
1 | //过滤器体 |
ExceptionTranslationFilter异常转换过滤器
处理权限认证过程种抛出的异常
UsernamePasswordAuthenticationFilter 判断用户post请求种的用户名和密码

基本
不使用springboot配置security
DelegatingFilterProxy
自定义用户名和密码匹配获取权限
获取用户数据
在配置文件中配置用户名和密码
1
2
3
4
5spring:
security:
user:
name: admin
password: admin通过配置类设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24//继承 WebSecurityConfigurerAdapter
// 设置的密码必须加密,使用passwordEncoder相同的加密器
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode("123");
auth.inMemoryAuthentication().withUser("wangzhe")
.password(encode)
.roles("admin");
}
//必须设置加密器
//exception:id null...
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}配置类设置的密码必须加密,必须设置passwordEncoder
通过数据库查找来获取用户名和密码,需要实现UserDetailService 接口
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
public class SecurityConfig extends WebSecurityConfigurerAdapter {
MyUserDetailService myUserDetailService;
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailService)
.passwordEncoder(passwordEncoder());
}
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
public class MyUserDetailService implements UserDetailsService {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("roles");
return new User("xiaomi", new BCryptPasswordEncoder().encode("123"),authorities);
}
}
自定义登录页面
登录页面必须以post请求,表单的name必须设置为username,password。
必须设置登录页面放行策略
1
2
3
4
5
6
7
8
9
10
11
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") // 登录页面
.loginProcessingUrl("/user/login") //表单登录按钮路径
.defaultSuccessUrl("/test/index") //登录成功的默认跳转路径
.and().authorizeHttpRequests()
.antMatchers("/hello","/user/login","/login.html").permitAll() //放行路径,需要添加登录页面到放行路径中
.anyRequest().authenticated()
.and().csrf().disable();//关闭csrf 防护
}
用户授权
> 单权限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") // 登录页面
.loginProcessingUrl("/user/login") //表单登录按钮路径
.defaultSuccessUrl("/test/index") //登录成功的默认跳转路径
.and().authorizeHttpRequests()
.antMatchers("/hello","/user/login","/login.html").permitAll() //放行路径,需要添加登录页面到放行路径中
.antMatchers("/test/authority").hasAuthority("admin") //添加权限校验
.antMatchers("/test/authorities").hasAnyAuthority("admin,girl")
.anyRequest().authenticated()
.and().csrf().disable();//关闭csrf 防护
}
> 多权限
1
2
.antMatchers("/test/authorities").hasAnyAuthority("admin","girl")
**权限不足**
1
2
3
4
5
6
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Apr 15 16:07:39 CST 2022
There was an unexpected error (type=Forbidden, status=403).
Forbidden
用户角色分配
> 分配权限是 字符串需要在前面拼接 `ROLE_`
1
2
3
4
//单角色分配
.antMatchers("/test/authority").hasRole("producer")
.antMatchers("/test/authorities").hasAnyRole("producer","consumer")
自定义403 错误页面
security配置类中添加
1 | http.exceptionHandling().accessDeniedPage("/unauth.html"); |
注解使用
@Secured
指定访问目标 所需的 角色
注解可以被用在方法上,controller或者service层都可以
开启注解使用
1 | //在启动类或者是配置类上添加 启动注解 |
访问目标上添加角色校验
1 |
|
@PreAuthorize
可以在进入方法前 进行校验
开启注解支持
1 |
在访问目标上添加角色or权限校验
1 |
|
@PostAuthorize
在方法执行后校验
开启注解支持
1 |
在访问目标上添加角色or权限校验
1 |
|
@PostFilter
对方法的返回结果进行过滤
在访问目标上添加结果过滤
1 | //filterObject 是返回List中的单个对象,可以使用方法,或者是.属性 |
@PreFilter
对方法的入参进行校验
用户注销
security配置
登出路径和登出成功页面1
2http.logout().logoutUrl("/logout").logoutSuccessUrl("/logoutSuccess.html");
需要放行logoutSuccess.html页面,但是只能有一个permitALL
自动登录
创建token-userinfo 表
建表语句在
org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl类中1
2
3
4
5CREATE TABLE persistent_logins (
username VARCHAR ( 64 ) NOT NULL,
series VARCHAR ( 64 ) PRIMARY KEY,
token VARCHAR ( 64 ) NOT NULL,
last_used TIMESTAMP NOT NULL)配置数据源
1
2
3
4
5
6
7
8
9
10
11
12//配置 PersistentTokenRepository 的数据源
//在配置类中添加
DataSource dataSource;
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}在
configure(HttpSecurity http)中配置自动登录的数据库,token持续时间,userservice1
2
3http.and().rememberMe().tokenRepository(persistentTokenRepository())//设置自动登录,数据库操作对象
.tokenValiditySeconds(60) //设置token的有效时间,单位秒
.userDetailsService(userDetailsService())在登录页面添加复选框:十天免登录
1
<input type="checkbox" name="remember-me"> 5分钟之内免登录
测试结果查看,客户端的cookie,数据库中的数据
1
2数据库信息
admin A1m2uLrSTC3CRQ9IRzyIgQ== Ieotk5N33+/5I6MNODR3gQ== 2022-05-13 09:44:44客户端cookie信息

CSRF
CSRF简介
1 | CSRF(Cross-site request forgery)跨站请求伪造,也被称为"One Click Attack"或者Session Riding,通常缩写为CSRF或者 |
SpringSecurity 4之后
默认开启CSRF保护,会针对POST,PUT,PATCH,DELETE这些请求进行保护。
开启后,只能在同站进行访问,跨站是不能访问的。
1 | //关闭CSRF |
csrf的实现在过滤器中
org.springframework.security.web.csrf.CsrfFilterspringSecurity 中的csrf功能时在这个过滤器中
开启csrf校验
- 在配置类中开启CSRF校验
- 需要在请求的时候传递_csrf的token
单点登录
补充
登录成功后的行为
- defaultSuccessUrl,successForwardUrl
1 | http.formLogin().defaultSuccessUrl("/index",true).successForwardUrl("/index") |
- http.successHandler(AuthenticationSuccessHandler successHandler)
1 | http.successHandler(AuthenticationSuccessHandler successHandler); |
登录失败后的行为
- ```java
.failureUrl(“/failure”) //跳转登录失败页面1
2
3
4
5
6
7
8
9
10
2. ```java
//登录失败,处理方法,前后端分离时使用
.failureHandler((req, resp, e) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(e.getMessage());
out.flush();
out.close();
});
未认证时访问其他页面是的行为
1 | //这里主要是针对,前后端分离的情况,后台没有办法重定向。 |
注销登录后返回json数据
1 | http.logout() .logoutUrl("/logout") |
配置用户的方式
配置在内存中
1
2
3
4
5
6
7
8
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("aoa")
.password(passwordEncoder()
.encode("123"))
.roles("admin");
}配置UserDetailsService
1
2
3
4
5
6
7
8
9
10
11
12
13
14//1. 重写方法
protected UserDetailsService userDetailsService() {
//在这里创建
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("bin").password(passwordEncoder().encode("123")).roles("bin").build());
inMemoryUserDetailsManager.createUser(User.withUsername("sofm").password(passwordEncoder().encode("123")).roles("sofm").build());
return inMemoryUserDetailsManager;
}
//2. 在configure(auth)中配置
auth.userDetailsService((userName)-> {
return null;
});
权限继承
1 | //新版本可能配置的字符串中使用 \n 分割 |

