文章目录
- 一、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/