Spring Security-GrantedAuthority已授予的权限、权限检查

时间:2024-10-05 11:57:57

文章目录

  • 一、Spring Security的GrantedAuthority(已授予的权限)
    • 1. Spring Security "角色"如何表示?与Shiro有何不同?
    • 2. 权限
      • 2.1 GrantedAuthority接口
        • 2.1.1 GrantedAuthority接口的默认实现SimpleGrantedAuthority
        • 2.1.2 角色和权限能否分开存储?角色能不能不带"ROLE_"前缀
  • 二、根据token获取用户信息以及权限校验
    • 1. filter 中根据token获取用户信息,并配置secruity 上下文
    • 2. 创建接口权限判断工具,代码添加注解进行权限判断
  • 三、总结(重要)
  • 四、参考

一、Spring Security的GrantedAuthority(已授予的权限)

【详解】GrantedAuthority(已授予的权限)
参考URL: /longfurcat/p/
Spring Security中角色和GrantedAuthority之间的区别
参考URL: /longfurcat/p/

1. Spring Security "角色"如何表示?与Shiro有何不同?

在Security中,角色和权限共用GrantedAuthority接口,唯一的不同角色就是多了个前缀"ROLE_",而且它没有Shiro的那种从属关系,即一个角色包含哪些权限等等。在Security看来角色和权限时一样的,它认证的时候,把所有权限(角色、权限)都取出来,而不是分开验证。

所以,在Security提供的UserDetailsService默认实现JdbcDaoImpl中,角色和权限都存储在auhtorities表中。而不是像Shiro那样,角色有个roles表,权限有个permissions表。以及相关的管理表等等。

GrantedAuthority接口的默认实现SimpleGrantedAuthority.

2. 权限

Authentication实现类都保存了一个GrantedAuthority列表,其表示用户所具有的权限。

Authentication实现类就是指我们自己实现的CustomUsernamePasswordAuthenticationToken ,其最终实现Authentication接口

public class CustomUsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    /**
     * 用户名
     */
    private final Object principal;

    /**
     * 密码
     */
    private Object credentials;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
    private final Collection<GrantedAuthority> authorities;
    private Object details;
    private boolean authenticated = false;
  • 1
  • 2
  • 3
  • 4

总结: 用户权限信息保存在GrantedAuthority接口实例集合中。

2.1 GrantedAuthority接口

我们知道UserDeitails接口里面有一个getAuthorities()方法。这个方法将返回此用户的所拥有的权限。这个集合将用于用户的访问控制,也就是Authorization。

所谓权限,就是一个字符串。一般不会重复。

所谓权限检查,就是查看用户权限列表中是否含有匹配的字符串。

GrantedAuthority是一个接口,其中只定义了一个getAuthority()方法,其返回值为String类型。

该方法允许AccessDecisionManager获取一个能够精确代表该权限的字符串。通过返回一个字符串,一个GrantedAuthority能够很轻易的被大部分AccessDecisionManager读取。

Spring Security内置了一个GrantedAuthority的实现,SimpleGrantedAuthority。它直接接收一个表示权限信息的字符串,然后getAuthority()方法直接返回该字符串。Spring Security内置的所有AuthenticationProvider都是使用它来封装Authentication对象的。

2.1.1 GrantedAuthority接口的默认实现SimpleGrantedAuthority

参考URL: /longfurcat/p/

GrantedAuthority接口的默认实现SimpleGrantedAuthority

package org.springframework.security.core.authority;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;

public final class SimpleGrantedAuthority implements GrantedAuthority {
    private static final long serialVersionUID = 500L;
    private final String role;

    public SimpleGrantedAuthority(String role) {
        Assert.hasText(role, "A granted authority textual representation is required");
        this.role = role;
    }

    public String getAuthority() {
        return this.role;
    }

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else {
            return obj instanceof SimpleGrantedAuthority ? this.role.equals(((SimpleGrantedAuthority)obj).role) : false;
        }
    }

    public int hashCode() {
        return this.role.hashCode();
    }

    public String toString() {
        return this.role;
    }
}

  • 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

注意,在构建SimpleGrantedAuthority对象的时候,它没有添加任何前缀。所以表示"角色"的权限,在数据库中就带有"ROLE_"前缀了。所以authorities表中的视图可能是这样的。
在这里插入图片描述

2.1.2 角色和权限能否分开存储?角色能不能不带"ROLE_"前缀

