上一篇大概陈述了一下关于SpringSecurity的用户登录,从使用不同的方式进行用户登录做了一个简单的介绍。在开头我也曾提到过,SpringSecurity主要有两大功能,第一个就是用户登录,第二个就是资源授权。这篇就来大概描述一下资源授权的整个过程。
一.实现configure(HttpSecurity http)方法
在配置类中重写configure(HttpSecurity http)方法,注意参数是HTTPSecurity,在这里做一个最简单的登录成功失败Url配置。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.successForwardUrl("/success")
.failureForwardUrl("/fail");
}
在Controller中新增以下几个方法,登录成功进入“home”页面,该页面有三个a标签,分别指向“admin”,“manager”,“worker”页面,在template目录下新建,home.html,fail.html,admin.html,manager.html,worker.html,子页面有跳转到home页面的连接。
@PostMapping("/success")
public String success(){
return "home";
}
@PostMapping("/fail")
public String fail(){
return "fail";
}
@GetMapping("/admin")
public String admin(){
return "admin";
}
@GetMapping("/manager")
public String manager(){
return "manager";
}
@GetMapping("/worker")
public String worker(){
return "worker";
}
@GetMapping("/home")
public String home(){
return "home";
}
运行代码,随便登录一个账号,发现只要用户名和密码输入正确,都可以访问Home页面中的任意链接。下面就对页面开始授权,只能登录账号访问已授权的页面,未授权的禁止访问。
二 方法
1.hasAuthority 方法
如果当前的主体具有指定的权限,则返回 true,否则返回 false.
在configure(HTTPSecurity http)方法中加上这句,这句话的意思就是访问“/admin”接口的用户必须拥有"admin"权限,访问“manager”的用户必须拥有“manager”权限。
http.authorizeRequests() .antMatchers("/admin").hasAuthority("admin") .antMatchers("/manager").hasAuthority("manager") .antMatchers("/worker").hasAuthority("worker") .anyRequest() // 其他请求 .authenticated();
当前数据库记录:
重新启动项目,发现name为“zhangsan”的用户只能访问“admin.html”,访问其他页面会报403,也就是没有当前权限。
2.hasAnyAuthority
如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回 true.
根据我们数据库的配置,name为“zhangsan”的用户可以访问既可以“admin.html”,也可以访问“manager.html”和“worker.htm”页面。若表示拥有多个权限,可以使用hasAnyAuthority()。
http.authorizeRequests()
.antMatchers("/admin").hasAnyAuthority("admin")
.antMatchers("/manager").hasAnyAuthority("admin,manager")
.antMatchers("/worker").hasAnyAuthority("admin,manager,worker")
.anyRequest() // 其他请求
.authenticated();
重启项目,发现zhangsan可以访问任意页面,lisi只能访问非admin页面,wangwu只能访问worker页面。
3.hasRole
如果用户具备给定角色就允许访问,否则出现 403。
http.authorizeRequests()
.antMatchers("/admin").hasRole("admin")
.anyRequest() // 其他请求
.authenticated();
然后我用"zhangsan"用户登录,发现跳转到了home页面,然后去访问“admin”链接时,却报了403.
查看hasRole源码发现,在权限名称的前面加了个"ROLE_",因为我在数据库中只写了指定的权限名称,没有在加"ROLE_"前缀,所以默认会没有权限访问。这个问题可以在数据库name前加上“ROLE_”,也可以修改读取权限方法,在权限名前加上“ROLE_”关键字即可。
修改读取权限集合方法,在name钱都加上“ROLE_”
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>(roles.size());
for (SysRole role : roles) {
list.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
}
return list;
}
重启项目,访问admin.html页面成功。
4.hasAnyRole
和hasAnyAuthority()用法相似,这里就不做演示了。只需要在权限间使用逗号隔开就可以了。
三.注解
1.@Secured
作用:用在接口上,用来判断接口是否具有某种角色。另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。多个角色可用逗号隔开。
首先需要开启注解,在启动类中加上@EnableGlobalMethodSecurity(securedEnabled=true)
定义一个接口,只能由“admin”权限的人才能够进入,进入接口后返回一个字符串。
@Secured("ROLE_admin")
@GetMapping("secured_admin")
@ResponseBody
public String securedAdmin() {
return "Secured Admin";
}
登录“admin”账号,访问该接口,页面打印后台返回的字符串。
使用其它账号提示没有权限。
2.@PreAuthorize
作用:进入方法前的验证用户是否具有某种权限。
在启动类中加上@EnableGlobalMethodSecurity(prePostEnabled = true)开启注解。
定义一个方法,具有“manager”权限的人才能进入该方法。
@PreAuthorize("hasAnyRole('manager')")
@GetMapping("pre_authorize")
@ResponseBody
public String preAuthorize() {
return "PreAuthorize Manager";
}
测试结果略。
3.@PostAuthorize
作用:在方法执行后进行权限验证。
在启动类中加入@EnableGlobalMethodSecurity(prePostEnabled= true)开启注解。
定义一个方法,只有具有“admin”权限的人才能进入,然后在该方法中做一个打印语句。
@PostAuthorize("hasAnyRole('admin')")
@GetMapping("post_authorize")
@ResponseBody
public String PostAuthorize() {
System.out.println("entry method");
return "PostAuthorize Admin";
}
登录没有“admin”权限的账号,尝试进入该方法,页面报403没有权限,查看idea控制台发现,打印语句执行。由此可以证明该注解是先进入方法,执行完成后才会验证是否拥有权限。
4.@PostFilter
作用:权限验证之后对数据进行过滤
定义一个方法,返回一个User对象list,然后对该List进行数据过滤,只返回name为"admin"的对象。
定义User对象
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String roleName;
}
定义方法,构造List,在里面添加三条数据。在方法上使用@PostFilter注解,仅返回name为“admin”的数据。
@Secured("ROLE_admin")
@GetMapping("post_filter")
@PostFilter("filterObject.name == 'admin'")
@ResponseBody
public List<User> postFilter() {
ArrayList<User> list = new ArrayList<>(3);
list.add(new User(1L, "admin", "admin"));
list.add(new User(2L, "manager", "manager"));
list.add(new User(3L, "worker", "worker"));
return list;
}
调用该方法,返回结果如下:
5.@PreFilter
作用:进入控制器之前对数据进行过滤。
往后台传一个list,并返回id为偶数的对象,过滤id为基数的对象。
定义一个接口,并使用@PreFilter过滤对象id。将过滤后的id在控制台打印。
@Secured("ROLE_admin")
@GetMapping("pre_filter")
@PreFilter("filterObject.id%2==0")
@ResponseBody
public List<User> preFilter(@RequestBody List<User> list) {
list.forEach(e -> System.out.println(e.getId()));
return list;
}
使用PostMan进行方法调用,传入一个对象list,里面有四个对象,id分别为1,2,3,4。
控制台打印结果:
后台返回数据: