登录鉴权简单实现 | 青训营笔记

234 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天

作为一个用户系统,登录是必不可少的,登录除了记录用户外,也是分辨用户进行鉴权的必不可少的一步。今天来简单记录一下我学习到的一种鉴权思路。

注解定义

  • @NoNeedLogin:该注解用在方法上,有该注解的方法意味着不用登录就可以直接访问
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface NoNeedLogin {
    }
    
  • @NoValidPrivilege:该注解用在方法上,有该注解的方法意味着不用鉴权就可以直接访问。
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NoValidPrivilege {
    
    }
    
  • @NotOpen:该注解用在方法上,有该注解的方法意味着停止访问。
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface NotOpen {
    }
    

登录拦截器

@Slf4j
@Component
public class SmartAuthenticationInterceptor extends HandlerInterceptorAdapter {

    public static final String TOKEN_NAME = "request-token";

    @Value("${access-control-allow-origin}")
    private String accessControlAllowOrigin;

    @Autowired
    private LoginTokenService loginTokenService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private PageOptionService pageOptionService;

    /**
     * 拦截服务器端响应处理ajax请求返回结果
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //跨域设置
        this.crossDomainConfig(response);
        boolean isHandlerMethod = handler instanceof HandlerMethod;
        if (! isHandlerMethod) {
            return true;
        }

        //不开放的注解
        boolean isNotOpen = ((HandlerMethod) handler).getMethodAnnotation(NotOpen.class) != null;
        if (isNotOpen){
            this.outputResult(response,ResponseCodeConst.NOT_OPEN);
            return false;
        }

        //不需要登录的注解
        Boolean isNoNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class) != null;
        if (isNoNeedLogin) {
            return true;
        }

        //放行的Uri前缀
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();
        String target = uri.replaceFirst(contextPath, "");
        if (CommonConst.CommonCollection.contain(CommonConst.CommonCollection.IGNORE_URL, target)) {
            return true;
        }

        //需要做token校验, 消息头的token优先于请求query参数的token
        String xHeaderToken = request.getHeader(TOKEN_NAME);
        String xRequestToken = request.getParameter(TOKEN_NAME);
        String xAccessToken = null != xHeaderToken ? xHeaderToken : xRequestToken;
        if (null == xAccessToken) {
            this.outputResult(response, ResponseCodeConst.NOT_LOGIN);
            return false;
        }

        //根据token获取登录用户
        Long uid = loginTokenService.getUidByToken(xAccessToken);
        //token无效或token过期
        if(Objects.isNull(uid)){
            this.outputResult(response,ResponseCodeConst.NOT_LOGIN);
            return false;
        }

        //根据uid从缓存获取用户数据
        RequestUserData requestUserData = redisService.getObject(RedisConstants.LOGIN_USER_REQUEST_DATA + uid,
                RequestUserData.class);
        if (Objects.isNull(requestUserData)){
            //redis过期也是无效登录
            this.outputResult(response,ResponseCodeConst.NOT_LOGIN);
            return false;
        }

        //判断账号是否被停用
        if(requestUserData.getStatus().equals(StatusEnum.DISABLED)){
            this.outputResult(response,new ResponseCodeConst("账号已被管理员停止使用!"));
            return false;
        }

        //判断接口权限
        String methodName = ((HandlerMethod) handler).getMethod().getName();
        String className = ((HandlerMethod) handler).getBeanType().getName();
        List<String> list = SmartStringUtil.splitConvertToList(className, "\.");
        String controllerName = list.get(list.size() - 1);
        Method m = ((HandlerMethod) handler).getMethod();
        Class<?> cls = ((HandlerMethod) handler).getBeanType();
        boolean isClzAnnotation = cls.isAnnotationPresent(NoValidPrivilege.class);
        boolean isMethodAnnotation = m.isAnnotationPresent(NoValidPrivilege.class);
        NoValidPrivilege noValidPrivilege = null;
        if (isClzAnnotation) {
            noValidPrivilege = cls.getAnnotation(NoValidPrivilege.class);
        } else if (isMethodAnnotation) {
            noValidPrivilege = m.getAnnotation(NoValidPrivilege.class);
        }

        //绕过权限验证
        if(true) {
            SmartRequestTokenUtil.setUser(requestUserData);
            return true;
        }

        //不需验证权限
        if (noValidPrivilege != null) {
            SmartRequestTokenUtil.setUser(requestUserData);
            return true;
        }

        //需要验证权限
        boolean privilegeValidPass = pageOptionService.checkRoleHavePrivilege(requestUserData.getRoleId(),
                controllerName,methodName);
        if (!privilegeValidPass) {
            this.outputResult(response, ResponseCodeConst.NOT_HAVE_PRIVILEGES);
            return false;
        }
        SmartRequestTokenUtil.setUser(requestUserData);
        return true;
    }



    /**
     * 配置跨域
     *
     * @param response
     */
    private void crossDomainConfig(HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Origin", accessControlAllowOrigin);
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
        response.setHeader("Access-Control-Expose-Headers", "*");
        response.setHeader("Access-Control-Allow-Headers", "Authentication,Origin, X-Requested-With, Content-Type, " + "Accept, x-access-token");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires ", "-1");
    }

    /**
     * 错误输出
     *
     * @param response
     * @param responseCodeConst
     * @throws IOException
     */
    private void outputResult(HttpServletResponse response, ResponseCodeConst responseCodeConst) throws IOException {
        ResponseDTO<Object> error = ResponseDTO.error(responseCodeConst);
        String msg = JSONObject.toJSONString(error);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(msg);
        response.flushBuffer();
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                Exception ex) throws Exception {
        SmartRequestTokenUtil.removeUser();
    }
}

鉴权方法

public boolean checkRoleHavePrivilege(Integer roleId,String controllerName,String methodName) {
    if(roleId == null) {
        return false;
    }
    ConcurrentMap<String, List<String>> map = rolePrivilegeMap.get(roleId);
    if(map == null) {
        return false;
    }
    List<String> methodList = map.get(controllerName);
    if(CollectionUtils.isEmpty(methodList)) {
        return false;
    }
    return methodList.contains(methodName);
}