你可以定义两张表,一张存角色,一张存权限。但是你自定义UserDetailsService的时候,需要保证把这两张表的数据都取出来,放到UserDails的权限集合中。当然你数据库中存储的角色也可以不带"ROLE_"前缀,就像这样。

在这里插入图片描述在这里插入图片描述Security才不管你是角色,还是权限。它只比对字符串。

比如它有个表达式hasRole(“ADMIN”)。那它实际上查询的是用户权限集合中是否存在字符串"ROLE_ADMIN"。 如果你从角色表中取出用户所拥有的角色时不加上"ROLE_"前缀,那验证的时候就匹配不上了。

所以角色信息存储的时候可以没有"ROLE_"前缀,但是包装成GrantedAuthority对象的时候必须要有。

二、根据token获取用户信息以及权限校验

1. filter 中根据token获取用户信息,并配置secruity 上下文

/**
* 这个过滤器,在所有请求之前,也在spring security filters之前
*/
@Component
public class AuthorizationTokenFilter  extends OncePerRequestFilter {


    @Autowired
    TokenRedisService tokenRedisService;

    private String tokenHead = "";
    private String tokenHeader = "Authorization";
    

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // 1.从header获取token
        String token = request.getHeader(this.tokenHeader);


        // 存在
        if(StringUtils.isNotBlank(token) && SecurityContextHolder.getContext().getAuthentication() == null){
            //根据从redis中查用户、角色、权限
            UserTokenInfo userTokenInfo = tokenRedisService.getTokenFromCache(token);
            if(userTokenInfo == null){
                throw new ServiceException(ExceptionCodeEnum.TOKEN_IS_INVALID);
            }

            LoginUser user = userTokenInfo.getLoginUser();

            //添加授权信息
            String[] permissions = user.getPermissions();
            List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(permissions);

            CustomUsernamePasswordAuthenticationToken authentication = new CustomUsernamePasswordAuthenticationToken(user,null, user.getCardId(), authorities);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }


        filterChain.doFilter(request, response);

    }
}

  • 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

2. 创建接口权限判断工具,代码添加注解进行权限判断

自定义接口权限判断工具,如下:

思路: 从spring secruity 上下文中 () 获取已授权信息,然后字符串匹配查看是否匹配注解中的字符串,如果匹配说明有权限执行该接口。

/**

  • 接口权限判断工具
    */
@Slf4j
@Component("permissionValidator")
public class PermissionService {


    /**
     * 判断接口是否有xxx:xxx权限
     * @param permission 权限
     * @return {boolean}
     */
    public boolean hasPermission(String permission) {
        if (StrUtil.isBlank(permission)) {
            return false;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return false;
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        return authorities.stream().map(GrantedAuthority::getAuthority).filter(StringUtils::hasText)
                .anyMatch(x -> PatternMatchUtils.simpleMatch(permission, x));
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在控制层添加 @PreAuthorize相应的注解,就可以判断,接口是否有权限执行。

@RestController
public class TestController {


    @PreAuthorize("@('sys_menu_add')")
    @RequestMapping("/hello")
    public Object hello(){
        return "hello!";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

三、总结(重要)

通过一、二章节学习,我们简单总结授权和鉴权过程:

所谓权限就是一个字符串,鉴权就是字符串匹配,通过字符串匹配判断用户所对应的角色是有用户该菜单(字符串)的的权限。

字符串权限被封在在 GrantedAuthority类结构中,用户在第一次登陆获取token,并把token对应的用户信息(包括权限信息,所谓权限信息就是字符串数组)存储到redis

之后,请求其他接口我们添加token,filter过滤器,拿到token从redis获取用户权限信息,组装自定义AuthenticationToken,然后托管给 Security上下文。

获取用户权限信息,组装自定义AuthenticationToken,源码示例如下:

            //添加授权信息
            String[] permissions = user.getPermissions();
            List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(permissions);

            CustomUsernamePasswordAuthenticationToken authentication = new CustomUsernamePasswordAuthenticationToken(user,null, user.getCardId(), authorities);
            SecurityContextHolder.getContext().setAuthentication(authentication);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

接下来,我们自定义了权限校验器,其原理就是,从注解拿字符和从 Security上下文拿到用户已授权信息进行字符串匹配。

四、参考

Spring Security(2)基于动态角色资源权限校验
参考URL: /kezp/p/