Spring Security

Spring Security (spring.io)

Spring Security核心功能

关于安全方面的两个主要区域是认证和授权(或者访问控制),这两点也是Spring Security核心功能

  • 用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
  • 用户授权:验证谋而用户是否有权限执行某一操作。在一个系统中,不同用户所具有的权限是不同的。

SpringSecurity特点

  • 和Spring无缝整合。

  • 全面的权限控制。

  • 专门为Web开发而设计。

    • 旧版本不能脱离Web环境使用。

    • 新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入核心模块就可以脱离Web环境。

  • 重量级。

Shiro

Apache旗下的轻量级权限控制框架。
特点:

  • 轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
  • 通用性。
  • 好处:不局限于Web环境,可以脱离Web环境使用。
  • 缺陷:在Web环境下一些特定的需求需要手动编写代码定制。

和Shiro的区别

Shiro - 轻量级

SpringSecurity - 重量级

相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有 Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。

自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。 因此,一般来说,常见的安全管理技术栈的组合是这样的:

  • SSM + Shiro

  • Spring Boot/Spring Cloud + Spring Security

    以上只是一个推荐的组合而已,如果单纯从技术上来说,无论怎么组合,都是可以运行的

测试security

导入对应security坐标和web坐标

1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

编写controller类

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/test")
public class TestController {

@GetMapping("hello")
public String hello(){
return "hello security";
}
}

访问页面

设置端口为8080

启动springboot项目访问页面链接会进入到security的拦截界面不能直接访问hello

登录拦截

需要进行登录

默认的用户名:user
密码在项目启动的时候在控制台会打印,注意每次启动的时候密码都回发生变化!

登录完成才能访问到对应的信息

SpringSecurity 基本原理

SpringSecurity 本质是一个过滤器链:

在启动springboot项目的时候可以看见一整条过滤器链(每个过滤器按顺序执行完成后才能正常启动)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor

知识补充

过滤器 和 拦截器的 6个区别,别再傻傻分不清了_程序员小富的博客-CSDN博客_过滤器和拦截器

过滤器和拦截器的区别_至今没搞明白的博客-CSDN博客_过滤器和拦截器的区别

过滤器(Filter)和拦截器(Interceptor)

Spring 的拦截器与 Servlet 的Filter 有相似之处,比如二者都是 AOP编程思想的体现, 都能实现权限检查、日志记录等。不同的是:

  • 使用范围不同:Filter 是 Servlet 规范规定的,只能用于 Web 程序中。而拦截器既 可以用于 web程序,也可以用于 Application、Swing 程序中。
  • 口 规范不同:Filter 是在 Servlet 规范中定义的,是Serviet 容器支持的。而拦截器是 在 Spring 容器内的,是 Spring 框架支特的。
  • 使用的资源不同:同其他的代码块一样,拦截器也是一个Spring 的组件,归 Spring 管理,配置在 Spring 文件中,因此能使用 Spring 里的任何资源、对象,例如 Sersice 对象、数据源、事务管理等,通过1oC 注入到拦截器即可:而Filter 则不能。
  • 深度不同:Filter 在只在 Servlet 前后起作用。而拦截器能够深入到方法前后、异常 抛出前后等,因此拦我器的使用具有更大的弹性。所以在 Spring 构架的程序中, 要优先使用拦截器.

1、实现原理不同
过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的。
2、使用范围不同
我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
3、触发时机不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。

拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
4、拦截的请求范围不同
过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。
5、注入Bean情况不同
这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。
6、控制执行顺序不同
过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。
拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行

三个重要的过滤器

  • FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。

拦截器

super.beforeInvocation(fi) 表示查看之前的filter 是否通过。
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务

  • ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常

    拦截器

  • UsernamePasswordAuthenticationFilter :对/login的POST请求做拦截,校验表单中用户名,密码。

拦截器

两个重要接口

UserDetailsService接口讲解

当什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。

继承接口实现从数据库匹配用户账号密码

如果需要自定义逻辑时,只需要实现UserDetailsService接口即可

返回值是UserDetail

这个类是系统默认的用户“主体” :包含了用户在数据库字段的各种信息

PasswordEncoder 接口讲解

1
2
3
4
5
6
7
8
// 表示把参数按照特定的解析规则进行解析 
String encode(CharSequence rawPassword);
// 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回true;如果不匹配,则返回false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
boolean matches(CharSequence rawPassword, String encodedPassword);
// 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回true,否则返回false。默认返回false。
default boolean upgradeEncoding(String encodedPassword) {
return false;
}

web权限方案

  • 认证
  • 授权

设置登录的用户名和密码

1.通过配置application文件

1
2
spring.security.user.name=02
spring.security.user.password=nian0209

