Commit 61fe7f51 by 段启岩

分离认证拦截器,修改认证逻辑

parent 5716c9ce
package cn.meteor.beyondclouds.config;
import cn.meteor.beyondclouds.core.interceptor.SubjectInterceptor;
import cn.meteor.beyondclouds.core.interceptor.AccessInterceptor;
import cn.meteor.beyondclouds.core.interceptor.TokenInterceptor;
import cn.meteor.beyondclouds.core.resolver.CurrentSubjectResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -9,7 +10,6 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.List;
......@@ -22,13 +22,21 @@ import java.util.List;
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
SubjectInterceptor subjectInterceptor() {
return new SubjectInterceptor();
TokenInterceptor tokenInterceptor() {
return new TokenInterceptor();
}
@Bean
AccessInterceptor accessInterceptor() {
return new AccessInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(subjectInterceptor())
registry.addInterceptor(tokenInterceptor())
.addPathPatterns("/**");
registry.addInterceptor(accessInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/error")
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
......
......@@ -14,4 +14,9 @@ public class SysConstants {
* 存储在HttpServletRequest里面当前登录的subject的键
*/
public static final String HTTP_ATTRIBUTE_SUBJECT = "current_subject";
/**
* 存储在HttpServletRequest里面的认证错误的信息
*/
public static final String AUTHORIZATION_ERROR_CODE = "AUTHORIZATION_ERROR_CODE";
}
package cn.meteor.beyondclouds.core.interceptor;
import cn.meteor.beyondclouds.core.bean.Subject;
import cn.meteor.beyondclouds.core.constant.SysConstants;
import cn.meteor.beyondclouds.core.emuns.AuthorizationErrorCode;
import cn.meteor.beyondclouds.modules.user.exception.AuthenticationServiceException;
import lombok.extern.java.Log;
import org.apache.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** 访问拦截器
* 判断当前访问者是否经过认证
* 若没有经过认证,则阻止继续访问,并返回错误信息
* 若经过认证,则直接放行
* @author meteor
*/
@Log
public class AccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Subject currentSubject = (Subject) request.getAttribute(SysConstants.HTTP_ATTRIBUTE_SUBJECT);
// 1.目标接口需要认证且未经过认证,则抛出异常
if (null == currentSubject || !currentSubject.authenticated()) {
AuthorizationErrorCode authorizationErrorCode = (AuthorizationErrorCode) request.getAttribute(SysConstants.AUTHORIZATION_ERROR_CODE);
if (null == authorizationErrorCode) {
authorizationErrorCode = AuthorizationErrorCode.NON_HEADER_AUTHORIZATION;
}
// 设置http响应状态
int httpStatus;
switch (authorizationErrorCode) {
case NON_HEADER_AUTHORIZATION:
httpStatus = HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION;
break;
case ILLEGAL_HEADER_AUTHORIZATION:
httpStatus = HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION;
break;
case SIGN_VERIFY_FAILURE:
httpStatus = HttpStatus.SC_UNAUTHORIZED;
break;
default:
httpStatus = HttpStatus.SC_UNAUTHORIZED;
}
response.setStatus(httpStatus);
// 抛出异常
throw new AuthenticationServiceException(authorizationErrorCode);
}
// 2.若不需要认证或者用户认证成功则直接放行
return true;
}
}
package cn.meteor.beyondclouds.core.interceptor;
import cn.meteor.beyondclouds.core.annotation.Anonymous;
import cn.meteor.beyondclouds.util.JwtUtils;
import cn.meteor.beyondclouds.core.bean.Subject;
import cn.meteor.beyondclouds.core.constant.HttpRequestHeaderNames;
import cn.meteor.beyondclouds.core.constant.SysConstants;
import cn.meteor.beyondclouds.core.emuns.AuthorizationErrorCode;
import cn.meteor.beyondclouds.core.exception.AuthorizationException;
import cn.meteor.beyondclouds.util.RequestUtils;
import com.auth0.jwt.interfaces.Claim;
import lombok.extern.java.Log;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/** 用户信息拦截器
* @author meteor
*/
@Log
public class SubjectInterceptor implements HandlerInterceptor {
private static final String BEARER_AUTHORIZATION_START = "Bearer";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/**
* 判断该接口是否需要认证用户才能访问
* 如果目标接口方法上有Anonymous注解,则直接放行
*/
if (((HandlerMethod) handler).hasMethodAnnotation(Anonymous.class)) {
// 构建一个匿名的subject
Subject subject = Subject.anonymous(RequestUtils.getIpAddr(request));
request.setAttribute(SysConstants.HTTP_ATTRIBUTE_SUBJECT, subject);
return true;
}
// 获取请求头Authorization
String authorization = request.getHeader(HttpRequestHeaderNames.AUTHORIZATION);
// 未找到认证信息
if (StringUtils.isEmpty(authorization)) {
response.setStatus(HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION);
throw new AuthorizationException(AuthorizationErrorCode.NON_HEADER_AUTHORIZATION);
}
// 认证信息非法
if (!authorization.startsWith(BEARER_AUTHORIZATION_START)) {
response.setStatus(HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION);
throw new AuthorizationException(AuthorizationErrorCode.ILLEGAL_HEADER_AUTHORIZATION);
}
// 未找到认证信息
if (authorization.length() <= BEARER_AUTHORIZATION_START.length() + 1) {
response.setStatus(HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION);
throw new AuthorizationException(AuthorizationErrorCode.NON_HEADER_AUTHORIZATION);
}
// 获取token
String token = authorization.substring(BEARER_AUTHORIZATION_START.length() + 1);
// 未携带token
if (StringUtils.isEmpty(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
throw new AuthorizationException(AuthorizationErrorCode.NON_ACCESS_TOKEN);
}
// 校验token
try {
Map<String, Claim> claims = JwtUtils.verifyToken(token);
// 构建一个经过系统认证的subject
Subject subject = Subject.authenticated(claims.get(SysConstants.CLAIM_SUBJECT_ID).asString(), RequestUtils.getIpAddr(request));
// 设置当前登录的 subject 到 HttpServletRequest里
request.setAttribute(SysConstants.HTTP_ATTRIBUTE_SUBJECT, subject);
return true;
} catch (Exception e) {
e.printStackTrace();
throw new AuthorizationException(AuthorizationErrorCode.SIGN_VERIFY_FAILURE);
}
}
}
package cn.meteor.beyondclouds.core.interceptor;
import cn.meteor.beyondclouds.core.bean.Subject;
import cn.meteor.beyondclouds.core.constant.HttpRequestHeaderNames;
import cn.meteor.beyondclouds.core.constant.SysConstants;
import cn.meteor.beyondclouds.core.emuns.AuthorizationErrorCode;
import cn.meteor.beyondclouds.util.JwtUtils;
import cn.meteor.beyondclouds.util.RequestUtils;
import com.auth0.jwt.interfaces.Claim;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* Token拦截器,根据token信息解析出subject
* 如果没有携带认证信息或认证信息错误,则在HttpServlet上下文设置一个匿名的Subject,并设置认证的错误信息,然后对请求进行放行
* 如果解析到正确的token,则在HttpServlet上下文设置一个经过认证的Subject,然后对请求进行放行
*
* @author meteor
*/
public class TokenInterceptor implements HandlerInterceptor {
private static final String BEARER_AUTHORIZATION_START = "Bearer";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authorization = request.getHeader(HttpRequestHeaderNames.AUTHORIZATION);
// 匿名subject
Subject anonymousSubject = Subject.anonymous(RequestUtils.getIpAddr(request));
// 1.未找到认证信息
if (StringUtils.isEmpty(authorization)) {
request.setAttribute(SysConstants.HTTP_ATTRIBUTE_SUBJECT, anonymousSubject);
request.setAttribute(SysConstants.AUTHORIZATION_ERROR_CODE, AuthorizationErrorCode.NON_HEADER_AUTHORIZATION);
return true;
}
// 2.找到认证信息但是认证信息非法
if (!authorization.startsWith(BEARER_AUTHORIZATION_START) || authorization.length() <= BEARER_AUTHORIZATION_START.length() + 1) {
request.setAttribute(SysConstants.HTTP_ATTRIBUTE_SUBJECT, anonymousSubject);
request.setAttribute(SysConstants.AUTHORIZATION_ERROR_CODE, AuthorizationErrorCode.ILLEGAL_HEADER_AUTHORIZATION);
return true;
}
// 3.找到认证信息,进行认证信息正确性校验,并生成Subject
try {
// 获取token
String token = authorization.substring(BEARER_AUTHORIZATION_START.length() + 1);
// 校验token
Map<String, Claim> claims = JwtUtils.verifyToken(token);
// 构建一个经过系统认证的subject
Subject authenticatedSubject = Subject.authenticated(claims.get(SysConstants.CLAIM_SUBJECT_ID).asString(), RequestUtils.getIpAddr(request));
request.setAttribute(SysConstants.HTTP_ATTRIBUTE_SUBJECT, authenticatedSubject);
return true;
} catch (Exception e) {
e.printStackTrace();
// token校验失败
request.setAttribute(SysConstants.HTTP_ATTRIBUTE_SUBJECT, anonymousSubject);
request.setAttribute(SysConstants.AUTHORIZATION_ERROR_CODE, AuthorizationErrorCode.SIGN_VERIFY_FAILURE);
return true;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment