Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

1 год назад
1 год назад
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
1 год назад
1 год назад
5 месяцев назад
​
6 месяцев назад
​
​
​
1 год назад
​
​
​
​
​
​
​
​
​
​
​
6 месяцев назад
1 год назад
​
6 месяцев назад
​
​
​
1 год назад
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
1 год назад
​
​
​
​
​
​
6 месяцев назад
​
​
5 месяцев назад
​
​
​
​
​
​
4 месяцев назад
​
​
​
​
​
​
​
​
​
​
3 месяцев назад
​
​
​
​
​
2 месяцев назад
​
3 месяцев назад
​
2 месяцев назад
​
​
​
​
​
​
​
​
​
​
​
​
​
6 месяцев назад
1 год назад
6 месяцев назад
1 год назад
​
​
​
​
6 месяцев назад
​
​
​
​
​
​
​
​
1 год назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package com.ffii.fpsms.config.security;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.beans.factory.annotation.Qualifier;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.context.annotation.Lazy;
  7. import org.springframework.core.annotation.Order;
  8. import org.springframework.http.HttpStatus;
  9. import org.springframework.ldap.core.support.BaseLdapPathContextSource;
  10. import org.springframework.security.authentication.AuthenticationManager;
  11. import org.springframework.security.config.Customizer;
  12. import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
  13. import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
  14. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  15. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  16. import org.springframework.security.config.http.SessionCreationPolicy;
  17. import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
  18. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  19. import org.springframework.security.crypto.password.PasswordEncoder;
  20. import org.springframework.security.web.SecurityFilterChain;
  21. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  22. import com.ffii.fpsms.config.security.jwt.JwtRequestFilter;
  23. import org.springframework.http.HttpMethod;
  24. import jakarta.servlet.http.HttpServletResponse;
  25. import java.io.IOException;
  26. import java.nio.charset.StandardCharsets;
  27. @Configuration
  28. @EnableWebSecurity
  29. @EnableMethodSecurity
  30. public class SecurityConfig {
  31. public static final String INDEX_URL = "/";
  32. public static final String LOGIN_URL = "/login";
  33. public static final String LDAP_LOGIN_URL = "/ldap-login";
  34. public static final String[] URL_WHITELIST = {
  35. INDEX_URL,
  36. LOGIN_URL,
  37. LDAP_LOGIN_URL,
  38. "/refresh-token",
  39. "/py/**"
  40. };
  41. public static final String[] CORS_ALLOWED_METHODS = {
  42. "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"
  43. };
  44. @Lazy
  45. @Autowired
  46. private JwtRequestFilter jwtRequestFilter;
  47. @Bean
  48. @Qualifier("AuthenticationManager")
  49. public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
  50. throws Exception {
  51. return authenticationConfiguration.getAuthenticationManager();
  52. }
  53. @Bean
  54. @Qualifier("LdapAuthenticationManager")
  55. public AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) {
  56. LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
  57. factory.setUserSearchFilter("cn={0}");
  58. return factory.createAuthenticationManager();
  59. }
  60. @Bean
  61. public PasswordEncoder passwordEncoder() {
  62. return new BCryptPasswordEncoder();
  63. }
  64. /**
  65. * FP-MTMS Version Checklist | Functions Ref. No. 67 | v1.0.0 | 2026-08-13
  66. * FP-MTMS Version Checklist | Functions Ref. No. 51 | v1.0.1 | 2026-08-06
  67. * FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.7 | 2026-09-08
  68. * (stockAdjustment/submit and GET /latestRemarks → INVENTORY_ADJUST)
  69. */
  70. @Bean
  71. @Order(1)
  72. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  73. return http
  74. .cors(Customizer.withDefaults()).csrf(csrf -> csrf.disable())
  75. .requestCache(requestCache -> requestCache.disable())
  76. .authorizeHttpRequests(
  77. authRequest -> authRequest
  78. .requestMatchers(URL_WHITELIST).permitAll()
  79. .requestMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll()
  80. /* PO stock-in nav alerts: TESTING / ADMIN / STOCK (no @PreAuthorize on Kotlin controllers). */
  81. .requestMatchers(HttpMethod.GET, "/stockInLine/alerts/purchase-incomplete-count")
  82. .hasAnyAuthority("TESTING", "ADMIN", "STOCK")
  83. .requestMatchers(HttpMethod.GET, "/stockInLine/alerts/purchase-incomplete")
  84. .hasAnyAuthority("TESTING", "ADMIN", "STOCK")
  85. .requestMatchers(HttpMethod.GET, "/product-process/Demo/Process/alerts/fg-qc-putaway")
  86. .hasAuthority("TESTING")
  87. .requestMatchers(HttpMethod.GET, "/device-presence/ping").authenticated()
  88. .requestMatchers(HttpMethod.POST, "/device-presence/heartbeat").authenticated()
  89. .requestMatchers(HttpMethod.GET, "/device-presence/active")
  90. .hasAnyAuthority("TESTING", "ADMIN")
  91. .requestMatchers(HttpMethod.GET, "/device-presence/history")
  92. .hasAnyAuthority("TESTING", "ADMIN")
  93. .requestMatchers(HttpMethod.GET, "/printer-monitor/status")
  94. .hasAnyAuthority("TESTING", "ADMIN")
  95. .requestMatchers(HttpMethod.GET, "/printer-monitor/history")
  96. .hasAnyAuthority("TESTING", "ADMIN")
  97. .requestMatchers(HttpMethod.POST, "/printer-monitor/check")
  98. .hasAnyAuthority("TESTING", "ADMIN")
  99. .requestMatchers(HttpMethod.GET, "/label-printer-monitor/status")
  100. .hasAnyAuthority("TESTING", "ADMIN")
  101. .requestMatchers(HttpMethod.POST, "/label-printer-monitor/check")
  102. .hasAnyAuthority("TESTING", "ADMIN")
  103. .requestMatchers(HttpMethod.GET, "/label-printer-monitor/label-stats")
  104. .hasAnyAuthority("TESTING", "ADMIN")
  105. .requestMatchers(HttpMethod.GET, "/label-printer-monitor/odometer-stats")
  106. .hasAnyAuthority("TESTING", "ADMIN")
  107. .requestMatchers(HttpMethod.POST, "/stockAdjustment/submit")
  108. .hasAuthority("INVENTORY_ADJUST")
  109. .requestMatchers(HttpMethod.GET, "/stockAdjustment/latestRemarks")
  110. .hasAuthority("INVENTORY_ADJUST")
  111. .requestMatchers(HttpMethod.GET, "/inventoryLotLine/trace")
  112. .hasAuthority("ITEM_TRACING")
  113. .requestMatchers(HttpMethod.GET, "/inventoryLotLine/trace/location/**")
  114. .hasAuthority("ITEM_TRACING")
  115. /* 工單 生產流程 完成工單:僅 ADMIN */
  116. .requestMatchers(HttpMethod.POST, "/product-process/Demo/ProcessLine/complete/**")
  117. .hasAuthority("ADMIN")
  118. /* 工序「已完成」(Just Pass):僅 ADMIN */
  119. .requestMatchers(HttpMethod.POST, "/product-process/Demo/ProcessLine/pass/**")
  120. .hasAuthority("ADMIN")
  121. /* M18 手動同步頁:ADMIN 或 M18_SYNC。po-by-code 另允許 PURCHASE(採購單搜尋自動同步)。 */
  122. .requestMatchers(HttpMethod.GET, "/m18/test/po-by-code")
  123. .hasAnyAuthority("ADMIN", "M18_SYNC", "PURCHASE")
  124. .requestMatchers(HttpMethod.GET, "/m18/test/do-by-code")
  125. .hasAnyAuthority("ADMIN", "M18_SYNC")
  126. .requestMatchers(HttpMethod.GET, "/m18/test/do-by-code-extra")
  127. .hasAnyAuthority("ADMIN", "M18_SYNC")
  128. .requestMatchers(HttpMethod.GET, "/m18/test/product-by-code")
  129. .hasAnyAuthority("ADMIN", "M18_SYNC")
  130. .anyRequest().authenticated())
  131. .httpBasic(httpBasic -> httpBasic.authenticationEntryPoint(
  132. (request, response, authException) -> sendUnauthorizedJson(response, "Unauthorized", "UNAUTHORIZED")))
  133. .sessionManagement(
  134. sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
  135. .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
  136. .build();
  137. }
  138. /** Send 401 with JSON body so frontend can consistently handle session timeout / missing token. */
  139. private static void sendUnauthorizedJson(HttpServletResponse response, String message, String code) throws IOException {
  140. response.setStatus(HttpStatus.UNAUTHORIZED.value());
  141. response.setContentType("application/json");
  142. response.setCharacterEncoding(StandardCharsets.UTF_8.name());
  143. String body = String.format("{\"message\":\"%s\",\"code\":\"%s\"}", message.replace("\"", "\\\""), code);
  144. response.getWriter().write(body);
  145. }
  146. }