🔞登录和权限校验
权限设计的最终目标就是定义每个用户可以在系统中做哪些事情。
当我们谈到权限的时候,一般可以分为 功能权限、数据权限和字段权限;
-
功能权限:
用户具有哪些权利,比如特定单据的增、删、改、查、审批、反审批等等;一般按照一个人在组织内的工作内容来划分;比如一个单据往往有录入人和审批人,录入人具有增、删、该、查的权限;而审批人具有审批、反审批和查询的权限。有时,功能权限被细分为页面权限和操作权限。
页面权限:目录、菜单,… 操作权限:按钮、外链
上面内容,前端要实现,后端也要实现,但是实现的框架不同,
先说前端:
前端的权限就是:是否展示这个组件出来,组件是否禁用/启用
采用了若依框架:
若依框架自定义了一个符号v-hasPerm=“[‘system:role:add’]”,它是根据用户所拥有的权限列表中是否包含v-**hasPerm=“[‘system:role:add’]”**中的权限,如果不包含就移除这个组件,用户所拥有的权限列表在全局状态中
这个符号的实现在directie中的permission中,如果角色是管理员就直接通过,其他角色则校验binding的[‘system:role:add’]是否在perms列表中,决定是否移除这个组件
这个接口返回用户信息和权限集合
然后就到后端的了
后端具体的就是需要拦截哪些请求,放行哪些请求,因为有些是不需要登录也可以访问的,比如我们不登录也可以看抖音,但是我们却不可以点赞和写评论;
而且我们还要判断用户所属的角色是否有权限做某个操作,上面已经举例了
这里举例使用Sa-token框架进行权限校验,不同权限框架都大同小异
如果是采用注解式的AOP,只要引入依赖,并配置好拦截器的配置类
<!-- sa-token整合SpringAOP实现注解鉴权 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-aop</artifactId>
<version>1.19.0</version>
</dependency>
最初肯定是登录校验了
使用Sa-Token进行登录认证
tpUtil.setLoginId(Object Id)
只此一句代码,便可以使会话登录成功,实际上,Sa-Token 在背后做了大量的工作,包括但不限于:
检查此账号是否之前已有登录;
为账号生成 Token 凭证与 Session 会话;
记录 Token 活跃时间;
通知全局侦听器,xx 账号登录成功;
将 Token 注入到请求上下文;
等等其它工作……
我们暂时不需要完整了解整个登录过程,只需要记住关键一点:Sa-Token 为这个账号创建了一个Token凭证,且通过 Cookie 上下文返回给了前端。
然后在拦截器里面添加Sa-token拦截器,标注需要拦截和放行的请求
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
// @Value("${file.upload-folder}")
private String UPLOAD_FOLDER="E:\\dowload\\work\\java\\项目\\yshopmall";
/**
* 注册sa-token的拦截器,打开注解式鉴权功能 (如果您不需要此功能,可以删除此类)
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//分页拦截器
registry.addInterceptor(new PageableInterceptor());
// 注册Sa-Token的路由拦截器
registry.addInterceptor(new SaRouteInterceptor())
.addPathPatterns("/system/**").excludePathPatterns("/login","/logout","/verify");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars/");
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/img/**").addResourceLocations("file:" + UPLOAD_FOLDER);
}
/**
* 注册跨域信息
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 允许所有跨域地址
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(3600);
}
}
然后就需要写用户的操作权限了
获得用户的所有角色和所拥有的操作权限,返回的数组里面不能包含NULL的元素
public interface StpInterface {
List<String> getPermissionList(Object var1, String var2);
List<String> getRoleList(Object var1, String var2);
}
然后呢,在收到请求,后端会校验用户所拥有的权限集合中是否包含这个操作所需要的权限
@PutMapping(value = "/update")
@OperationLogger(value = "修改反馈")
@SaCheckPermission("system:feedback:update") //这个注解是Sa-token框架的注解
@ApiOperation(value = "修改反馈", httpMethod = "PUT", response = ResponseResult.class, notes = "修改反馈")
public ResponseResult update(@RequestBody FeedBack feedBack) {
return feedBackService.updateFeedBack(feedBack);
}
Sa-token框架中不同用户的所属权限已经在重写的getPermissionLis()方法中根据用户Id获取了,进入标记了@SaCheckPermission注解的方法时,会先搜索用户的权限集合是否包含,如同前端的校验一样
这个是通过切入ascept,在执行方法前,通过注解,判断当前用户的角色,以此决定数据的权限,然后在SQL查询中,动态拼接SQL完成数据查看范围的差异
自己新建一个注解,然后定义在注解执行时切入什么
@Data
public class BaseEntity implements Serializable {
@Transient //这个注解标识这个属性不存在与数据库,不会产生映射,但是又可以在程序中使用这个参数
private Map<String, Object> params=new HashMap<>();
}
public class Say extends BaseEntity {}
@Aspect
@Component
public class DataScopeAspect
{
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
@Autowired
UserMapper userMapper;
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
// 配置织入点
@Pointcut("@annotation(com.shiyi.utils.DataScope)")
public void dataScopePointCut()
{
}
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) throws Throwable
{
System.out.println("这里执行的切入");
handleDataScope(point);
}
protected void handleDataScope(final JoinPoint joinPoint)
{
// 获得注解
DataScope controllerDataScope = getAnnotationLog(joinPoint);
if (controllerDataScope == null)
{
return;
}
// 获取当前的用户
User loginUser=userMapper.selectById((Serializable) StpUtil.getLoginId());
dataScopeFilter(joinPoint,loginUser);
}
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
*/
public static void dataScopeFilter(JoinPoint joinPoint, User user)
{
StringBuilder sqlString = new StringBuilder();
if(user.getRoleId()==1){
sqlString.append("1");
}
else {
sqlString.append(StringUtils.format("user_id ="+user.getId()));
}
Object params = joinPoint.getArgs()[0];
// System.out.println("打印了用户ID:"+user.getId());
if (params!=null && params instanceof BaseEntity)
{
Map<String,Object>mp=new HashMap<>();
mp.put("dataScope"," (" + sqlString.toString() + ")");
((BaseEntity) params).setParams(mp);
}
}
/**
* 是否存在注解,如果存在就获取
*/
private DataScope getAnnotationLog(JoinPoint joinPoint)
{
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
return method.getAnnotation(DataScope.class);
}
return null;
}
}
我设置的SQL语句是如果是管理员可以查看所有的数据
而用户只可以查看自己的数据
3.字段权限:在特定的单据中,可以看到哪些字段;比如针对入库单,财务人员能看到采购成本,而库管员看不到等等。字段权限和数据权限的区别在于,数据权限规定了哪些行的数据看不到,而字段权限规定了哪些列的数据看不到;这种权限设计现在见的比较少了,因为如果两个用户看到的列都不一样,通过设计不同的表单也能实现,此时字段权限就转换为功能权限了。