1.4 授权
在上一步操作中,我们已经完成了用户权限的授权设置,用户在访问系统资源的时候,我们需要判断用户是否有访问该资源的权限。下面来完成该任务,首选准备一个案例。
1.4.1 案例准备
Controller
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping
public String list() {
return "用户列表";
}
@GetMapping("/add")
@ResponseBody
public String add() {
return "新增用户成功";
}
@GetMapping("/edit")
@ResponseBody
public String edit() {
return "编辑用户成功";
}
@GetMapping("/del")
@ResponseBody
public String del() {
return "删除用户成功";
}
}
Template
登录系统,访问用户列表:
测试发现,目前用户的是个功能都可以访问,进一步,让权限控制起作用。
分配角色和权限
UserDetails 类的成员变量 authorities 用来保存用户的角色和权限。AuthorityUtils.commaSeparatedStringToAuthorityList()
方法可以将逗号分隔的权限字符串和角色字符串转换为权限集合。
我们来设置系统的权限和角色规则:
- 角色 USER、ADMIN
- 拥有 USER 角色可以查看、新增和编辑用户
- 拥有 ADMIN 角色可以查看、新增、编辑和删除用户
- 权限
- system:user:view 查看用户
- system:user:add 新增用户
- system:user:edit 编辑用户
- system:user:del 删除用户
我们来设置 jack 的角色和权限:
- jack 可以查看、新增、修改用户,但不能删除用户
- 给 jack 分配 USER 角色可以满足要求
- 给 jack 分配 system:user:view、system:user:add、system:user:edit 三个权限也可以满足要求
- 我们同时分配 USER 角色和 system:user:view、system:user:add、system:user:edit 权限给 jack
具体代码实现:
注意:角色名称前需要添加 ROLE_ 前缀,用来和权限做区分。
1.4.2 安全注解
启动注解
Spring Security 授权控制可以配合安全注解使用,要开启这些注解,只需要在Spring Security配置文件类添加 @EnableGlobalMethodSecurity 注解:
查看该注解源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({GlobalMethodSecuritySelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {
/**
* 基于表达式进行方法访问控制
*/
boolean prePostEnabled() default false;
/**
* 基于角色进行方法访问控制
*/
boolean securedEnabled() default false;
/**
* 基于 JSR-250 注解
*/
boolean jsr250Enabled() default false;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
基于表达式进行方法访问控制功能非常强大,可以实现 securedEnabled() 和 jsr250Enabled() 的所有功能,我们只启用prePostEnabled() ,该注解基本能满足常见的开发需求;
@PreAuthorize
给注解使用在方法上,在使用了给注解的方法调用之前,通过注解中配置的表达式来判断是否可以调用该方法。
常用表达式:
表达式 | 示例 | 描述 |
---|---|---|
hasRole([role]) |
hasRole('ADMIN') |
用户拥有指定的角色 允许调用方法 |
hasAnyRole([role1,role2]) |
hasAnyRole('USER','ADMIN') |
用户拥有任意一个指定的角色 允许调用方法 |
hasAuthority([authority]) |
hasAuthority('system:user:add') |
用户拥有指定的权限时 允许调用方法 |
hasAnyAuthority([auth1,auth2]) |
hasAnyAuthority( 'system:user:add', 'system:user:edit') |
用户拥有任意一个指定的权限 允许调用方法 |
示例:
@PreAuthorize(“hasAuthority(‘system:user:view’)”)
必须拥有 system:user:view 权限才可以调用被注解的方法。
@PreAuthorize(“hasRole(‘ADMIN’)”)
必须拥有 ADMIN 角色才可以调用被注解的方法。
注意:配置角色表达式不需要添加 ROLE_ 前缀
使用安全注解
使用角色控制
访问 http://localhost:8080 ,使用 jack 用户登录系统,jack 的角色和权限如下:
测试发现,查看、新增、编辑用户都可以正常访问,访问删除用户的时候请求返回 403,抛出了 AccessDeniedException 异常(访问被拒绝异常):
使用权限控制
测试发现,访问删除用户功能,和使用角色控制时候抛出一样的异常信息,但是异常信息不够友好,我们来自定义没有权限的异常处理。
无权限处理
因为 AccessDeniedException 异常是在 Controller 层抛出的,处理该异常非常简单,只需要在全局的异常控制器中增加对应异常的处理逻辑就可以,代码如下:
重启系统,再次登录访问删除用户功能:
1.4.3 安全标签
可以使用 thymeleaf 对 Spring Security 提供的安全扩展标签,来控制页面内容的显示,在页面上只显示用户有权访问的资源。
引入依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
使用
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>系统初始化页面</title>
</head>
<body>
<div>
Hello Spring Security 您好:<span sec:authentication="name"></span>
<hr>
<a th:href="@{/logout}">注销</a>
<hr>
<a th:href="@{/user}" sec:authorize="hasAuthority('system:user:view')">用户列表</a> |
<a th:href="@{/user/add}" sec:authorize="hasAuthority('system:user:add')">新增用户</a> |
<a th:href="@{/user/edit}" sec:authorize="hasAuthority('system:user:edit')">编辑用户</a> |
<a th:href="@{/user/del}" sec:authorize="hasAuthority('system:user:del')">删除用户</a> |
</div>
</body>
</html>
页面效果
常用标签
SpringSecurity 的标签属性有以下几个:
sec:authorize="isAuthenticated()"
判断用户是否已经登陆认证
sec:authentication="name"
获得当前用户的用户名,引号内的参数必须是 name。
sec:authentication="principal.xxxx"
获得当前用户的其它属性
sec:authorize="hasRole('USER')"
判断当前用户是否拥有指定的角色,角色名称不需要加 ROLE_前缀。
sec:authorize="hasAnyRole('USER','ADMIN')"
判断当前用户是否拥有任意一个指定的角色。角色名称不需要加 ROLE_前缀。
sec:authorize="hasAuthority('system:user:add')"
判断当前用户是否拥有指定的权限。
sec:authorize="hasAnyAuthority('system:user:view','system:user:add')"
判断当前用户是否拥有任意一个指定的权限。