package com.estate.expert.common.auth;
|
|
import com.alibaba.fastjson.JSONObject;
|
import com.auth0.jwt.JWT;
|
import com.auth0.jwt.algorithms.Algorithm;
|
import com.estate.expert.common.exception.TokenExpiredException;
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
import com.estate.expert.common.enums.AppEnv;
|
import com.estate.expert.common.exception.BizException;
|
import com.google.common.base.Strings;
|
import com.google.common.io.BaseEncoding;
|
import lombok.AllArgsConstructor;
|
import lombok.Builder;
|
import lombok.Data;
|
import lombok.ToString;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.Order;
|
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import java.lang.annotation.Annotation;
|
import java.security.KeyFactory;
|
import java.security.interfaces.RSAPublicKey;
|
import java.security.spec.X509EncodedKeySpec;
|
import java.util.Objects;
|
|
/**
|
* 认证拦截器
|
*/
|
@Slf4j
|
@Order(100)
|
public class AuthInterceptor implements HandlerInterceptor {
|
|
public final static String AUTH_PAYLOAD_KEY = "_auth_payload_";
|
public final static String CALLER_PAYLOAD_KEY = "_caller_payload_";
|
|
private Algorithm algorithm;
|
|
private static final String AUTH_HEADER = "Authorization";
|
|
private static final String SUPPORT_AUTH = "Bearer";
|
|
public static final String ORIGINAL_CALLER_HEADER_NAME = "xg-original-caller";
|
|
public AuthInterceptor(String publicKey) throws Exception {
|
Objects.requireNonNull(publicKey);
|
|
RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
|
.generatePublic(new X509EncodedKeySpec(BaseEncoding.base64().decode(publicKey)));
|
|
this.algorithm = Algorithm.RSA256(rsaPublicKey, null);
|
}
|
|
/**
|
* 预处理
|
* @param request 请求
|
* @param response 响应
|
* @param handler 处理器
|
* @return 是否继续处理
|
*/
|
@Override
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
log.info("AuthInterceptor::preHandle::Start Recv ReqBody ==> {}", request.getRequestURI());
|
if (!(handler instanceof HandlerMethod)) {
|
return true;
|
}
|
IgnoreAuth ignoreAuth = getAnnotation((HandlerMethod) handler, IgnoreAuth.class);
|
if (ignoreAuth != null) {
|
return true;
|
}
|
|
Token token = Token.from(request.getHeader(AUTH_HEADER));
|
if (!StringUtils.equals(SUPPORT_AUTH, token.getType())) {
|
throw new BizException(AuthResultCode.API_AUTH_NOT_SUPPORTED);
|
}
|
|
try {
|
DecodedJWT jwt = JWT.require(algorithm).build()
|
.verify(token.getToken());
|
AuthCaller payload = AuthCaller.builder()
|
.appId(jwt.getClaim("appId").asString())
|
.appEnv(AppEnv.valueOf(jwt.getClaim("appEnv").asString()))
|
.ext(jwt.getClaim("ext").asString())
|
.build();
|
|
request.setAttribute(AUTH_PAYLOAD_KEY, payload);
|
|
OriginalCaller caller;
|
String originalCallerHeader = request.getHeader(ORIGINAL_CALLER_HEADER_NAME);
|
if (!Strings.isNullOrEmpty(originalCallerHeader)) {
|
caller = JSONObject.parseObject(originalCallerHeader, OriginalCaller.class);
|
} else {
|
caller = OriginalCaller.fromAuthCaller(payload);
|
}
|
request.setAttribute(CALLER_PAYLOAD_KEY, caller);
|
} catch (TokenExpiredException e) {
|
response.setStatus(TokenExpiredException.STATUS_CODE);
|
throw new BizException(AuthResultCode.API_AUTH_TOKEN_EXPIRED);
|
} catch (Exception e) {
|
log.error("api auth, token is invalid. token = {}", token, e);
|
throw new BizException(AuthResultCode.API_AUTH_TOKEN_INVALID);
|
}
|
|
return true;
|
}
|
|
/**
|
* 获取注解
|
* @param handlerMethod 处理器方法
|
* @param clazz 注解类
|
* @return 注解
|
*/
|
private <T extends Annotation> T getAnnotation(HandlerMethod handlerMethod, Class<T> clazz) {
|
T annotation = handlerMethod.getMethodAnnotation(clazz);
|
if (annotation != null) {
|
return annotation;
|
}
|
annotation = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), clazz);
|
return annotation;
|
}
|
|
@Data
|
@Builder
|
@AllArgsConstructor
|
@ToString
|
private static class Token {
|
private static final transient String DELIMITER = StringUtils.SPACE;
|
|
private String type;
|
|
private String token;
|
|
/**
|
* 从字符串中获取Token
|
* @param header 字符串
|
* @return Token
|
*/
|
public static Token from(String header) {
|
return Token.builder()
|
.type(StringUtils.substringBefore(header, DELIMITER))
|
.token(StringUtils.substringAfter(header, DELIMITER))
|
.build();
|
}
|
}
|
}
|