基于拦截器做权限校验
图
在系统的开发中,基本上都会有权限管理问题,这也是项目设计中不可或缺的一环,目前也有很多成熟的权限管理框架可以选择,如果想基于拦截器手动实现权限管理,自定义权限系统,可以参考以下方式实现

权限校验

在通用的权限管理中,会出现用户、角色、权限三个表,并且设置大多保存菜单列表给前端,如果将粒度改为按钮级别,表中的数据是很多的,每次查询都要关联到很多数据

虽然三者的关系看似多对多(表设计),其实在真正应用时会发现,一个用户进入系统只会选择一个角色,而一个角色对应的也只有一套权限,从这个大方向上看,三者的关系是单一的

那么要做到细粒度的权限控制,只需要规定好每个接口都需要一个权限值,而每个角色能拥有某些权限值,一个用户绑定一个角色即可

实现的方案就是,可以将其表设计简化,大体用户、角色、权限关系模型依然没有变,但是可以将每个接口规定一个权限值(数字),并直接放在角色表中即可,大大简化了复杂度,session中不在缓存那么多数据(session数据量大也会耗内存)

实现方式

定义一个权限列表注解类

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Auth {

    int keyType();

    /**
     * 登录后的权限
     */
    int AUTH_COMMON_LOGIN = 100;

    /**
     * 测试接口权限
     */
    int AUTH_COMMON_TEST = 101;

}

定义拦截器拦截权限

**
 * 请求拦截
 */
public class ReqInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.通过session或toten拿到用户角色(ID或名称)
        // 2.通过角色在缓存或数据库中拿到角色对应的权限值(字符)        
        // 3.拿到接口所需要的权限值
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Auth auth = handlerMethod.getMethodAnnotation(Auth.class);
        // 判断权限值是否包含了接口所需要的值
        if (auths.contains(String.valueOf(auth.keyType()))) {
            // 有权限放行
            return true;
        }
        // 没有权限不放行并设置一个状态码给前端做判断
        response.setStatus(500);
        return false;

    }
}

使用方式,在接口上加权限注解

@RestController
@RequestMapping("/test")
public class TestController {

    @PostMapping("say")
    @Auth(keyType = Auth.AUTH_COMMON_TEST)
    public R<String> test(@RequestBody JSONObject params) {
        System.out.println(params);
        return R.ok("测试成功");
    }

}

复杂业务

有这样一个场景,例如公告管理接口,公告存在于不同的版块中,这对于接口端来说就是一个字段的区别,但是在前端,不同的模块放在了不同的地方,并且对于不同的角色看到的模块不一样,如果基于接口来做的权限,也就是这个接口是否有权访问,如果这个接口有权访问,那么所有模块都有权访问,这不符合需求,如果再写一个接口(就是同样的代码),这就是低级程序员干的事,所以现在需要实现的是接口与权限的一对多关系

使用上面的方式做的权限校验可以轻松的实现

调整注解类

int[] keyType();

使用的时候指定多个权限

@Auth(keyType = {Auth.AUTH_COMMON_NOTICE_LIST,Auth.AUTH_COMMON_LOGIN})

调整拦截器的auth.keyType(),因为此时是个数组

以下内容是二次升级后记录

说明

可实现一下功能:

  • 按钮级别权限控制(即对每个接口进行控制)
  • 一个接口(如列表)支持有一个权限即可查看

实现方式

定义一个权限列表注解类

import java.lang.annotation.*;

/**
 * 自定义注解实现鉴权
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Auth {

    String[] keyType();

    /**
     * 登录后的权限
     */
    String COMMON_LOGIN = "COMMON_LOGIN";

    /**
     * 测试接口权限
     */
    String COMMON_TEST = "COMMON_TEST";

}

定义拦截器拦截做权限判断

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxzn.commonVo.Result;
import com.gxzn.vo.Auth;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * 请求拦截
 */
public class ReqInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        // 获取请求头的token
        String token = request.getHeader("token");
        // 这里根据token查询到用户信息和权限列表
        List<String> auths = new ArrayList<>();
        auths.add("COMMON_LOGIN");
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Auth auth = handlerMethod.getMethodAnnotation(Auth.class);
        // 判断权限值是否包含了接口所需要的值,有一个即可
        if (Objects.nonNull(auth) && Arrays.stream(auth.keyType()).anyMatch(auths::contains)) {
            // 有权限放行
            return true;
        }
        // 没有权限不放行并设置一个状态码给前端做判断
        response.setStatus(200);
        response.setContentType("application/json;charset=UTF-8");
        Result<String> result = new Result<>();
        result.setCode(Result.CODE_UNAUTHORIZED);
        result.setMsg("未授权");
        response.getWriter().write(new ObjectMapper().writeValueAsString(result));
        return false;
    }

}

注册拦截器,指定哪些接口需要拦截

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 拦截器注册
 */
@Configuration
public class Interceptor implements WebMvcConfigurer {

    @Bean
    ReqInterceptor reqInterceptor() {
        return new ReqInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(reqInterceptor()).addPathPatterns("/**")
                .excludePathPatterns("/login");

    }

}

使用方式

/**
 * 测试
 */
@RestController
@RequestMapping("/test")
public class TestController {

    /**
     * 示例
     */
    //    @Auth(keyType = Auth.COMMON_LOGIN) // 单个权限
    @Auth(keyType = {Auth.COMMON_LOGIN, Auth.COMMON_TEST}) // 多个权限有一个即可
    @RequestMapping("/demo")
    public Result<String> demo(@RequestBody SystemAdminUser systemAdminUser) {
        System.out.println(systemAdminUser);
        return Result.successMsg("成功");
    }

}

信息传递

在拦截器中将用户相关的信息放入Attribute中,然后使用工具类去获取即可,例如在拦截器中加入数据:

// 将信息加入到请求头,便于controller使用
request.setAttribute("adminType", userCacheVo.getType());
request.setAttribute("adminId", userCacheVo.getId());

使用工具类进行获取:

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * 用户信息工具
 */
public class UserInfoUtil {

    /**
     * 获取管理员ID
     */
    public static String adminUserId() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.nonNull(requestAttributes)) {
            HttpServletRequest request = requestAttributes.getRequest();
            System.out.println(request.getAttribute("adminId"));
            return (String) request.getAttribute("adminId");
        }
        return "";
    }

    /**
     * 获取管理员类型
     */
    public static Integer adminUserType() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.nonNull(requestAttributes)) {
            HttpServletRequest request = requestAttributes.getRequest();
            System.out.println(request.getAttribute("adminId"));
            return (Integer) request.getAttribute("adminType");
        }
        return 0;
    }

}