这是我参与「第五届青训营 」伴学笔记创作活动的第 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);
}