AdminEndpointGuardConfig.java 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. package com.malk.controller;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.commons.lang3.StringUtils;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.web.servlet.HandlerInterceptor;
  7. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  8. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  9. import javax.servlet.http.HttpServletRequest;
  10. import javax.servlet.http.HttpServletResponse;
  11. import java.nio.charset.StandardCharsets;
  12. import java.security.MessageDigest;
  13. import java.util.Arrays;
  14. import java.util.Collections;
  15. import java.util.List;
  16. /**
  17. * 生产管理接口保护:默认关闭;临时开启时必须同时配置并携带管理令牌。
  18. */
  19. @Slf4j
  20. @Configuration
  21. public class AdminEndpointGuardConfig implements WebMvcConfigurer {
  22. static final String TOKEN_HEADER = "X-AKDS-Admin-Token";
  23. static final List<String> PROTECTED_PATHS = Collections.unmodifiableList(Arrays.asList(
  24. "/workhours/**",
  25. "/personnel-sync/**",
  26. "/approval/writeback/sync",
  27. "/approval/resubmit",
  28. "/timecard/summary/cleanup-duplicates",
  29. "/timecard/summary/delete-instances"
  30. ));
  31. @Value("${admin-api.enabled:false}")
  32. private boolean enabled;
  33. @Value("${admin-api.token:}")
  34. private String token;
  35. @Override
  36. public void addInterceptors(InterceptorRegistry registry) {
  37. registry.addInterceptor(new AdminEndpointInterceptor(enabled, token))
  38. .addPathPatterns(PROTECTED_PATHS);
  39. }
  40. static final class AdminEndpointInterceptor implements HandlerInterceptor {
  41. private final boolean enabled;
  42. private final String expectedToken;
  43. AdminEndpointInterceptor(boolean enabled, String expectedToken) {
  44. this.enabled = enabled;
  45. this.expectedToken = expectedToken;
  46. }
  47. @Override
  48. public boolean preHandle(
  49. HttpServletRequest request,
  50. HttpServletResponse response,
  51. Object handler) throws Exception {
  52. response.setHeader("Cache-Control", "no-store");
  53. if (!enabled) {
  54. response.sendError(HttpServletResponse.SC_NOT_FOUND);
  55. return false;
  56. }
  57. String actualToken = request.getHeader(TOKEN_HEADER);
  58. if (!tokenMatches(expectedToken, actualToken)) {
  59. log.warn("[AdminAPI] 管理接口鉴权失败 method={} path={} remote={}",
  60. request.getMethod(), request.getRequestURI(), request.getRemoteAddr());
  61. response.sendError(HttpServletResponse.SC_FORBIDDEN);
  62. return false;
  63. }
  64. return true;
  65. }
  66. }
  67. static boolean tokenMatches(String expectedToken, String actualToken) {
  68. if (StringUtils.isBlank(expectedToken) || StringUtils.isBlank(actualToken)) {
  69. return false;
  70. }
  71. return MessageDigest.isEqual(
  72. expectedToken.getBytes(StandardCharsets.UTF_8),
  73. actualToken.getBytes(StandardCharsets.UTF_8));
  74. }
  75. }