Spring Security 的权限分控制也是参考 RBAC 模型来的,由
用户
、角色
、权限
等基础部分组成,然后再这个基础之上进行设计和开发而来的一套安全框架。
1. Security 实现授权的基本方式
我认为 Spring Security 的认证方式无非是三种如下所示:
我这里只是简单的测试一下前两种的认证方式,第三种很常用,但是比较复杂,下次再写
- 基于配置的对
url
授权 - 基于注解的对方法级别的授权
- 动态的实现授权认证
a. 基于配置的对 url 授权
构建项目的过程自动忽略了,由于不需要从数据库中读取权限,所以本次不需要与数据库交互,采用简单的从内存中读取用户。
初始化环境:
用户三个:
admin 密码 admin 角色 test,admin
test 密码 test 角色 test
dev 密码 dev 角色 dev
资源url:
/admin/add
/admin/upodate
/test/add
/test/upodate
/dev/add
/dev/upodate
配置用户
// 密码器
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// 配置基础的用户三个
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("admin")).roles("test","admin")
.and()
.withUser("test").password(passwordEncoder().encode("test")).roles("test")
.and()
.withUser("dev").password(passwordEncoder().encode("dev")).roles("dev")
;
}
// 配置url的权限和授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 有 admin 角色的可以访问所有的 admin/ 开头的请求
.antMatchers("/admin/**").hasRole("admin")
// 有 test 角色的可以访问所有的 test/ 开头的请求
.mvcMatchers("/test/**").hasRole("test")
// 有 admin 或者 dev 角色的都可以访问所有的 dev/ 开头的请求
.antMatchers("/dev/**").hasAnyRole("admin","dev","test")
// 其他请求只要登录就可以访问
.anyRequest().authenticated()
.and()
// 开启表单登录和登陆成功后的url url默认方式是POST方式
.formLogin()
.successForwardUrl("/index")
;
}
admin controller
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/add")
public String add(){
return this.getClass()+"添加的方法";
}
@GetMapping("/update")
public String update(){
return this.getClass()+"更新的方法的方法";
}
}
test controller
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/add")
public String add(){
return this.getClass()+"添加的方法";
}
@GetMapping("/update")
public String update(){
return this.getClass()+"更新的方法的方法";
}
}
dev controller
@RestController
@RequestMapping("/dev")
public class DevController {
@GetMapping("/add")
public String add(){
return this.getClass()+"添加的方法";
}
@GetMapping("/update")
public String update(){
return this.getClass()+"更新的方法的方法";
}
}
登录成功后的index请求
/**
* @author logic
*/
@RestController
public class IndexController {
@PostMapping("/index")
public String index(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication.getName()+"登录成功";
}
}
预测结果:
- admin 可以访问所有的url 因为 admin 有
admin
、test
两个角色,所以admin/**
、和test/**
的所有请求都可以访问,然后dev/**
配置了只要有admin
或者dev
test
角色的都可以访问,所以admin用户可以访问多有的url - test 用户可以访问
test/**
和dev/**
的所有url - dev 用户只能访问
dev/**
- 其他未分配的url只要登录即可访问,配置过的url需要对应的权限才能访问
结果截图:
admin 用户的截图
test 用户:
dev用户的截图
总结:通过配置的方式分配权限主要是使用这几个方法,
antMatchers()
、mvcMatchers()
,这两个方法定义路径的,hasRole()
、hasAnyRole()
这两个方法设置权限。只要一一对应即可访问。
还有一对方法hasAuthority()
、hasAnyAuthority
通过判断用户的GrantedAuthority
来进行访问控制,底层跟hasAnyRole()
差不多,我这里就不测试了。
权限开放的方法是permitAll()
,.antMatchers("/**").permitAll()
就表示所有的请求都可以通过,不需要验证
通过配置的方式一般是不用的,配置一般只配置一些静态的文件资源
b. 基于注解的对方法级别的授权
基于注解的方式有几个前提条件:
- 注释掉配置文件中的路由配置(url的权限配置)
- 开启全局的注解
@EnableGlobalMethodSecurity
- 选择使用的参数,共支持
prePostEnabled
,securedEnabled
,jsr250Enabled
,proxyTargetClass
四种参数
- 使用
prePostEnabled
使用prePostEnabled
只用在注解上面开启prePostEnabled
即可如:@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//密码器
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("admin")).roles("test","admin")
.and()
.withUser("test").password(passwordEncoder().encode("test")).roles("test")
.and()
.withUser("dev").password(passwordEncoder().encode("dev")).roles("dev")
;
}
// 配置url的权限和授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.successForwardUrl("/index")
;
}
}
- 使用
prePostEnabled
的话涉及到了四个注解@PreAuthorize
、@PostAuthorize
、@PreFilter
、@PostFilter
-
PerAuthorize 它的作用就是在调用标记的方法之前,通过表达式来判定是否有权限访问。常用的方法有
- 基于角色的授权方式如:
@PreAuthorize("hasAnyRole('admin')")
,表示标记的方法拥有admin
角色才可以访问 - 基于
UserDetails
的表达式,例如@PreAuthorize("hasAnyRole('admin') and principal.username.startsWith('adm')")
必须有admin
角色和用户名必须adm
开头 - 基于对入参的 SpEL 表达式处理方法入参,例如:
@PreAuthorize("#name.equals(principal.username)")
表示请求参数name
必须与登录的用户命相同才能访问
- 基于角色的授权方式如:
-
PostAuthorize 它的作用跟 PerAuthorize 一样,只不过是在调用目标方法之后执行,如果方法没有返回值,那么相当于没有权限。
-
@PreFilter 它的验证过程发生在接口接收参数之前,对入参进行过滤,并且入参必须为
java.utile.Collection
,你入参是一个集合,那么他就可以对这个集合里面的数据通过规则进行过滤。 -
@PreFilter 它的验证过程发生在接口处理好数据进行返回的时候,可以对返回的结果进行过滤。
测试:
@RestController @RequestMapping("/admin") public class AdminController { @GetMapping("/add") @PreAuthorize("hasAnyRole('admin') and principal.username.startsWith('adm')") public String add(){ return this.getClass()+"添加的方法"; } @PreAuthorize("#name.equals(principal.username)") @GetMapping("/update") public String update(String name){ return this.getClass()+"更新的方法的方法"; } @PreFilter(value = "filterObject.contains(principal.username)",filterTarget = "names") @GetMapping("/del") public String del(@RequestParam(value = "names") List<String> names){ System.out.println(names); return this.getClass()+"删除方法执行成功"; } }
预测结果:
1.用户admin
可以访问 /admin/add
2.所有用户访问/admin/update?name=?
携带参数与登录用户名相同即可访问
3.用户dev
,test
无法访问/admin/add
测试:
1. admin
2. test 用户
总结:spring security 的认证方式有好几种,通常都是从数据库中读取角色来分配权限的,像配置和注解一般不使用,就像注解系统默认是关闭的,通过javaConfig来配置的一般只是一些静态的文件,但是我们现在的项目一般都是前后端分离和分布式的,一般也用不上配置,所以这些只用了解即可,所以一些测试我也没有做的那么全。