使用配置的账号密码可以登录

2.通过配置类

  • 直接创建BCryptPasswordEncoder对象在访问的时候会出现对象匹配不上的问题报错无法访问
  • 有两种解决办法
    • 第一种:返回一个PasswordEncoder对象将其注入到spring容器中
    • 修改代码:auth.inMemoryAuthentication().passwordEncoder(passwordEncoder).withUser(“02”).password(password).roles(“admin”);

在Spring Security5.7版本中将弃用WebSecurityConfigurerAdapter接口类

Spring Security 弃用WebSecurityConfigurerAdapter_鲨雕东西的博客-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//第一种
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//将密码进行加密
String password = passwordEncoder.encode("nian0209");
//将用户信息设置到内存中去
auth.inMemoryAuthentication().withUser("02").password(password).roles("admin");
// 第二种 auth.inMemoryAuthentication().passwordEncoder(passwordEncoder).withUser("02").password(password).roles("admin");

}

}

3.自定义编写实现类—–继承UserDetailsService

这里并没有连接数据库测试,没有对应的用户账号密码匹配,只是模拟了用户登录,而且只有密码匹配规则,所以在登录的时候,只要密码输入正确即可登录

1
2
3
4
5
6
7
8
9
10
@Service("UserDetailsService")
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//借助AuthorityUtils职责工具类生成角色
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
//返回一个对象信息
return new User("02",new BCryptPasswordEncoder().encode("nian0209"),auths);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//设置自己写的实现类并设置密码编码
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}

}

配置数据库访问

配置数据库访问相关坐标

1
2
3
4
5
6
7
8
9
10
11
12
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>

配置数据源

1
2
3
4
5
6
server.port=8080
#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=?

实体类

1
2
3
4
5
6
@Data
public class Users {
private Integer id;
private String username;
private String password;
}

mapper接口

1
2
3
4
@Mapper
public interface UserMapper extends BaseMapper<Users> {

}

service层

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
@Service("UserDetailsService")
public class MyUserDetailService implements UserDetailsService {

@Autowired
private UserMapper userMapper;

//根据用户名这个参数来进行条件判断,匹配对应的账户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

//调用userMapper,根据用户名查询数据库
QueryWrapper<Users> wrapper = new QueryWrapper<>();
//where username = ?
wrapper.eq("username",username);
Users users = userMapper.selectOne(wrapper);
//判断数据库理有没有这个用户
if (users == null){ //没有这个用户,认证失败
throw new UsernameNotFoundException("用户名不存在!");
}
//借助AuthorityUtils职责工具类生成角色
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
//从数据库查询的结果来返回user对象,得到对应的用户名和密码
return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}
}

继承WebSecurityConfigurerAdapter类重写configure方法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}

/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//设置自己写的实现类并设置密码编码
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}

}

BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时多使用这个解析器。
BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认10.

设置自定义页面

编写自定义页面连接方法

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
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//设置自己写的实现类并设置密码编码
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}

//使用HttpSecurity参数,来选择自定义的登录页面设置
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义自己编写的登录页面
http.formLogin()
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/test/index").permitAll() //登录成功之后跳转的路径
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
}

编写登录页面

用户名和密码的name必须和实体类中定义的一样

1
2
3
4
5
6
7
<form action="/user/login"  method="post">
用户名:<input type="text" name="username">
<br/>
密码: <input type="text" name="password">
<br/>
<input type="submit" value="login">
</form>

基于角色或权限进行访问控制

hasAuthority 方法

如果当前的主体具有指定的权限,则返回 true,否则返回false

在配置类中设置有哪些权限可以访问当前访问地址

.antMatchers(“/test/index”).hasAnyAuthority(“admins”)//设置权限为admins才问/test/index

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//使用HttpSecurity参数,来选择自定义的登录页面设置
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义自己编写的登录页面
http.formLogin()
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/test/index").permitAll() //登录成功之后跳转的路径
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问
.antMatchers("/test/index").hasAuthority("admins")//设置权限为admins才问/test/index
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
在UserDetailService,把返回User对象设置权限

AuthorityUtils.commaSeparatedStringToAuthorityList(“admins”)设置权限角色为:admins

1
2
//借助AuthorityUtils职责工具类将返回的用户设置角色
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");

hasAnyAuthority方法

如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true.

一个用户拥有两个角色且其中一个角色能访问这个地址,就可以访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//使用HttpSecurity参数,来选择自定义的登录页面设置
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义自己编写的登录页面
http.formLogin()
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/test/index").permitAll() //登录成功之后跳转的路径
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问
//设置拥有权限manager,admins其中一个就能问/test/index
.antMatchers("/test/index").hasAnyAuthority("admins","manager")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}

