1.4 授权

在上一步操作中,我们已经完成了用户权限的授权设置,用户在访问系统资源的时候,我们需要判断用户是否有访问该资源的权限。下面来完成该任务,首选准备一个案例。

1.4.1 案例准备

Controller

image.png

  1. @RestController
  2. @RequestMapping("/user")
  3. public class UserController {
  4. @GetMapping
  5. public String list() {
  6. return "用户列表";
  7. }
  8. @GetMapping("/add")
  9. @ResponseBody
  10. public String add() {
  11. return "新增用户成功";
  12. }
  13. @GetMapping("/edit")
  14. @ResponseBody
  15. public String edit() {
  16. return "编辑用户成功";
  17. }
  18. @GetMapping("/del")
  19. @ResponseBody
  20. public String del() {
  21. return "删除用户成功";
  22. }
  23. }

Template

image.png
登录系统,访问用户列表:
image.png
测试发现,目前用户的是个功能都可以访问,进一步,让权限控制起作用。

分配角色和权限

image.png
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

具体代码实现:
image.png

注意:角色名称前需要添加 ROLE_ 前缀,用来和权限做区分。

已经分配好了角色和权限,如何进行授权控制呢?使用授权注解。

1.4.2 安全注解

启动注解

Spring Security 授权控制可以配合安全注解使用,要开启这些注解,只需要在Spring Security配置文件类添加 @EnableGlobalMethodSecurity 注解:
image.png
查看该注解源码:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({ElementType.TYPE})
  3. @Documented
  4. @Import({GlobalMethodSecuritySelector.class})
  5. @EnableGlobalAuthentication
  6. @Configuration
  7. public @interface EnableGlobalMethodSecurity {
  8. /**
  9. * 基于表达式进行方法访问控制
  10. */
  11. boolean prePostEnabled() default false;
  12. /**
  13. * 基于角色进行方法访问控制
  14. */
  15. boolean securedEnabled() default false;
  16. /**
  17. * 基于 JSR-250 注解
  18. */
  19. boolean jsr250Enabled() default false;
  20. boolean proxyTargetClass() default false;
  21. AdviceMode mode() default AdviceMode.PROXY;
  22. int order() default 2147483647;
  23. }

基于表达式进行方法访问控制功能非常强大,可以实现 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_ 前缀

使用安全注解

使用角色控制

image.png
访问 http://localhost:8080 ,使用 jack 用户登录系统,jack 的角色和权限如下:
image.png
测试发现,查看、新增、编辑用户都可以正常访问,访问删除用户的时候请求返回 403,抛出了 AccessDeniedException 异常(访问被拒绝异常):
image.png

使用权限控制

image.png
测试发现,访问删除用户功能,和使用角色控制时候抛出一样的异常信息,但是异常信息不够友好,我们来自定义没有权限的异常处理。

无权限处理

因为 AccessDeniedException 异常是在 Controller 层抛出的,处理该异常非常简单,只需要在全局的异常控制器中增加对应异常的处理逻辑就可以,代码如下:
image.png
重启系统,再次登录访问删除用户功能:

image.png
1.4.3 安全标签

可以使用 thymeleaf 对 Spring Security 提供的安全扩展标签,来控制页面内容的显示,在页面上只显示用户有权访问的资源。

引入依赖

  1. <dependency>
  2. <groupId>org.thymeleaf.extras</groupId>
  3. <artifactId>thymeleaf-extras-springsecurity5</artifactId>
  4. </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>

页面效果

image.png

常用标签

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')"

判断当前用户是否拥有任意一个指定的权限。