RequestAspect.java 9.54 KB
package com.huaheng.control.management.utils.aspect;

import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.huaheng.control.management.utils.config.ApplicationConfig;
import com.huaheng.control.management.utils.constant.CommonConstant;
import com.huaheng.control.management.utils.constant.RSA256Key;
import com.huaheng.control.management.utils.support.PassRequestAuthentication;
import com.huaheng.control.management.utils.support.RequestAuthentication;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 第三方系统调用接口身份认证Aspect
 * @author     TanYibin
 * @createDate 2023年2月14日
 */
@Slf4j
@Aspect
@Component
public class RequestAspect {

    @Autowired
    private RSA256Key rsa256Key;

    @Autowired
    private ApplicationConfig applicationConfig;

    @Pointcut("execution(public * com.huaheng.control.management.controller..*.*(..))" + "&& (@annotation(org.springframework.web.bind.annotation.RequestMapping) "
        + "|| @annotation(org.springframework.web.bind.annotation.GetMapping) " + "|| @annotation(org.springframework.web.bind.annotation.PostMapping))")
    public void executeRequestAspect() {}

    /**
     * @author               TanYibin
     * @createDate           2026年1月4日
     * @param      point
     * @return
     * @throws     Throwable
     */
    @Around("executeRequestAspect()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();

        String audience = (String)request.getAttribute("audience");
        if (StringUtils.isEmpty(audience)) {
            audience = "unknown";
        }

        // 记录请求开始信息(包含参数)
        log.info(">>> [{}] {} Audience:{} Params:{} From:{}", request.getMethod(), url, audience, getRequestParams(request, point.getArgs()),
            CommonConstant.getIpAddr(request));

        Throwable throwable = null;

        Object result = null;
        try {
            result = point.proceed();
            return result;
        } catch (Throwable e) {
            throwable = e;
            throw e;
        } finally {
            long callTime = System.currentTimeMillis() - startTime;
            // 记录返回结果
            if (throwable != null) {
                log.error("<<< [{}] {} Audience:{} Execution exception! {}", request.getMethod(), url, audience, throwable.getMessage(), throwable);
            } else {
                log.info("<<< [{}] {} Audience:{} Result:{} Cost:{}ms", request.getMethod(), url, audience, getResultInfo(result), callTime);
            }
        }
    }

    /**
     * API Token 验证
     * @author                           TanYibin
     * @createDate                       2023年2月14日
     * @param      joinPoint
     * @throws     NoSuchMethodException
     */
    @Before("executeRequestAspect()")
    public void doBefore(JoinPoint joinPoint) throws NoSuchMethodException {
        Method method = this.getTargetMethod(joinPoint);
        // 检查是否有passtoken注释,有则跳过认证
        if (method.isAnnotationPresent(PassRequestAuthentication.class)) {
            PassRequestAuthentication passApiAuthentication = method.getAnnotation(PassRequestAuthentication.class);
            if (passApiAuthentication.required()) {
                return;
            }
        }
        // 获取request对象
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String token = request.getHeader("token");
        if (token == null) {
            throw new RuntimeException("Request authentication token is null");
        }
        Algorithm algorithm = Algorithm.RSA256(rsa256Key.getPublicKey(), rsa256Key.getPrivateKey());
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(applicationConfig.getArtifactId()).build();
        DecodedJWT jwt = verifier.verify(token);
        new RequestAuthentication.ApiAuthenticationBuild().operator(jwt.getClaim("operator").asString()).audience(jwt.getAudience().get(0)).issuer(jwt.getIssuer())
            .issuedAt(jwt.getIssuedAt()).expireDateTime(jwt.getExpiresAt()).bulid();
        if (jwt.getClaim("operator").asString().equals(jwt.getIssuer())) {
            throw new RuntimeException("Request authentication token error");
        }
        log.debug("Request authentication token info: 使用方: {}, 提供方: {}, 失效时间: {}", jwt.getAudience().get(0), jwt.getClaim("operator").asString(),
            DateUtil.format(jwt.getExpiresAt(), DatePattern.NORM_DATETIME_PATTERN));

        // 在验证token后,将audience存储为请求属性
        request.setAttribute("audience", jwt.getAudience().get(0));
    }

    @After("executeRequestAspect()")
    public void doAfter(JoinPoint joinPoint) throws NoSuchMethodException {
        RequestAuthentication.clean();
    }

    /**
     * 基于连接点信息获取目标方法对象
     * @author                           TanYibin
     * @createDate                       2023年2月14日
     * @param      joinPoint
     * @return
     * @throws     NoSuchMethodException
     */
    private Method getTargetMethod(JoinPoint joinPoint) throws NoSuchMethodException {
        // 获取目标类对象
        Class<?> clazz = joinPoint.getTarget().getClass();
        // 获取方法签名信息,方法名和参数列表
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        // 获取目标方法对象
        return clazz.getDeclaredMethod(signature.getName(), signature.getParameterTypes());
    }

    /**
     * 获取请求参数信息
     * 针对单个对象作为请求体的情况进行优化
     */
    private String getRequestParams(HttpServletRequest request, Object[] args) {
        try {
            Object params = null;
            // 获取URL参数(适用于GET请求和POST的URL参数)
            Map<String, String> queryParams = getQueryParams(request);
            if (!queryParams.isEmpty()) {
                params = queryParams;
            }

            // 获取Body参数(适用于POST请求的请求体对象)
            Object bodyParam = getBodyParam(args);
            if (bodyParam != null) {
                params = bodyParam;
            }

            return params == null ? "无参数" : JSON.toJSONString(params);
        } catch (Exception e) {
            return "参数解析失败";
        }
    }

    /**
     * 获取URL查询参数
     */
    private Map<String, String> getQueryParams(HttpServletRequest request) {
        Map<String, String> queryParams = new HashMap<>();
        Enumeration<String> paramNames = request.getParameterNames();

        while (paramNames.hasMoreElements()) {
            String paramName = paramNames.nextElement();
            String paramValue = request.getParameter(paramName);
            queryParams.put(paramName, paramValue);
        }

        return queryParams;
    }

    /**
     * 获取请求体参数(针对单个对象的情况)
     */
    private Object getBodyParam(Object[] args) {
        if (args == null || args.length == 0) {
            return null;
        }

        // 过滤掉HttpServletRequest等框架参数,返回第一个业务参数对象
        for (Object arg : args) {
            if (!isFrameworkType(arg)) {
                return arg;
            }
        }

        return null;
    }

    /**
     * 判断是否为框架内置类型参数
     */
    private boolean isFrameworkType(Object arg) {
        return arg instanceof HttpServletRequest || arg instanceof javax.servlet.http.HttpServletResponse
            || arg instanceof org.springframework.validation.BindingResult || arg instanceof org.springframework.ui.Model;
    }

    /**
     * 获取返回结果信息(安全处理,避免日志过大)
     */
    private String getResultInfo(Object result) {
        try {
            if (result == null) {
                return "null";
            }
            String resultStr = JSON.toJSONString(result);
            // 控制日志长度,避免日志文件过大
            int maxLength = 500;
            if (resultStr.length() > maxLength) {
                return resultStr.substring(0, maxLength) + "...[长度:" + resultStr.length() + "]";
            }
            return resultStr;
        } catch (Exception e) {
            return result.toString();
        }
    }
}