hasRole 方法

如果用户具备给定角色就允许访问,否则出现403。
如果当前主体具有指定的角色,则返回true。

给用户设置多个角色

设置角色名前带ROLE_前缀

1
2
//设置权限和角色
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");

设置访问权限

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义自己编写的登录页面
http.formLogin()
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/test/index").permitAll() //登录成功之后跳转的路径
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问
.antMatchers("/test/index").hasRole("admins")//用户具备admins角色才能访问
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}

hasAnyRole

表示用户具备任何一个条件都可以访问。

设置可访问的角色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//使用HttpSecurity参数,来选择自定义的登录页面设置
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义自己编写的登录页面
http.formLogin()
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/test/index").permitAll() //登录成功之后跳转的路径
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问
//.antMatchers("/test/index").hasAuthority("admins")//设置权限为admins才问/test/index
//设置拥有权限manager,admins其中一个就能问/test/index
//.antMatchers("/test/index").hasAnyAuthority("admins","manager")
//.antMatchers("/test/index").hasRole("admins")//用户具备admins角色才能访问
//用户具备"admins","role"两个角色其中一个就可以访问
.antMatchers("/test/index").hasAnyRole("admins","role")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
用户service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service("UserDetailsService")
public class MyUserDetailService implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

//调用userMapper,根据用户名查询数据库
QueryWrapper<Users> wrapper = new QueryWrapper<>();
//where username = ?
wrapper.eq("username",username);
Users users = userMapper.selectOne(wrapper);
//判断数据库理有没有这个用户
if (users == null){ //没有这个用户,认证失败
throw new UsernameNotFoundException("用户名不存在!");
}
//借助AuthorityUtils职责工具类将返回的用户设置角色
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
//从数据库查询的结果来返回user对象,得到对应的用户名和密码
return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}
}

自定义403没有权限访问页面

需要有对应的页面

403是因为角色没有对应权限访问该页面所以出403错误

1
2
//设置403没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");

注解的使用

hasRole(role) 用户拥有指定的角色权限时返回true
hasAnyRole([role1,role2]) 用户拥有任意一个指定的角色权限时返回true
hasAuthority(authority) 用户拥有指定的权限时返回true
hasAnyAuthority([authority1,authority2]) 用户拥有任意一个指定的权限时返回true

@Secured

  • @Secured(“ROLE_VIEWER”) 表示只有拥有ROLE_VIEWER角色的用户,才能够访问getUsername()方法。

  • @Secured({ “ROLE_DBA”, “ROLE_ADMIN” }) 表示用户拥有”ROLE_DBA”, “ROLE_ADMIN” 两个角色中的任意一个角色,均可访问

判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
使用注解先要开启注解功能!

在启动类或者配置类上添加启动注解开发的注解

@EnableGlobalMethodSecurity(securedEnabled=true)

1
2
3
4
5
6
7
8
9
@EnableGlobalMethodSecurity(securedEnabled=true)
@SpringBootApplication
public class SpringsecurityApplication {

public static void main(String[] args) {
SpringApplication.run(SpringsecurityApplication.class, args);
}

}

在controller层使用注解开发

1
2
3
4
5
@GetMapping("update")
@Secured({"ROLE_sale","manager"}) //@Secured({"ROLE_sale","manager"}) //用户具有"ROLE_sale"角色,或"manager"的权限其中之一才能访问
public String update(){
return "hello update";
}

在userServiceDetail中设置用户角色

1
2
//借助AuthorityUtils职责工具类将返回的用户设置角色sale 权限admins
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");

@PreAuthorize

@PreAuthorize:注解适合进入方法前的权限验证,

@PreAuthorize可以将登录用户的roles/permissions参数传到方法中。

先开启注解功能: @EnableGlobalMethodSecurity(prePostEnabled = true)

1
2
3
4
5
6
7
8
9
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true)
@SpringBootApplication
public class SpringsecurityApplication {

public static void main(String[] args) {
SpringApplication.run(SpringsecurityApplication.class, args);
}

}

在controller层使用注解开发

1
2
3
4
5
@GetMapping("update")
@PreAuthorize("hasAnyAuthority('admins')") //用户具备admins权限才能访问
public String update(){
return "hello update";
}

@PostAuthorize

先开启注解功能: @EnableGlobalMethodSecurity(prePostEnabled = true)

和@PreAuthorize的开启注解一样,使用也类似

@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.

方法执行完毕再进行校验

1
2
3
4
5
6
@GetMapping("update")
@PostAuthorize("hasAnyAuthority('menu:system')") //访问web页面,执行完update方法之后,再进行校验 控制台会打印update.... 但是没有menu:system权限,无法访问
public String update(){
System.out.println("update.....");
return "hello update";
}

