增加 auth 认证拦截器

This commit is contained in:
YunaiV
2020-04-21 23:21:03 +08:00
parent 6bcad5d53f
commit eec8f0860e
56 changed files with 621 additions and 439 deletions

View File

@@ -4,6 +4,7 @@ import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.vo.CommonResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -100,6 +101,11 @@ public class ServiceExceptionUtil {
return new ServiceException(code, message);
}
public static ServiceException exception(CommonResult result) {
Assert.isTrue(result.isError(), "结果必须是错误的");
return new ServiceException(result.getCode(), result.getMessage());
}
/**
* 将错误编号对应的消息使用 params 进行格式化。
*

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-spring-boot-starter-security</artifactId>
<dependencies>
<!-- Mall 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>system-rpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
<optional>true</optional>
</dependency>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>mall-spring-boot-starter-web</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- RPC 相关 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,16 @@
package cn.iocoder.mall.security.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(name = {"cn.iocoder.mall.system.rpc.api.systemlog.SystemLogRPC", "org.apache.dubbo.config.annotation.Reference"})
public class CommonSecurityAutoConfiguration implements WebMvcConfigurer {
// ========== 拦截器相关 ==========
}

View File

@@ -0,0 +1,40 @@
package cn.iocoder.mall.security.core.account;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.rpc.api.oauth2.OAuth2RPC;
import cn.iocoder.mall.system.rpc.request.oauth2.OAuth2AccessTokenAuthenticateRequest;
import cn.iocoder.mall.system.rpc.response.oauth2.OAuth2AccessTokenResponse;
import cn.iocoder.mall.web.core.util.CommonWebUtil;
import org.apache.dubbo.config.annotation.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AccountAuthInterceptor extends HandlerInterceptorAdapter {
private Logger logger = LoggerFactory.getLogger(getClass());
@Reference(validation = "true", version = "${dubbo.consumer.OAuth2RPC.version}")
private OAuth2RPC oauth2RPC;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 执行认证
String accessToken = HttpUtil.obtainAuthorization(request);
OAuth2AccessTokenAuthenticateRequest oauth2AccessTokenAuthenticateRequest = new OAuth2AccessTokenAuthenticateRequest()
.setAccessToken(accessToken).setIp(HttpUtil.getIp(request));
CommonResult<OAuth2AccessTokenResponse> oauth2AccessTokenResponseResult = oauth2RPC.authenticate(oauth2AccessTokenAuthenticateRequest);
if (oauth2AccessTokenResponseResult.isError()) { // TODO 有一个问题点,假设 token 认证失败,但是该 url 是无需认证的,是不是一样能够执行过去?
throw ServiceExceptionUtil.exception(oauth2AccessTokenResponseResult);
}
// 设置账号编号
CommonWebUtil.setAccountId(request, oauth2AccessTokenResponseResult.getData().getAccountId());
return true;
}
}

View File

@@ -0,0 +1 @@
package cn.iocoder.mall.security.core;

View File

@@ -0,0 +1 @@
package cn.iocoder.mall.security;

View File

@@ -37,6 +37,7 @@
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

View File

@@ -1,8 +1,9 @@
package cn.iocoder.mall.web.config;
import cn.iocoder.mall.web.constant.CommonMallConstants;
import cn.iocoder.mall.web.handler.GlobalResponseBodyHandler;
import cn.iocoder.mall.web.interceptor.AccessLogInterceptor;
import cn.iocoder.mall.web.core.constant.CommonMallConstants;
import cn.iocoder.mall.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.mall.web.core.handler.GlobalResponseBodyHandler;
import cn.iocoder.mall.web.core.interceptor.AccessLogInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -28,10 +29,16 @@ public class CommonWebAutoConfiguration implements WebMvcConfigurer {
return new GlobalResponseBodyHandler();
}
@Bean
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
}
// ========== 拦截器相关 ==========
@Bean
@ConditionalOnClass(name = "cn.iocoder.mall.system.rpc.api.SystemLogRPC")
@ConditionalOnClass(name = {"cn.iocoder.mall.system.rpc.api.systemlog.SystemLogRPC", "org.apache.dubbo.config.annotation.Reference"})
@ConditionalOnMissingBean(AccessLogInterceptor.class)
public AccessLogInterceptor accessLogInterceptor() {
return new AccessLogInterceptor();

View File

@@ -1,4 +1,4 @@
package cn.iocoder.mall.web.constant;
package cn.iocoder.mall.web.core.constant;
public interface CommonMallConstants {

View File

@@ -1,4 +1,4 @@
package cn.iocoder.mall.spring.boot.web.handler;
package cn.iocoder.mall.web.core.handler;
import cn.iocoder.common.framework.constant.SysErrorCodeEnum;
import cn.iocoder.common.framework.exception.ServiceException;
@@ -6,12 +6,10 @@ import cn.iocoder.common.framework.util.ExceptionUtil;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.MallUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.api.SystemLogService;
import cn.iocoder.mall.system.api.dto.systemlog.AccessLogAddDTO;
import cn.iocoder.mall.system.api.dto.systemlog.ExceptionLogAddDTO;
import cn.iocoder.mall.system.rpc.api.systemlog.SystemLogRPC;
import cn.iocoder.mall.system.rpc.request.systemlog.ExceptionLogAddRequest;
import cn.iocoder.mall.web.core.util.CommonWebUtil;
import com.alibaba.fastjson.JSON;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.slf4j.Logger;
@@ -20,32 +18,37 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.util.Assert;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.util.Date;
@ControllerAdvice
/**
* 全局异常处理器 Exception 翻译成 CommonResult + 对应的异常编号
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 异常总数 Metrics
*/
private static final Counter EXCEPTION_COUNTER = Metrics.counter("mall.exception.total");
// TODO 芋艿应该还有其它的异常需要进行翻译
// /**
// * 异常总数 Metrics
// */
// private static final Counter EXCEPTION_COUNTER = Metrics.counter("mall.exception.total");
private Logger logger = LoggerFactory.getLogger(getClass());
@Value("${spring.application.name}")
private String applicationName;
@Reference(validation = "true", version = "${dubbo.consumer.AdminAccessLogService.version:1.0.0}")
private SystemLogService systemLogService;
// TODO 目前存在一个问题如果未引入 system-rpc-api 依赖GlobalExceptionHandler 会报类不存在未来封装出 Repository 解决该问题
@Reference(validation = "true", version = "${dubbo.consumer.SystemLogRPC.version}")
private SystemLogRPC systemLogRPC;
// 逻辑异常
@ResponseBody
@ExceptionHandler(value = ServiceException.class)
public CommonResult serviceExceptionHandler(HttpServletRequest req, ServiceException ex) {
logger.debug("[serviceExceptionHandler]", ex);
@@ -53,14 +56,12 @@ public class GlobalExceptionHandler {
}
// Spring MVC 参数不正确
@ResponseBody
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
return CommonResult.error(SysErrorCodeEnum.MISSING_REQUEST_PARAM_ERROR.getCode(), SysErrorCodeEnum.MISSING_REQUEST_PARAM_ERROR.getMessage() + ":" + ex.getMessage());
}
@ResponseBody
@ExceptionHandler(value = ConstraintViolationException.class)
public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
logger.info("[constraintViolationExceptionHandler]", ex);
@@ -68,20 +69,18 @@ public class GlobalExceptionHandler {
// 拼接详细报错
StringBuilder detailMessage = new StringBuilder("\n\n详细错误如下");
ex.getConstraintViolations().forEach(constraintViolation -> detailMessage.append("\n").append(constraintViolation.getMessage()));
return CommonResult.error(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(), SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getMessage()
+ detailMessage.toString());
return CommonResult.error(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(),
SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getMessage() + detailMessage.toString());
}
// TODO 芋艿应该还有其它的异常需要进行翻译
@ResponseBody
@ExceptionHandler(value = Exception.class)
public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
logger.error("[exceptionHandler]", e);
// 插入异常日志
ExceptionLogAddDTO exceptionLog = new ExceptionLogAddDTO();
ExceptionLogAddRequest exceptionLog = new ExceptionLogAddRequest();
try {
// 增加异常计数 metrics
EXCEPTION_COUNTER.increment();
// 增加异常计数 metrics TODO 暂时去掉
// EXCEPTION_COUNTER.increment();
// 初始化 exceptionLog
initExceptionLog(exceptionLog, req, e);
// 执行插入 exceptionLog
@@ -93,13 +92,9 @@ public class GlobalExceptionHandler {
return CommonResult.error(SysErrorCodeEnum.SYS_ERROR.getCode(), SysErrorCodeEnum.SYS_ERROR.getMessage());
}
private void initExceptionLog(ExceptionLogAddDTO exceptionLog, HttpServletRequest request, Exception e) {
// 设置用户编号
exceptionLog.setUserId(MallUtil.getUserId(request));
if (exceptionLog.getUserId() == null) {
exceptionLog.setUserId(AccessLogAddDTO.USER_ID_NULL);
}
exceptionLog.setUserType(MallUtil.getUserType(request));
private void initExceptionLog(ExceptionLogAddRequest exceptionLog, HttpServletRequest request, Exception e) {
// 设置账号编号
exceptionLog.setAccountId(CommonWebUtil.getAccountId(request));
// 设置异常字段
exceptionLog.setExceptionName(e.getClass().getName());
exceptionLog.setExceptionMessage(ExceptionUtil.getMessage(e));
@@ -124,8 +119,13 @@ public class GlobalExceptionHandler {
}
@Async
public void addExceptionLog(ExceptionLogAddDTO exceptionLog) {
systemLogService.addExceptionLog(exceptionLog);
public void addExceptionLog(ExceptionLogAddRequest exceptionLog) {
try {
systemLogRPC.addExceptionLog(exceptionLog);
} catch (Throwable th) {
logger.error("[addAccessLog][插入异常日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
}
}
}

View File

@@ -1,7 +1,7 @@
package cn.iocoder.mall.web.handler;
package cn.iocoder.mall.web.core.handler;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.web.util.CommonWebUtil;
import cn.iocoder.mall.web.core.util.CommonWebUtil;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
@@ -18,7 +18,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
* 原因是GlobalResponseBodyHandler 本质上是 AOP它不应该改变 Controller 返回的数据结构
*
* 目前GlobalResponseBodyHandler 的主要作用是记录 Controller 的返回结果
* 方便 {@link cn.iocoder.mall.web.interceptor.AccessLogInterceptor} 记录访问日志
* 方便 {@link cn.iocoder.mall.web.core.interceptor.AccessLogInterceptor} 记录访问日志
*/
@ControllerAdvice
public class GlobalResponseBodyHandler implements ResponseBodyAdvice {

View File

@@ -1,11 +1,11 @@
package cn.iocoder.mall.web.interceptor;
package cn.iocoder.mall.web.core.interceptor;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.MallUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.rpc.api.SystemLogRPC;
import cn.iocoder.mall.system.rpc.request.system.AccessLogAddRequest;
import cn.iocoder.mall.web.util.CommonWebUtil;
import cn.iocoder.mall.system.rpc.api.systemlog.SystemLogRPC;
import cn.iocoder.mall.system.rpc.request.systemlog.AccessLogAddRequest;
import cn.iocoder.mall.web.core.util.CommonWebUtil;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.dubbo.config.annotation.Reference;
@@ -55,7 +55,7 @@ public class AccessLogInterceptor extends HandlerInterceptorAdapter {
}
private void initAccessLog(AccessLogAddRequest accessLog, HttpServletRequest request) {
// 设置用户编号
// 设置账号编号
accessLog.setAccountId(CommonWebUtil.getAccountId(request));
// 设置访问结果
CommonResult result = CommonWebUtil.getCommonResult(request);

View File

@@ -1,7 +1,7 @@
package cn.iocoder.mall.web.util;
package cn.iocoder.mall.web.core.util;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.web.constant.CommonMallConstants;
import cn.iocoder.mall.web.core.constant.CommonMallConstants;
import javax.servlet.ServletRequest;
import java.util.Date;

View File

@@ -1,103 +0,0 @@
package cn.iocoder.mall.web.handler;
//@ControllerAdvice
//public class GlobalExceptionHandler {
//
//// /**
//// * 异常总数 Metrics
//// */
//// private static final Counter EXCEPTION_COUNTER = Metrics.counter("mall.exception.total");
//
// private Logger logger = LoggerFactory.getLogger(getClass());
//
// @Value("${spring.application.name}")
// private String applicationName;
//
// @Reference(validation = "true", version = "${dubbo.consumer.AdminAccessLogService.version:1.0.0}")
// private SystemLogService systemLogService;
//
// // 逻辑异常
// @ResponseBody
// @ExceptionHandler(value = ServiceException.class)
// public CommonResult serviceExceptionHandler(HttpServletRequest req, ServiceException ex) {
// logger.debug("[serviceExceptionHandler]", ex);
// return CommonResult.error(ex.getCode(), ex.getMessage());
// }
//
// // Spring MVC 参数不正确
// @ResponseBody
// @ExceptionHandler(value = MissingServletRequestParameterException.class)
// public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
// logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
// return CommonResult.error(SysErrorCodeEnum.MISSING_REQUEST_PARAM_ERROR.getCode(), SysErrorCodeEnum.MISSING_REQUEST_PARAM_ERROR.getMessage() + ":" + ex.getMessage());
// }
//
// @ResponseBody
// @ExceptionHandler(value = ConstraintViolationException.class)
// public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
// logger.info("[constraintViolationExceptionHandler]", ex);
// // TODO 芋艿,后续要想一个更好的方式。
// // 拼接详细报错
// StringBuilder detailMessage = new StringBuilder("\n\n详细错误如下");
// ex.getConstraintViolations().forEach(constraintViolation -> detailMessage.append("\n").append(constraintViolation.getMessage()));
// return CommonResult.error(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(), SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getMessage()
// + detailMessage.toString());
// }
//
// // TODO 芋艿,应该还有其它的异常,需要进行翻译
// @ResponseBody
// @ExceptionHandler(value = Exception.class)
// public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
// logger.error("[exceptionHandler]", e);
// // 插入异常日志
// ExceptionLogAddDTO exceptionLog = new ExceptionLogAddDTO();
// try {
// // 增加异常计数 metrics
// EXCEPTION_COUNTER.increment();
// // 初始化 exceptionLog
// initExceptionLog(exceptionLog, req, e);
// // 执行插入 exceptionLog
// addExceptionLog(exceptionLog);
// } catch (Throwable th) {
// logger.error("[exceptionHandler][插入访问日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
// }
// // 返回 ERROR CommonResult
// return CommonResult.error(SysErrorCodeEnum.SYS_ERROR.getCode(), SysErrorCodeEnum.SYS_ERROR.getMessage());
// }
//
// private void initExceptionLog(ExceptionLogAddDTO exceptionLog, HttpServletRequest request, Exception e) {
// // 设置用户编号
// exceptionLog.setUserId(MallUtil.getUserId(request));
// if (exceptionLog.getUserId() == null) {
// exceptionLog.setUserId(AccessLogAddDTO.USER_ID_NULL);
// }
// exceptionLog.setUserType(MallUtil.getUserType(request));
// // 设置异常字段
// exceptionLog.setExceptionName(e.getClass().getName());
// exceptionLog.setExceptionMessage(ExceptionUtil.getMessage(e));
// exceptionLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
// exceptionLog.setExceptionStackTrace(ExceptionUtil.getStackTrace(e));
// StackTraceElement[] stackTraceElements = e.getStackTrace();
// Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
// StackTraceElement stackTraceElement = stackTraceElements[0];
// exceptionLog.setExceptionClassName(stackTraceElement.getClassName());
// exceptionLog.setExceptionFileName(stackTraceElement.getFileName());
// exceptionLog.setExceptionMethodName(stackTraceElement.getMethodName());
// exceptionLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
// // 设置其它字段
// exceptionLog.setTraceId(MallUtil.getTraceId())
// .setApplicationName(applicationName)
// .setUri(request.getRequestURI()) // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
// .setQueryString(HttpUtil.buildQueryString(request))
// .setMethod(request.getMethod())
// .setUserAgent(HttpUtil.getUserAgent(request))
// .setIp(HttpUtil.getIp(request))
// .setExceptionTime(new Date());
// }
//
// @Async
// public void addExceptionLog(ExceptionLogAddDTO exceptionLog) {
// systemLogService.addExceptionLog(exceptionLog);
// }
//
//}

View File

@@ -27,12 +27,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
}) // 有引入 system-sdk
public class AdminMVCAutoConfiguration implements WebMvcConfigurer {
@Bean
// @ConditionalOnMissingBean(AccessLogInterceptor.class)
public AccessLogInterceptor adminAccessLogInterceptor() {
return new AccessLogInterceptor();
}
@Bean
@ConditionalOnMissingBean(AdminSecurityInterceptor.class)
public AdminSecurityInterceptor adminSecurityInterceptor() {
@@ -45,18 +39,6 @@ public class AdminMVCAutoConfiguration implements WebMvcConfigurer {
return new AdminDemoInterceptor();
}
@Bean
@ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
public GlobalResponseBodyHandler globalReturnValueHandler() {
return new GlobalResponseBodyHandler();
}
@Bean
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminAccessLogInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");

View File

@@ -17,6 +17,7 @@
<module>common-dependencies</module>
<module>mall-spring-boot-starter-swagger</module>
<module>mall-spring-boot-starter-web</module>
<module>mall-spring-boot-starter-security</module>
</modules>
<dependencyManagement>