This commit is contained in:
parent
19e34dc36b
commit
45be3a1356
|
|
@ -4,6 +4,7 @@ import com.bonus.digitalSignage.filter.TokenFilter;
|
|||
import com.bonus.digitalSignage.system.service.impl.UserDetailsServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
|
|
@ -15,15 +16,19 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand
|
|||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||
import org.springframework.security.web.firewall.HttpFirewall;
|
||||
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* spring security配置
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class BnsSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
|
|
@ -39,8 +44,8 @@ public class BnsSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
private UserDetailsServiceImpl userDetailsService;
|
||||
@Autowired
|
||||
private TokenFilter tokenFilter;
|
||||
// @Autowired
|
||||
// private OptionsRequestFilter optionsRequestFilter;
|
||||
@Autowired
|
||||
private CorsConfigurationSource corsConfigurationSource;
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder bCryptPasswordEncoder() {
|
||||
|
|
@ -49,7 +54,21 @@ public class BnsSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable();
|
||||
http
|
||||
.csrf(csrf -> csrf
|
||||
.requireCsrfProtectionMatcher(request -> {
|
||||
String uri = request.getRequestURI();
|
||||
String method = request.getMethod();
|
||||
|
||||
// 保护登录和退出登录
|
||||
return ("/login".equals(uri) && "POST".equalsIgnoreCase(method)) ||
|
||||
("/logout".equals(uri) && "POST".equalsIgnoreCase(method));
|
||||
})
|
||||
);
|
||||
|
||||
// 使用新的跨域配置
|
||||
http.cors(cors -> cors.configurationSource(corsConfigurationSource));
|
||||
|
||||
// 基于token,所以不需要session
|
||||
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||
http.authorizeRequests()
|
||||
|
|
@ -61,10 +80,9 @@ public class BnsSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
.successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler).and()
|
||||
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
|
||||
http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
|
||||
// 解决不允许显示在iframe的问题
|
||||
http.headers().frameOptions().disable();
|
||||
// 让CspFilter来处理frame options,避免冲突
|
||||
// http.headers().frameOptions().disable();
|
||||
http.headers().cacheControl();
|
||||
// http.addFilter(optionsRequestFilter);
|
||||
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
}
|
||||
|
||||
|
|
@ -81,5 +99,4 @@ public class BnsSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
firewall.setAllowUrlEncodedDoubleSlash(true);
|
||||
return firewall;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package com.bonus.digitalSignage.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 跨域配置类
|
||||
* 解决前后端不分离项目的跨域问题
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Value("${cors.allowed-origins}")
|
||||
private String allowedOrigins;
|
||||
|
||||
@Value("${cors.allowed-methods}")
|
||||
private String allowedMethods;
|
||||
|
||||
@Value("${cors.allowed-headers}")
|
||||
private String allowedHeaders;
|
||||
|
||||
@Value("${cors.allow-credentials}")
|
||||
private boolean allowCredentials;
|
||||
|
||||
@Value("${cors.max-age}")
|
||||
private long maxAge;
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns(getAllowedOriginPatterns().toArray(new String[0]))
|
||||
.allowedMethods(getAllowedMethodArray())
|
||||
.allowedHeaders(getAllowedHeaderArray())
|
||||
.allowCredentials(allowCredentials)
|
||||
.maxAge(maxAge)
|
||||
.exposedHeaders("Content-Length", "Content-Type", "Token", "Authorization");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOriginPatterns(getAllowedOriginPatterns());
|
||||
configuration.setAllowedMethods(Arrays.asList(getAllowedMethodArray()));
|
||||
configuration.setAllowedHeaders(Arrays.asList(getAllowedHeaderArray()));
|
||||
configuration.setExposedHeaders(Arrays.asList("Content-Length", "Content-Type", "Token", "Authorization"));
|
||||
configuration.setAllowCredentials(allowCredentials);
|
||||
configuration.setMaxAge(maxAge);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
// 统一仅注册一套白名单策略,避免出现“*”
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
private List<String> getAllowedOriginPatterns() {
|
||||
if (allowedOrigins == null || allowedOrigins.trim().isEmpty()) {
|
||||
return Arrays.asList(
|
||||
"http://localhost:*",
|
||||
"http://127.0.0.1:*",
|
||||
"http://192.168.*.*:*",
|
||||
"http://10.*.*.*:*"
|
||||
);
|
||||
}
|
||||
return Arrays.asList(allowedOrigins.split(","));
|
||||
}
|
||||
|
||||
private String[] getAllowedMethodArray() {
|
||||
if (allowedMethods == null || allowedMethods.trim().isEmpty()) {
|
||||
return new String[]{"GET", "POST", "PUT", "DELETE", "OPTIONS"};
|
||||
}
|
||||
return allowedMethods.split(",");
|
||||
}
|
||||
|
||||
private String[] getAllowedHeaderArray() {
|
||||
if (allowedHeaders == null || allowedHeaders.trim().isEmpty()) {
|
||||
return new String[]{"Content-Type", "X-Requested-With", "Token", "Authorization", "X-Custom-Header"};
|
||||
}
|
||||
return allowedHeaders.split(",");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
package com.bonus.digitalSignage.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Component
|
||||
@Order(1) // 确保过滤器优先级
|
||||
public class CspFilter implements Filter {
|
||||
|
||||
// 静态资源扩展名模式
|
||||
private static final Pattern STATIC_RESOURCE_PATTERN = Pattern.compile(
|
||||
".*\\.(css|js|map|png|jpg|jpeg|gif|ico|svg|webp|bmp|" +
|
||||
"woff|woff2|ttf|eot|otf|pdf|txt|xml|json|" +
|
||||
"zip|rar|7z|tar|gz|mp4|mp3|wav|avi|mov|webm|" +
|
||||
"doc|docx|xls|xlsx|ppt|pptx)$",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
// 静态资源路径前缀
|
||||
private static final List<String> STATIC_PATH_PREFIXES = Arrays.asList(
|
||||
"/static/", "/public/", "/resources/", "/assets/", "/css/", "/js/",
|
||||
"/images/", "/img/", "/fonts/", "/webjars/", "/vendor/", "/dist/",
|
||||
"/uploads/", "/downloads/", "/libs/", "/layui/"
|
||||
);
|
||||
|
||||
// WebGL和3D地图相关页面路径
|
||||
private static final List<String> WEBGL_PAGE_PATHS = Arrays.asList(
|
||||
"/pages/synthesisQuery/digitalSignage.html",
|
||||
"/pages/basic/lineManagement/child/setSpanTowerLonAndLat.html"
|
||||
);
|
||||
|
||||
@Value("${spring.profiles.active:prod}")
|
||||
private String activeProfile;
|
||||
|
||||
@Value("${csp.report-only:false}")
|
||||
private boolean cspReportOnly;
|
||||
|
||||
@Value("${csp.allow-iframe:true}")
|
||||
private boolean allowIframe;
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
String requestUri = httpRequest.getRequestURI();
|
||||
|
||||
// 设置所有必要的安全头
|
||||
setSecurityHeaders(httpResponse, requestUri);
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private void setSecurityHeaders(HttpServletResponse response, String requestUri) {
|
||||
// 1. 设置ClickJacking防护头(优先解决)
|
||||
setClickJackingProtectionHeaders(response, requestUri);
|
||||
|
||||
// 2. 设置CSP头
|
||||
setCspHeader(response, requestUri);
|
||||
|
||||
// 3. 设置其他安全头
|
||||
setAdditionalSecurityHeaders(response);
|
||||
}
|
||||
|
||||
private void setCspHeader(HttpServletResponse response, String requestUri) {
|
||||
String cspPolicy;
|
||||
|
||||
if (isStaticResource(requestUri)) {
|
||||
// 静态资源使用简单策略
|
||||
cspPolicy = "default-src 'self'";
|
||||
}
|
||||
else if (isLoginPage(requestUri)) {
|
||||
// 登录页面 - 使用安全的CSP策略,移除不安全的指令
|
||||
String frameAncestors = allowIframe ? "'self'" : "'none'";
|
||||
|
||||
cspPolicy = "default-src 'self'; " +
|
||||
// 允许同源脚本和外部JavaScript库
|
||||
"script-src 'self' 'unsafe-inline' https:; " +
|
||||
// 只允许同源样式
|
||||
"style-src 'self' 'unsafe-inline' https:; " +
|
||||
// 只允许同源图片和数据URI
|
||||
"img-src 'self' data: blob: https:; " +
|
||||
// 只允许同源字体和数据URI
|
||||
"font-src 'self' data: https:; " +
|
||||
// 只允许同源连接
|
||||
"connect-src 'self' https:; " +
|
||||
"frame-ancestors " + frameAncestors + "; " +
|
||||
"form-action 'self'; " +
|
||||
"object-src 'none'; " +
|
||||
"base-uri 'self'; " +
|
||||
"report-uri /api/csp-violation";
|
||||
}
|
||||
else if (isWebglPage(requestUri)) {
|
||||
// WebGL和3D地图页面 - 需要更宽松的策略支持WebGL、Worker等
|
||||
String frameAncestors = allowIframe ? "'self'" : "'none'";
|
||||
|
||||
cspPolicy = "default-src 'self'; " +
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data:; " +
|
||||
"style-src 'self' 'unsafe-inline' data: blob:; " +
|
||||
"img-src 'self' data: blob: https:; " +
|
||||
"font-src 'self' data: blob: https:; " +
|
||||
"connect-src 'self' https: blob: data: http://data.mars3d.cn; " +
|
||||
"frame-ancestors " + frameAncestors + "; " +
|
||||
"form-action 'self'; " +
|
||||
"object-src 'none'; " +
|
||||
"base-uri 'self'; " +
|
||||
"worker-src 'self' blob: data:; " +
|
||||
"child-src 'self' blob: data:; " +
|
||||
"report-uri /api/csp-violation"; // 移除 upgrade-insecure-requests,避免强制HTTPS
|
||||
} else {
|
||||
// 普通HTML页面 - 根据配置决定是否允许iframe
|
||||
String frameAncestors = allowIframe ? "'self'" : "'none'";
|
||||
|
||||
cspPolicy = "default-src 'self'; " +
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; " +
|
||||
"style-src 'self' 'unsafe-inline' https:; " +
|
||||
"img-src 'self' data: blob: https:; " +
|
||||
"font-src 'self' data: https:; " +
|
||||
"connect-src 'self' https:; " +
|
||||
"frame-ancestors " + frameAncestors + "; " +
|
||||
"form-action 'self'; " +
|
||||
"object-src 'none'; " +
|
||||
"base-uri 'self'; " +
|
||||
"report-uri /api/csp-violation"; // 移除 upgrade-insecure-requests,避免强制HTTPS
|
||||
}
|
||||
|
||||
String headerName = cspReportOnly ?
|
||||
"Content-Security-Policy-Report-Only" : "Content-Security-Policy";
|
||||
|
||||
response.setHeader(headerName, cspPolicy);
|
||||
}
|
||||
|
||||
private void setClickJackingProtectionHeaders(HttpServletResponse response, String requestUri) {
|
||||
// 对于静态资源,使用宽松的ClickJacking防护
|
||||
if (isStaticResource(requestUri)) {
|
||||
response.setHeader("X-Frame-Options", "SAMEORIGIN");
|
||||
return;
|
||||
}
|
||||
|
||||
// 对于HTML页面,根据配置决定防护级别
|
||||
if (allowIframe) {
|
||||
response.setHeader("X-Frame-Options", "SAMEORIGIN");
|
||||
} else {
|
||||
response.setHeader("X-Frame-Options", "DENY");
|
||||
}
|
||||
}
|
||||
|
||||
private void setAdditionalSecurityHeaders(HttpServletResponse response) {
|
||||
response.setHeader("X-Content-Type-Options", "nosniff");
|
||||
response.setHeader("X-XSS-Protection", "1; mode=block");
|
||||
response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
||||
response.setHeader("Permissions-Policy",
|
||||
"geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=()");
|
||||
|
||||
// 注意:HSTS 只应在 HTTPS 部署下开启;当前未在此处强制设置
|
||||
// 如需开启,请在 HTTPS 部署完成后,通过配置控制
|
||||
// 例如:Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
||||
}
|
||||
|
||||
private boolean isStaticResource(String uri) {
|
||||
if (uri == null || uri.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String path = uri.split("\\?")[0];
|
||||
|
||||
if (STATIC_RESOURCE_PATTERN.matcher(path).matches()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return STATIC_PATH_PREFIXES.stream().anyMatch(path::startsWith);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为登录页面
|
||||
*/
|
||||
private boolean isLoginPage(String requestUri) {
|
||||
return requestUri != null && (
|
||||
requestUri.endsWith("/login.html") ||
|
||||
requestUri.endsWith("/login") ||
|
||||
requestUri.contains("/login")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机nonce值
|
||||
*/
|
||||
private String generateNonce() {
|
||||
byte[] nonceBytes = new byte[16];
|
||||
new java.util.Random().nextBytes(nonceBytes);
|
||||
return java.util.Base64.getEncoder().encodeToString(nonceBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成内容的SHA-256哈希值
|
||||
*/
|
||||
private String generateHash(String content) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(content.getBytes("UTF-8"));
|
||||
return "'sha256-" + java.util.Base64.getEncoder().encodeToString(hash) + "'";
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWebglPage(String uri) {
|
||||
if (uri == null || uri.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String path = uri.split("\\?")[0];
|
||||
return WEBGL_PAGE_PATHS.stream().anyMatch(path::contains);
|
||||
}
|
||||
|
||||
private boolean isProduction() {
|
||||
return "prod".equals(activeProfile) || "production".equals(activeProfile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// 清理资源
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.bonus.digitalSignage.config;
|
||||
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 额外的安全头过滤器
|
||||
* 用于设置更多的安全相关头信息
|
||||
*/
|
||||
@Component
|
||||
@Order(2)
|
||||
public class SecurityHeadersFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
// 设置额外的安全头
|
||||
setAdditionalSecurityHeaders(httpRequest, httpResponse);
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private void setAdditionalSecurityHeaders(HttpServletRequest request, HttpServletResponse response) {
|
||||
// 1) 缓存控制
|
||||
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
// 2) IE下载策略
|
||||
response.setHeader("X-Download-Options", "noopen");
|
||||
|
||||
// 3) 跨域策略(条件化设置)
|
||||
if (isPotentiallyTrustworthy(request)) {
|
||||
response.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
||||
response.setHeader("Cross-Origin-Resource-Policy", "same-origin");
|
||||
response.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
||||
} else {
|
||||
response.setHeader("Cross-Origin-Opener-Policy", "");
|
||||
response.setHeader("Cross-Origin-Resource-Policy", "");
|
||||
response.setHeader("Cross-Origin-Embedder-Policy", "");
|
||||
}
|
||||
|
||||
// 4) 不再设置已废弃的 Feature-Policy,避免与 Permissions-Policy 冲突
|
||||
// Permissions-Policy 已在 CspFilter 中统一设置
|
||||
}
|
||||
|
||||
private boolean isPotentiallyTrustworthy(HttpServletRequest request) {
|
||||
boolean isSecure = request.isSecure();
|
||||
String forwardedProto = request.getHeader("X-Forwarded-Proto");
|
||||
if (!isSecure && forwardedProto != null) {
|
||||
isSecure = "https".equalsIgnoreCase(forwardedProto);
|
||||
}
|
||||
String host = request.getServerName();
|
||||
boolean isLocalhost = "localhost".equalsIgnoreCase(host) || "127.0.0.1".equals(host);
|
||||
return isSecure || isLocalhost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// 清理资源
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import com.bonus.digitalSignage.utils.SystemUtils;
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
|
|
@ -16,28 +15,6 @@ import com.bonus.digitalSignage.table.PageTableArgumentResolver;
|
|||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 跨域支持
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public WebMvcConfigurer corsConfigurer() {
|
||||
return new WebMvcConfigurer() {
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**").allowedMethods("*");
|
||||
// registry.addMapping("/**")
|
||||
// .allowedOrigins("http://example.com")
|
||||
// .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
// .allowedHeaders("*")
|
||||
// .allowCredentials(true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* datatable分页解析
|
||||
*
|
||||
|
|
|
|||
|
|
@ -9,32 +9,27 @@ import java.io.IOException;
|
|||
|
||||
/**
|
||||
* @author 16043
|
||||
* 注意:此过滤器已不再设置CORS头,由CorsConfig统一处理
|
||||
*/
|
||||
@Component
|
||||
@Order(1)
|
||||
@Order(3) // 降低优先级,避免与CspFilter冲突
|
||||
public class OptionsRequestFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
// TODO Auto-generated method stub
|
||||
// 初始化方法
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader("Access-Control-Allow-Methods",
|
||||
"POST, GET, OPTIONS, DELETE");
|
||||
response.setHeader("Access-Control-Max-Age", "3600");
|
||||
response.setHeader("Access-Control-Allow-Headers",
|
||||
"Content-Type, x-requested-with, X-Custom-Header, Token,Authorization");
|
||||
// 不再设置CORS头,由CorsConfig统一处理
|
||||
// 只处理请求转发
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// TODO Auto-generated method stub
|
||||
// 清理资源
|
||||
}
|
||||
}
|
||||
|
|
@ -75,10 +75,8 @@ public class TokenFilter extends OncePerRequestFilter {
|
|||
throws ServletException, IOException {
|
||||
String method= request.getMethod();
|
||||
if("options".equals(method.toLowerCase())){
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
|
||||
response.setHeader("Access-Control-Max-Age", "3600");
|
||||
response.setHeader("Access-Control-Allow-Headers", "Content-Type, x-requested-with, X-Custom-Header, Token,Authorization");
|
||||
// 移除硬编码的跨域配置,让CorsConfig统一处理
|
||||
// 只设置认证信息,不设置CORS头
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(new LoginUser(),
|
||||
null, null);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
|
|
|||
|
|
@ -199,7 +199,11 @@ public class UserController {
|
|||
@LogAnnotation(operModul = "系统管理-用户管理", operation = "当前登录用户", operDesc = "系统级事件",operType="查询")
|
||||
public ServerResponse currentUser() {
|
||||
try {
|
||||
return ServerResponse.createSuccess(UserUtil.getLoginUser());
|
||||
LoginUser loginUser = UserUtil.getLoginUser();
|
||||
if(Objects.nonNull(loginUser)){
|
||||
loginUser.setPassword("");
|
||||
}
|
||||
return ServerResponse.createSuccess(loginUser);
|
||||
}catch (Exception e){
|
||||
log.error(e.toString(),e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.bonus.digitalSignage.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
|
@ -8,10 +10,11 @@ import com.alibaba.fastjson.JSONObject;
|
|||
|
||||
public class ResponseUtil {
|
||||
|
||||
|
||||
public static void responseJson(HttpServletResponse response, int status, Object data) {
|
||||
try {
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
||||
// response.setHeader("Access-Control-Allow-Origin", allowedOrigins.toString());
|
||||
// response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.setStatus(status);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
server.port=11998
|
||||
server.servlet.context-path=/gzDigitalSignage
|
||||
spring.datasource.url=jdbc:mysql://192.168.0.16:4419/gz_digital_signage?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=Bonus@admin123!
|
||||
#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/yn_img_tool?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
|
||||
#spring.datasource.url=jdbc:mysql://192.168.0.16:4419/gz_digital_signage?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
|
||||
#spring.datasource.username=root
|
||||
#spring.datasource.password=ccw1998@yyt1999
|
||||
#spring.datasource.password=Bonus@admin123!
|
||||
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/gz_digital_signage?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=ccw1998@yyt1999
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.datasource.max-idle=10
|
||||
spring.datasource.max-wait=60000
|
||||
|
|
@ -18,14 +18,13 @@ mybatis.mapper-locations=classpath:mappers/*/*Mapper.xml
|
|||
mybatis.type-aliases-package=com.bonus.digitalSignage.*.vo
|
||||
#redis config
|
||||
|
||||
spring.redis.host=192.168.0.7
|
||||
spring.redis.port=16379
|
||||
spring.redis.database=6
|
||||
spring.redis.password=Bonus@admin123!
|
||||
#spring.redis.host=192.168.0.7
|
||||
#spring.redis.port=16379
|
||||
#spring.redis.database=6
|
||||
#spring.redis.password=Bonus@admin123!
|
||||
|
||||
#spring.redis.host=127.0.0.1
|
||||
#spring.redis.port=6379
|
||||
#spring.redis.password=liang971108lu@L
|
||||
spring.redis.host=127.0.0.1
|
||||
spring.redis.port=6379
|
||||
|
||||
# \u65E5\u5FD7
|
||||
logging.config=classpath:logback-boot.xml
|
||||
|
|
@ -66,3 +65,39 @@ download.output.dir=/data/digitalSignage/final
|
|||
#????
|
||||
quartz.scan=false
|
||||
|
||||
# CSP和安全头配置
|
||||
# 是否启用CSP报告模式(true为仅报告,false为强制执行)
|
||||
csp.report-only=false
|
||||
|
||||
# 是否允许页面在iframe中显示(true为允许同源iframe,false为完全禁止)
|
||||
csp.allow-iframe=true
|
||||
|
||||
# 是否启用WebGL支持(true为启用,false为禁用)
|
||||
csp.enable-webgl=true
|
||||
|
||||
# 跨域配置
|
||||
# 允许的源(多个用逗号分隔)
|
||||
cors.allowed-origins=http://localhost:11998,http://127.0.0.1:11998,http://192.168.0.39:11998,http://192.168.0.14:11998,http://36.33.26.201:11998
|
||||
|
||||
# 允许的HTTP方法
|
||||
cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
|
||||
|
||||
# 允许的请求头
|
||||
cors.allowed-headers=Content-Type,X-Requested-With,Token,Authorization,X-Custom-Header
|
||||
|
||||
# 是否允许携带认证信息
|
||||
cors.allow-credentials=true
|
||||
|
||||
# 预检请求缓存时间(秒)
|
||||
cors.max-age=3600
|
||||
|
||||
# 安全头配置
|
||||
# 是否启用严格的安全头
|
||||
security.headers.strict=true
|
||||
|
||||
# 是否启用HSTS(HTTP严格传输安全)
|
||||
security.hsts.enabled=true
|
||||
|
||||
# 是否清除服务器信息头
|
||||
security.headers.clear-server-info=true
|
||||
|
||||
|
|
|
|||
|
|
@ -34,10 +34,12 @@
|
|||
<div class="layui-layout layui-layout-admin" style="border-bottom: solid 5px #eeecec;height: 65px;">
|
||||
<!-- 标题栏 -->
|
||||
<div class="layui-header header header-demo" style="background-color: #00377A;height: 65px;">
|
||||
<input type="hidden" name="_csrf" id="csrfToken">
|
||||
<div class="layui-main">
|
||||
<div class="admin-login-box">
|
||||
<a class="logo" style="left: 0;" href="javascript:void(0)">
|
||||
<span style="font-size: 22px;color: #fff;letter-spacing: 2px;">项目一体化作战动态管控平台</span>
|
||||
<!-- <span style="font-size: 22px;color: #fff;letter-spacing: 2px;">项目一体化管控平台</span>-->
|
||||
</a>
|
||||
<div class="admin-side-toggle" style="display: none;">
|
||||
<i class="fa fa-bars" aria-hidden="true"></i>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
// 自动设置CSRF令牌
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 或者从cookie中获取(如果使用CookieCsrfTokenRepository)
|
||||
const csrfCookie = document.cookie.split('; ')
|
||||
.find(row => row.startsWith('XSRF-TOKEN='));
|
||||
if (csrfCookie) {
|
||||
const token = decodeURIComponent(csrfCookie.split('=')[1]);
|
||||
document.getElementById('csrfToken').value = token;
|
||||
}
|
||||
});
|
||||
if (top != self) {
|
||||
parent.location.href = '/digitalSignage/login.html';
|
||||
}
|
||||
var token = localStorage.getItem("token");
|
||||
if (token != null && token.trim().length != 0) {
|
||||
$.ajax({
|
||||
type: 'get',
|
||||
url: ctxPath + '/users/current?token=' + token,
|
||||
success: function (data) {
|
||||
location.href = ctxPath + '/index.html';
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
console.log(xhr);
|
||||
var msg = xhr.responseText;
|
||||
var response = JSON.parse(msg);
|
||||
var code = response.code;
|
||||
var message = response.message;
|
||||
if (code == 401) {
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function login(obj) {
|
||||
$(obj).attr("disabled", true);
|
||||
|
||||
var username = $.trim($('#username').val());
|
||||
var password = $.trim($('#password').val());
|
||||
if (username == "" || password == "") {
|
||||
$("#info").html('用户名或者密码不能为空');
|
||||
$(obj).attr("disabled", false);
|
||||
} else {
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: ctxPath + '/login',
|
||||
data: {
|
||||
username: encryptCBC(username),
|
||||
password: encryptCBC(password),
|
||||
_csrf: $('#csrfToken').val()
|
||||
},
|
||||
success: function (data) {
|
||||
localStorage.setItem("token", data.token);
|
||||
location.href = ctxPath + '/index.html';
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
var msg = xhr.responseText;
|
||||
var response = JSON.parse(msg);
|
||||
$("#info").html(response.message);
|
||||
$(obj).attr("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,13 @@
|
|||
// 自动设置CSRF令牌
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 或者从cookie中获取(如果使用CookieCsrfTokenRepository)
|
||||
const csrfCookie = document.cookie.split('; ')
|
||||
.find(row => row.startsWith('XSRF-TOKEN='));
|
||||
if (csrfCookie) {
|
||||
const token = decodeURIComponent(csrfCookie.split('=')[1]);
|
||||
document.getElementById('csrfToken').value = token;
|
||||
}
|
||||
});
|
||||
let urlIds=new Array();
|
||||
initMenu();
|
||||
initsy();
|
||||
|
|
@ -132,8 +142,11 @@ function showLoginInfo(){
|
|||
|
||||
function logout(){
|
||||
$.ajax({
|
||||
type : 'get',
|
||||
type : 'POST',
|
||||
url : ctxPath + '/logout',
|
||||
data: {
|
||||
_csrf: $('#csrfToken').val()
|
||||
},
|
||||
success : function(data) {
|
||||
localStorage.removeItem("token");
|
||||
location.href= ctxPath + '/login.html';
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@ var ctxPath = getContextPath();
|
|||
let aqEnnable = true;//是否开启安全验证
|
||||
// console.log(ctxPath)
|
||||
var url=window.location.href;
|
||||
console.log(url)
|
||||
// console.log(url)
|
||||
|
||||
let test_url="http://192.168.0.14:11998/gzDigitalSignage";
|
||||
let dev_url="http://36.33.26.201:21100/gzDigitalSignage";
|
||||
let local_url="http://127.0.0.1:11998/gzDigitalSignage";
|
||||
let local_url="http://192.168.0.39:11998/gzDigitalSignage";
|
||||
let dataUrl = "http://112.27.246.86:21997/gzDigitalSignage"
|
||||
|
||||
if(url.indexOf("36.33.26.201")!=-1){
|
||||
dataUrl = dev_url;
|
||||
}else if(url.indexOf("127.0.0.1")!=-1 || url.indexOf("localhost")!=-1){
|
||||
}else if(url.indexOf("127.0.0.1")!=-1 || url.indexOf("localhost")!=-1 || url.indexOf("192.168.0.39")!=-1){
|
||||
dataUrl = local_url
|
||||
}else if(url.indexOf("192.168.0.14")!=-1){
|
||||
dataUrl = test_url
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
<div id="darkbannerwrap"></div>
|
||||
|
||||
<form id="login-form" method="post" onsubmit="return false;">
|
||||
<!-- 添加CSRF令牌 -->
|
||||
<input type="hidden" name="_csrf" id="csrfToken">
|
||||
<input id="username" name="username" placeholder="用户名" type="text" autocomplete="off">
|
||||
<hr class="hr15">
|
||||
<input id="password" name="password" placeholder="密码" type="password" autocomplete="off">
|
||||
|
|
@ -33,61 +35,6 @@
|
|||
<script src="js/libs/jquery-3.7.0.min.js"></script>
|
||||
<script src="js/my/aes.js"></script>
|
||||
<script src="js/publicJs.js"></script>
|
||||
<script type="text/javascript">
|
||||
if (top != self) {
|
||||
parent.location.href = '/digitalSignage/login.html';
|
||||
}
|
||||
var token = localStorage.getItem("token");
|
||||
if (token != null && token.trim().length != 0) {
|
||||
$.ajax({
|
||||
type: 'get',
|
||||
url: ctxPath + '/users/current?token=' + token,
|
||||
success: function (data) {
|
||||
location.href = ctxPath + '/index.html';
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
console.log(xhr);
|
||||
var msg = xhr.responseText;
|
||||
var response = JSON.parse(msg);
|
||||
var code = response.code;
|
||||
var message = response.message;
|
||||
if (code == 401) {
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function login(obj) {
|
||||
$(obj).attr("disabled", true);
|
||||
|
||||
var username = $.trim($('#username').val());
|
||||
var password = $.trim($('#password').val());
|
||||
if (username == "" || password == "") {
|
||||
$("#info").html('用户名或者密码不能为空');
|
||||
$(obj).attr("disabled", false);
|
||||
} else {
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: ctxPath + '/login',
|
||||
data: {
|
||||
username: encryptCBC(username),
|
||||
password: encryptCBC(password)
|
||||
},
|
||||
success: function (data) {
|
||||
localStorage.setItem("token", data.token);
|
||||
location.href = ctxPath + '/index.html';
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
var msg = xhr.responseText;
|
||||
var response = JSON.parse(msg);
|
||||
$("#info").html(response.message);
|
||||
$(obj).attr("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="js/login.js"></script>
|
||||
|
||||
</html>
|
||||
Loading…
Reference in New Issue