@PostFilter

对返回的数据进行过滤

@PostFilter :权限验证之后对数据进行过滤 留下用户名是admin1的数据
表达式中的 filterObject 引用的是方法返回值List中的某一个元素

1
2
3
4
5
6
7
8
9
10
@GetMapping("getAll")
@PreAuthorize("hasAnyAuthority('admins')")
@PostFilter("filterObject.username == 'admin1'") //过滤掉username不对于admin1
public List<Users> getAllUser() {
ArrayList<Users> list = new ArrayList<>();
list.add(new Users(12,"admin1","1111"));
list.add(new Users(22, "admin2", "888"));
System.out.println(list);
return list;//return的是admin1的信息
}

@PreFilter

@PreFilter: 进入控制器之前对数据进行过滤

将id能整除二的保留,其他过滤

1
2
3
4
5
6
7
8
9
@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> list) {
list.forEach(t -> {
System.out.println(t.getId() + "\t" + t.getUsername());
});
return list;
}

用户注销

登录成功后跳转到success页面,在用户登录的情况下,可以访问该用户对应角色后权限能访问的页面,退出登录后,无法访问之前需要相应权限或角色的页面

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
    //使用HttpSecurity参数,来选择自定义的登录页面设置
@Override
protected void configure(HttpSecurity http) throws Exception {

//用户注销
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
//设置403没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
//自定义自己编写的登录页面
http.formLogin()
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/success.html").permitAll() //登录成功之后跳转的路径
.and().authorizeRequests()
.antMatchers("/", "/test/hello", "/user/login").permitAll() //设置哪些路径可以直接访问
//.antMatchers("/test/index").hasAuthority("admins")//设置权限为admins才问/test/index
//设置拥有权限manager,admins其中一个就能问/test/index
//.antMatchers("/test/index").hasAnyAuthority("admins","manager")
.antMatchers("/test/index").hasRole("sale")//用户具备sale角色才能访问
//用户具备"admins","role"两个角色其中一个就可以访问
// .antMatchers("/test/index").hasAnyRole("admins","role")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
}

基于安全登录的数据库记住我

验证过程

设置登录页面

记住我的name标签必须为:remember-me

设置记住我复选框选项

1
2
3
4
5
6
7
8
9
<form action="/user/login"  method="post">
用户名:<input type="text" name="username">
<br/>
密码: <input type="text" name="password">
<br/>
<input type="checkbox" name="remember-me">自动登录
<br/>
<input type="submit" value="login">
</form>

config

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
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

//注入数据源
@Autowired
private DataSource dataSource;

//配置对象
@Bean
public PersistentTokenRepository persistentTokenRepository() {
//使用jdbc模板操作数据库生成的token
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
// 赋值数据源
jdbcTokenRepository.setDataSource(dataSource);
// 自动创建表,第一次执行会创建,以后要执行就要删除掉!
// 自己建表就不要打开
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}

@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//设置自己写的实现类并设置密码编码
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}

//使用HttpSecurity参数,来选择自定义的登录页面设置
@Override
protected void configure(HttpSecurity http) throws Exception {

//用户注销
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
//设置403没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
//自定义自己编写的登录页面
http.formLogin()
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/success.html").permitAll() //登录成功之后跳转的路径
.and().authorizeRequests()
.antMatchers("/", "/test/hello", "/user/login").permitAll() //设置哪些路径可以直接访问
//.antMatchers("/test/index").hasAuthority("admins")//设置权限为admins才问/test/index
//设置拥有权限manager,admins其中一个就能问/test/index
//.antMatchers("/test/index").hasAnyAuthority("admins","manager")
.antMatchers("/test/index").hasRole("sale")//用户具备sale角色才能访问
//用户具备"admins","role"两个角色其中一个就可以访问
// .antMatchers("/test/index").hasAnyRole("admins","role")
.anyRequest().authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository())//设置记住我
.tokenValiditySeconds(60)//设置token存活时间 单位秒
.userDetailsService(userDetailsService)//设置服务类
.and().csrf().disable(); //关闭csrf防护
}
}

CSRF

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。 跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。 从Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护

预防CSRF跨站攻击

不够明白

防止 ASP.NET MVC 中的跨站点请求伪造 (CSRF) 攻击 | Microsoft Docs

在登录页面添加一个隐藏域:

<input type=”hidden”th:if=”${_csrf}!=null”th:value=”${_csrf.token}”name=”_csrf”/>
关闭安全配置的类中的csrf // http.csrf().disable();

微服务权限授权

待更~

等待springcloud