自定义权限管理:用户即角色

发布时间:2023年12月22日
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

还是一样,只贴出核心代码。

SQL:t_user增加user_type

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `user_type` tinyint(1) NOT NULL DEFAULT '4' COMMENT '用户类型 1-管理员 2-教师 3-学生 4-游客',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted` tinyint(1) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of t_user
-- ----------------------------
BEGIN;
INSERT INTO `t_user` VALUES (1, 'bravo1988', '123456', 1, '2021-02-13 20:01:44', '2021-02-15 10:29:46', 0);
INSERT INTO `t_user` VALUES (2, 'bravo', '123456', 4, '2021-02-14 10:37:25', '2021-02-14 10:37:25', 0);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

权限注解

/**
 * 登录权限注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface LoginRequired {
}
/**
 * 角色权限注解
 * 注意:由于默认权限都是针对用户而言,所以我在PermissionRequired上加了LoginRequired
 */
@LoginRequired
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface PermissionRequired {
    /**
     * 角色,默认游客权限
     *
     * @return
     */
    UserType[] userType() default {UserType.VISITOR};

    /**
     * 逻辑关系,比如 ADMIN&&TEACHER 或者 ADMIN||TEACHER
     *
     * @return
     */
    Logical logical();
}

枚举

/**
 * 当前设计不支持一个用户有多个角色,所以AND暂时用不到,而OR的作用更多的是提示当前逻辑是“符合其中一个角色即可”
 */
@Getter
public enum Logical {
    AND,
    OR;
}
/**
 * 用户角色枚举
 */
@Getter
public enum UserType {
    ADMIN(1, "管理员"),
    TEACHER(2, "教师"),
    STUDENT(3, "学生"),
    VISITOR(4, "游客");

    private final Integer value;
    private final String desc;

    UserType(Integer value, String desc) {
        this.value = value;
        this.desc = desc;
    }

    /**
     * 是否有权限
     *
     * @param userTypes
     * @param typeOfUser
     * @param logical
     * @return
     */
    public static boolean hasPermission(UserType[] userTypes, Integer typeOfUser, Logical logical) {
        Objects.requireNonNull(userTypes);
        Objects.requireNonNull(logical);

        // 1.1 要求单个角色权限,且当前用户刚好是这个角色
        if (userTypes.length == 1 && userTypes[0].getValue().equals(typeOfUser)) {
            return true;
        }
        // 1.2 要求多个角色权限
        if (userTypes.length > 1) {
            // AND:预留,当前设计其实不支持一个用户有多个角色
            if (Logical.AND.equals(logical)) {
                return false;
            }

            // OR:只要用户拥有其中一个角色即可
            if (Logical.OR.equals(logical)) {
                for (UserType type : userTypes) {
                    if (type.getValue().equals(typeOfUser)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }
}

权限拦截(关键代码)

public class SecurityInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 不拦截跨域请求相关
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            return true;
        }

        // 如果方法上没有加@LoginRequired或@PermissionRequired(上面叠加了@LoginRequired),直接放行
        if (isLoginFree(handler)) {
            return true;
        }

        // 登录校验
        User user = handleLogin(request, response);
        ThreadLocalUtil.put(WebConstant.USER_INFO, user);

        // 权限校验
        checkPermission(user, handler);

        // 放行到Controller
        return super.preHandle(request, response, handler);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 及时移除,避免ThreadLocal内存泄漏
        ThreadLocalUtil.remove(WebConstant.USER_INFO);
        super.afterCompletion(request, response, handler, ex);
    }
    
   /**
     * 接口是否免登录(支持Controller上添加@LoginRequired)
     *
     * @param handler
     * @return
     */
    private boolean isLoginFree(Object handler) {

        // 判断是否支持免登录
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;

            // 类上是否有@LoginRequired
            Class<?> controllerClazz = handlerMethod.getBeanType();
            LoginRequired ControllerLogin = AnnotationUtils.findAnnotation(controllerClazz, LoginRequired.class);

            // 方法上是否有@LoginRequired
            Method method = handlerMethod.getMethod();
            LoginRequired methodLogin = AnnotationUtils.getAnnotation(method, LoginRequired.class);

            return ControllerLogin == null && methodLogin == null;
        }

        return true;
    }

    /**
     * 是否需要权限认证(支持Controller上添加@PermissionRequired)
     *
     * @param handler
     * @return
     */
    private boolean isPermissionFree(Object handler) {
        // 判断是否需要权限认证
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Class<?> controllerClazz = handlerMethod.getBeanType();
            Method method = handlerMethod.getMethod();
            PermissionRequired controllerPermission = AnnotationUtils.getAnnotation(controllerClazz, PermissionRequired.class);
            PermissionRequired methodPermission = AnnotationUtils.getAnnotation(method, PermissionRequired.class);
            // 没有加@PermissionRequired,不需要权限认证
            return controllerPermission == null && methodPermission == null;
        }

        return true;
    }

    /**
     * 登录校验
     *
     * @param request
     * @param response
     * @return
     */
    private User handleLogin(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession();
        User currentUser = (User) session.getAttribute(WebConstant.CURRENT_USER_IN_SESSION);
        if (currentUser == null) {
            // 抛异常,请先登录
            throw new BizException(ExceptionCodeEnum.NEED_LOGIN);
        }
        return currentUser;
    }

    /**
     * 权限校验
     *
     * @param user
     * @param handler
     */
    private void checkPermission(User user, Object handler) {
        // 如果方法上没有加@PermissionRequired,直接放行
        if (isPermissionFree(handler)) {
            return;
        }

        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            Class<?> controllerClazz = handlerMethod.getBeanType();

            PermissionRequired controllerPermission = AnnotationUtils.findAnnotation(controllerClazz, PermissionRequired.class);
            PermissionRequired methodPermission = AnnotationUtils.getAnnotation(method, PermissionRequired.class);
            if (hasPermission(controllerPermission, user.getUserType()) && hasPermission(methodPermission, user.getUserType())) {
                return;
            }

            // 代码走到这,说明权限不匹配
            throw new BizException(ExceptionCodeEnum.PERMISSION_DENY);
        }
    }

    private boolean hasPermission(Annotation permissionAnnotation, Integer typeOfUser) {
        if (permissionAnnotation == null) {
            return true;
        }

        UserType[] userTypes = (UserType[]) AnnotationUtils.getValue(permissionAnnotation, "userType");
        Logical logical = (Logical) AnnotationUtils.getValue(permissionAnnotation, "logical");
        // 我把权限判断的逻辑封装到UserType枚举类中复用
        return UserType.hasPermission(userTypes, typeOfUser, logical);
    }

}

测试

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private HttpSession session;

    @PostMapping("/register")
    public Result<User> register(@RequestBody User userInfo) {
        int rows = userMapper.insert(userInfo);
        if (rows > 0) {
            return Result.success(userInfo);
        }

        return Result.error("插入失败");
    }

    @PostMapping("/login")
    public Result<User> login(@RequestBody User loginInfo) {
        LambdaQueryWrapper<User> lambdaQuery = Wrappers.lambdaQuery();
        lambdaQuery.eq(User::getName, loginInfo.getName());
        lambdaQuery.eq(User::getPassword, loginInfo.getPassword());

        User user = userMapper.selectOne(lambdaQuery);
        if (user == null) {
            return Result.error("用户名或密码错误");
        }

        session.setAttribute(WebConstant.CURRENT_USER_IN_SESSION, user);
        return Result.success(user);
    }

    @LoginRequired
    @GetMapping("/needLogin")
    public Result<String> needLogin() {
        return Result.success("if you see this, you are logged in.");
    }

    @GetMapping("/needNotLogin")
    public Result<String> needNotLogin() {
        return Result.success("if you see this, you are logged in.");
    }

    @PermissionRequired(userType = {UserType.ADMIN,UserType.TEACHER}, logical = Logical.OR)
    @GetMapping("/needPermission")
    public Result<String> needPermission() {
        return Result.success("if you see this, you has the permission.");
    }
}
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

文章来源:https://blog.csdn.net/smart_an/article/details/135144269
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。