一、背景介绍
最近项目中要实现根据不同用户去划分不同的角色,而不同角色具备调用不同接口的权限这个功能。用户在调用接口时需要校验用户是否具有权限访问接口,防止外界恶意调用随意篡改
二、思路&方案
为什么要进行接口鉴权?
- 接口鉴权可以提供对接口访问的监控和追踪功能。通过记录和审计用户对接口的访问情况,可以及时发现异常行为和安全威胁,并采取相应的措施进行应对。
- 通过接口鉴权,可以限制对敏感数据的访问。只有具有相应权限的用户或应用程序才能获取敏感数据,从而保护数据的机密性和完整性。
- 接口鉴权可以实现对接口的细粒度访问控制。根据用户的身份、角色、权限等级等,可以控制用户对接口的不同操作和功能的访问权限,确保只有有权用户可以执行相应的操作。
接口鉴权是保证系统安全、数据保护和访问控制的重要机制,防止未经授权的访问、滥用和数据泄露,提高系统的安全性和可靠性。
@PerAuthorizre权限注解
作用:方法前拦截判断是否具备权限,用来控制被注解标记的类或方法是否能够被调用
好处:
安全性增强:只有经过授权的用户才能执行带有 @PerAuthorizr 注解的代码,从而减少了潜在的安全漏洞和攻击风险。
标记权限:通过使用 @PerAuthorizr 注解,可以明确地标记哪些代码需要特定的权限才能执行。这有助于开发人员更好地了解代码的权限需求,并确保只有具有相应权限的用户或角色可以访问该代码。
底层实现:
- 底层实现可能会使用 AOP 技术。通过 AOP,可以在代码执行之前或之后,根据注解的参数进行权限验证和授权操作。
- 可能会使用拦截器或过滤器来拦截请求,并进行权限验证和授权操作。拦截器或过滤器可以在请求到达目标代码之前对请求进行预处理,包括检查用户的权限和身份验证。
- 可能会使用缓存机制。通过缓存用户的权限信息,可以避免频繁地进行权限查询和验证,提高系统的响应速度。
使用:
@PreAuthorize("")调用别名为ss类的hasPermi方法,并传入参数
@ss注解
作用:自定义,定义了权限校验流程
结果:返回true则鉴权成功,false则返回403说明没有权限
使用:
@ss.hasPermi('system:role:list')
调用别名为ss类的hasPermi方法,并传入参数
逻辑校验流程
第一步、定义@ss类,并添加hasPermi校验方法
- 获取传入用户的信息
- 判断用户信息的权限信息集合中是否含有定义的权限
第二步、在要鉴权的接口上添加@PreAuthorizez注解
三、过程
NS图
引入依赖
<!-- spring security 安全认证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.5.8</version>
</dependency>
定义@ss类,并添加hasPermi校验方法
package com.example;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Set;
/**
* @BelongsPackage: com.example
* @Description: 自定义权限实现
* @Version: 1.0
*/
@Service("ss")
public class PermissionService {
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission) throws Exception {
//判断是否传入权限字符
if ( StringUtils.isEmpty(permission))
{
return false;
}
//获取用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
//判断是否有用户信息,或者用户信息中是否包含权限集合
if ( StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
//将权限设置到请求头中
PermissionContextHolder.setContext(permission);
//判断是否包含权限
return hasPermissions(loginUser.getPermissions(), permission);
}
/**
* 判断是否包含权限
*
* @param permissions 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
private boolean hasPermissions(Set<String> permissions, String permission)
{
return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}
}
字符串工具类
package com.example;
/**
* @BelongsPackage: com.example
* @Description: 字符串工具类
* @Version: 1.0
*/
public class StringUtils {
/** 空字符串 */
private static final String NULLSTR = "";
/**
* * 判断一个对象是否为空
* @return true:为空 false:非空
*/
public static boolean isNull(Object object)
{
return object == null;
}
/**
* * 判断一个字符串是否为空串
* @return true:为空 false:非空
*/
public static boolean isEmpty(String str)
{
return isNull(str) || NULLSTR.equals(str.trim());
}
/**
* 去空格
*/
public static String trim(String str)
{
return (str == null ? "" : str.trim());
}
}
权限信息
package com.example;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* @BelongsPackage: com.example
* @Description: TODO
* @Version: 1.0
*/
public class PermissionContextHolder {
private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";
public static void setContext(String permission)
{
RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,
RequestAttributes.SCOPE_REQUEST);
}
public static String getContext()
{
return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,
RequestAttributes.SCOPE_REQUEST));
}
}
安全服务工具类
package com.example;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* @BelongsPackage: com.example
* @Description: 安全服务工具类
* @Version: 1.0
*/
public class SecurityUtils {
/**
* 获取用户
**/
public static LoginUser getLoginUser() throws Exception {
try
{
return (LoginUser) getAuthentication().getPrincipal();
}
catch (Exception e)
{
throw new Exception("获取用户信息异常");
}
}
/**
* 获取Authentication
*/
public static Authentication getAuthentication()
{
return SecurityContextHolder.getContext().getAuthentication();
}
}
登录用户身份权限
package com.example;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Set;
/**
* @BelongsPackage: com.example
* @Description: 登录用户身份权限
* @Version: 1.0
*/
@Data
public class LoginUser implements UserDetails
{
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户唯一标识
*/
private String token;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 权限列表
*/
private Set<String> permissions;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
四、总结
本文章对@PerAuthorizre注解结合项目具体如何进行鉴权进行了详细分析,我们在项目使用的过程中除了知道怎么用,也要知其所以然。
如果大家想要了解 spring sercurity+token安全机制是如何做的权限认证和授权,关于SpringSecurity如何集成JWT做的认证授权大家可以参考这篇博主的文章:SpringSecurity集成JWT认证框架_jwt spring security_哆木的博客-CSDN博客)