diff --git a/src/main/java/cn/teammodel/config/BodyReaderRequestWrapper.java b/src/main/java/cn/teammodel/config/BodyReaderRequestWrapper.java new file mode 100644 index 0000000..163cf76 --- /dev/null +++ b/src/main/java/cn/teammodel/config/BodyReaderRequestWrapper.java @@ -0,0 +1,54 @@ +package cn.teammodel.config; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.*; + +/** +* 解决 request inputstream 只能读取一次(拦截器读取后,controller 就不能拿到) +*/ +public class BodyReaderRequestWrapper extends HttpServletRequestWrapper { + // 将流中的内容保存 + private final byte[] buff; + public BodyReaderRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + InputStream is = request.getInputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] b = new byte[1024]; + int len; + while ((len = is.read(b)) != -1) { + baos.write(b, 0, len); + } + buff = baos.toByteArray(); + } + @Override + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream bais = new ByteArrayInputStream(buff); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + @Override + public boolean isReady() { + return false; + } + @Override + public void setReadListener(ReadListener listener) { + } + @Override + public int read() throws IOException { + return bais.read(); + } + }; + } + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + public String getRequestBody() { + return new String(buff); + } +} \ No newline at end of file diff --git a/src/main/java/cn/teammodel/config/WebMvcConfig.java b/src/main/java/cn/teammodel/config/WebMvcConfig.java new file mode 100644 index 0000000..cb7badb --- /dev/null +++ b/src/main/java/cn/teammodel/config/WebMvcConfig.java @@ -0,0 +1,41 @@ +package cn.teammodel.config; + +import cn.teammodel.config.intercepter.EnhanceRequestServletFilter; +import cn.teammodel.config.intercepter.UploadApiLogInterceptor; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * WebMvc配置 : 静态文件、拦截器等 + * @author winter + * @create 2024-01-15 17:06 + */ +@Configuration +@RequiredArgsConstructor +public class WebMvcConfig implements WebMvcConfigurer { + private final UploadApiLogInterceptor uploadApiLogInterceptor; + /** + * 拦截器配置 + */ + @Override + public void addInterceptors(@NotNull InterceptorRegistry registry) { + registry.addInterceptor(uploadApiLogInterceptor); + } + + // 注册过滤器 + @Bean + public FilterRegistrationBean filterRegistrationBean(){ + FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean<>(); + filterRegistrationBean.setFilter(new EnhanceRequestServletFilter()); + filterRegistrationBean.addUrlPatterns("/*"); + //order的数值越小 则优先级越高,这里直接使用的最高优先级 + filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); + return filterRegistrationBean; + } +} diff --git a/src/main/java/cn/teammodel/config/intercepter/EnhanceRequestServletFilter.java b/src/main/java/cn/teammodel/config/intercepter/EnhanceRequestServletFilter.java new file mode 100644 index 0000000..92a5fd3 --- /dev/null +++ b/src/main/java/cn/teammodel/config/intercepter/EnhanceRequestServletFilter.java @@ -0,0 +1,19 @@ +package cn.teammodel.config.intercepter; + +import cn.teammodel.config.BodyReaderRequestWrapper; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +public class EnhanceRequestServletFilter implements Filter { + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + // 防止流读取一次后就没有了, 所以需要将流继续写出去 + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + // 这里将原始request传入,读出流并存储 + ServletRequest requestWrapper = new BodyReaderRequestWrapper(httpServletRequest); + // 这里将原始request替换为包装后的request,此后所有进入controller的request均为包装后的 + filterChain.doFilter(requestWrapper, servletResponse);// + } +} \ No newline at end of file diff --git a/src/main/java/cn/teammodel/config/intercepter/UploadApiLogInterceptor.java b/src/main/java/cn/teammodel/config/intercepter/UploadApiLogInterceptor.java new file mode 100644 index 0000000..7b2da98 --- /dev/null +++ b/src/main/java/cn/teammodel/config/intercepter/UploadApiLogInterceptor.java @@ -0,0 +1,134 @@ +package cn.teammodel.config.intercepter; + +import cn.hutool.crypto.SecureUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.jwt.JWTUtil; +import cn.teammodel.manager.NotificationService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.List; + +/** + * 上传接口调用日志拦截器 + * @author winter + * @create 2024-03-18 10:00 + */ +@Component +@Slf4j +public class UploadApiLogInterceptor implements HandlerInterceptor { + @Resource + private NotificationService notificationService; + + private final ObjectMapper mapper = new ObjectMapper(); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String ip = ServletUtil.getClientIP(request); + String client = ""; + String tokenSha = ""; + String id = ""; + String name = ""; + String school = ""; + String scope = ""; + String referer = request.getHeader("Referer"); + Long requestTime = Instant.now().toEpochMilli(); + // 获取 json 请求参数(这里读了后,后面就再也读不到了,需要处理) + BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)); + StringBuilder responseStrBuilder = new StringBuilder(); + String inputStr; + while ((inputStr = streamReader.readLine()) != null) + responseStrBuilder.append(inputStr); + + JsonNode jsonNode = mapper.readTree(responseStrBuilder.toString()); + String xAuthToken = request.getHeader("x-auth-AuthToken"); + // 如果有 authorization + String authorization = request.getHeader("Authorization"); + if (StringUtils.isNotBlank(authorization)) { + authorization = authorization.replace("Bearer ", ""); + List roles = (List) JWTUtil.parseToken(authorization).getPayload("roles"); + if (ObjectUtils.isNotEmpty(roles)) { + client = roles.get(0); + } + tokenSha = SecureUtil.sha1("Bearer " + authorization); + } + String xAuthIdToken = request.getHeader("X-Auth-IdToken"); + if (StringUtils.isNotBlank(xAuthIdToken)) { + id = (String) JWTUtil.parseToken(xAuthIdToken).getPayload("sub"); + name = (String) JWTUtil.parseToken(xAuthIdToken).getPayload("name"); + if (StringUtils.isNotBlank(tokenSha)) { + tokenSha = SecureUtil.sha1(xAuthIdToken); + } + } + String xAuthSchool = request.getHeader("X-Auth-School"); + if (StringUtils.isNotBlank(xAuthSchool)) { + school = xAuthSchool; + } + if (StringUtils.isNotBlank(xAuthToken)) { + id = (String) JWTUtil.parseToken(xAuthToken).getPayload("sub"); + name = (String) JWTUtil.parseToken(xAuthToken).getPayload("name"); + school = (String) JWTUtil.parseToken(xAuthToken).getPayload("azp"); + scope = (String) JWTUtil.parseToken(xAuthToken).getPayload("scope"); + if (StringUtils.isNotBlank(tokenSha)) { + tokenSha = SecureUtil.sha1(xAuthToken); + } + } + + ObjectNode root = mapper.createObjectNode(); + root.put("ip", ip); + root.put("time", requestTime); + root.put("id", id); + root.put("name", name); + root.set("param", jsonNode); + root.put("school", school); + root.put("client", client); + root.put("tid", tokenSha); + root.put("scope", scope); + root.put("path", request.getContextPath() + request.getRequestURI()); + root.put("host", request.getServerName()); + root.put("p", "appraisal"); + + // 发送请求 + OkHttpClient okHttpClient = new OkHttpClient(); + String requestData = root.toString(); + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), requestData); + Request okRequest = new Request.Builder() + .url("https://teammodelosfunction.chinacloudsites.cn/api/http-log") + .post(requestBody) + .build(); + + okHttpClient.newCall(okRequest).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + log.error("UploadApiLogIntercepter error" ) ; + notificationService.send("日志上传告警: 请求发送失败,检查请求发送客户端"); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (!response.isSuccessful()) { + log.error("UploadApiLogIntercepter error" ) ; + notificationService.send("日志上传告警: 请求响应异常,检查 API 源是否正常"); + } else { + log.info("UploadApiLogIntercepter success"); + } + } + }); + return true; + } +} diff --git a/src/test/java/cn/teammodel/TestWithoutSpring.java b/src/test/java/cn/teammodel/TestWithoutSpring.java index 5ebe81a..0bd297d 100644 --- a/src/test/java/cn/teammodel/TestWithoutSpring.java +++ b/src/test/java/cn/teammodel/TestWithoutSpring.java @@ -1,5 +1,6 @@ package cn.teammodel; +import cn.hutool.jwt.JWTUtil; import cn.teammodel.common.FiveEducations; import cn.teammodel.model.entity.appraise.Appraise; import cn.teammodel.model.entity.appraise.AppraiseTreeNode; @@ -31,18 +32,9 @@ import java.util.UUID; public class TestWithoutSpring { @Test public void testJwtDecode() { -// Claims claims = null; -// try { -// Jwts.parser() -// -// -//// claims = Jwts.parser() -//// .setSigningKey("fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA=") -//// .parseClaimsJws("eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0LnRlYW1tb2RlbC5jbiIsInN1YiI6IjE1OTUzMjEzNTQiLCJhenAiOiJoYmNuIiwiZXhwIjoxNjk5NTg2MzM1LCJuYW1lIjoi572X6ICB5biIIiwicGljdHVyZSI6Imh0dHBzOi8vY29yZXN0b3JhZ2VzZXJ2aWNlLmJsb2IuY29yZS5jaGluYWNsb3VkYXBpLmNuL2FjY291bnQvYXZhdGFyLzE1OTUzMjEzNTQiLCJyb2xlcyI6WyJ0ZWFjaGVyIiwiYWRtaW4iLCJhcmVhIl0sInBlcm1pc3Npb25zIjpbXSwic3RhbmRhcmQiOiJzdGFuZGFyZDEiLCJzY29wZSI6InRlYWNoZXIiLCJhcmVhIjoiNjllM2Q0MTMtNTBhMS00ZjVlLTg0NGEtZTBmN2M5NjIyZWEzIiwid2Vic2l0ZSI6IklFUyJ9.4NdqwDdDQcyberEAirX0srOIb1ADXLJfP-a9qNXb0yw") -//// .getBody(); -// } catch (Exception e) { -// e.printStackTrace(); -// } + // 解析 JWT,不验证签 + Object roles = JWTUtil.parseToken("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlR4OFo1d2lkT2VSdDVCSGdWX2lvdXpzbjd6OCJ9.eyJhdWQiOiI3MjY0MzcwNC1iMmU3LTRiMjYtYjg4MS1iZDU4NjVlN2E3YTUiLCJpc3MiOiJodHRwczovL2xvZ2luLnBhcnRuZXIubWljcm9zb2Z0b25saW5lLmNuLzQ4MDdlOWNmLTg3YjgtNDE3NC1hYTViLWU3NjQ5N2Q3MzkyYi92Mi4wIiwiaWF0IjoxNzEwNzQ0MzA4LCJuYmYiOjE3MTA3NDQzMDgsImV4cCI6MTcxMDc0ODIwOCwiYWlvIjoiNDJaZ1lPQS9KL0t0ZTIzdWFzN2dEMGFPdW80WHpmdjlmMXhUREp6ZlZsNzk0VWkwWndVQSIsImF6cCI6ImM3MzE3Zjg4LTdjZWEtNGU0OC1hYzU3LWExNjA3MWY3Yjg4NCIsImF6cGFjciI6IjEiLCJvaWQiOiJkNDliYjc4Mi02ZGMyLTQ5MzktODY3OC1kMzNjMjY3MTliZjkiLCJyaCI6IjAuREFJQXota0hTTGlIZEVHcVctZGtsOWM1S3dRM1pITG5zaVpMdUlHOVdHWG5wNlVCQUFBLiIsInJvbGVzIjpbIklFUyJdLCJzdWIiOiJkNDliYjc4Mi02ZGMyLTQ5MzktODY3OC1kMzNjMjY3MTliZjkiLCJ0aWQiOiI0ODA3ZTljZi04N2I4LTQxNzQtYWE1Yi1lNzY0OTdkNzM5MmIiLCJ1dGkiOiJ6MGhKdDg0c1UwMlpxNTJNdGNRS0FBIiwidmVyIjoiMi4wIn0.U9KWPccJQ4ZgfzHBNTK6swuEJvkZM8ebfrOpAl7uDoNynlVEsp8CcQ2a1PIO3V8X8tcNassCplc08qbCqcL49R3S5vP2bWTiucT7c_fyCogxMYpmRM0RmGz8RF1tFF3VAxG4_q1Gt9LgzLbNhxxCQcbgJuZhUMKAP246esHlJrkue88FwgmMxf_rwxDH8VwbAchHtSuIRbj3zYSzgKU0Lpmibrr88g_gQZ061f4RyRezTmKLoOqhoGjEosOOnXOdI67YyxpdMjm64uzc92fNUyC9PHvu8XbTTbdUlkU2lPgrk5xFfyVus9SdxWQXB4PCIDhcBsoVQQlWGmY-kvw_8w").getPayload("roles"); + System.out.println(roles); } @Test