1、完成了租户header头验证

2、jwt功能实现
3、解决了前一个递归调用的问题。
This commit is contained in:
asp_ly 2024-11-27 23:25:09 +08:00
parent 07c2d803b5
commit f11564fa50
7 changed files with 141 additions and 55 deletions

View File

@ -0,0 +1,56 @@
package com.qqchen.deploy.backend.common.config;
import com.qqchen.deploy.backend.common.interceptor.TenantInterceptor;
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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import java.util.Arrays;
import java.util.Collections;
@Configuration
public class AppConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 允许所有来源
configuration.setAllowedOrigins(Collections.singletonList("*"));
// 允许的请求方法
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
// 允许的请求头
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Requested-With"));
// 暴露的响应头
configuration.setExposedHeaders(Collections.singletonList("Authorization"));
// 允许携带凭证注意如果您设置了所有来源要设置凭证可能会遇到问题
configuration.setAllowCredentials(true);
// 设置预检最大有效期
configuration.setMaxAge(3600L);
// URL 基于 CORS 配置源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); // 针对所有路径注册 CORS 配置
return source;
}
@Bean
public WebMvcConfigurer webMvcConfigurer(TenantInterceptor tenantInterceptor) {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 LocaleChangeInterceptor
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
registry.addInterceptor(localeChangeInterceptor);
// 注册 TenantInterceptor
registry.addInterceptor(tenantInterceptor).addPathPatterns("/**"); // Ensures it intercepts all requests
}
};
}
}

View File

@ -1,27 +0,0 @@
package com.qqchen.deploy.backend.common.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 允许所有来源
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的HTTP方法
.allowedHeaders("*") // 允许所有header
.maxAge(3600); // 预检请求的有效期单位为秒
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
registry.addInterceptor(localeChangeInterceptor);
}
}

View File

@ -0,0 +1,55 @@
package com.qqchen.deploy.backend.common.interceptor;
import com.qqchen.deploy.backend.common.context.TenantContext;
import com.qqchen.deploy.backend.common.enums.ResponseCode;
import com.qqchen.deploy.backend.common.exception.BusinessException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Arrays;
import java.util.List;
@Component
public class TenantInterceptor implements HandlerInterceptor {
private final AntPathMatcher pathMatcher = new AntPathMatcher();
// 不需要进行租户隔离的路径
private final List<String> excludePaths = Arrays.asList(
// "/api/v1/users/login",
"/api/system/tenants/list",
"/swagger-ui/**",
"/v3/api-docs/**"
);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String path = request.getRequestURI();
System.out.println("Intercepting path: " + path); // 添加日志
// 如果是白名单路径不进行租户校验
if (isExcludePath(path)) {
TenantContext.clear();
return true;
}
// 获取租户ID
String tenantId = request.getHeader("X-Devops-Tenant-Id");
if (tenantId == null || tenantId.trim().isEmpty()) {
throw new BusinessException(ResponseCode.TENANT_NOT_FOUND);
}
TenantContext.setTenantId(tenantId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
TenantContext.clear();
}
private boolean isExcludePath(String path) {
return excludePaths.stream()
.anyMatch(pattern -> pathMatcher.match(pattern, path));
}
}

View File

@ -42,7 +42,7 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// .cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
@ -59,28 +59,16 @@ public class SecurityConfig {
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("anonymous")
.password("{noop}")
.roles("ANONYMOUS")
.build();
return new InMemoryUserDetailsManager(user);
}
//TODO 这里会导致无限递归循环报错
// @Bean
// public UserDetailsService userDetailsService() {
// UserDetails user = User.withUsername("anonymous")
// .password("{noop}")
// .roles("ANONYMOUS")
// .build();
// return new InMemoryUserDetailsManager(user);
// }
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Tenant-ID"));
configuration.setExposedHeaders(Arrays.asList("Authorization", "X-Tenant-ID"));
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public PasswordEncoder passwordEncoder() {

View File

@ -0,0 +1,17 @@
package com.qqchen.deploy.backend.common.utils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordGenerator {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "admin123";
String encodedPassword = encoder.encode(rawPassword);
System.out.println("Raw password: " + rawPassword);
System.out.println("Encoded password: " + encodedPassword);
// 验证密码
boolean matches = encoder.matches(rawPassword, encodedPassword);
System.out.println("Password matches: " + matches);
}
}

View File

@ -43,9 +43,6 @@ public class User extends Entity<Long> {
@Column(nullable = false)
private Boolean enabled = true;
@Column(name = "dept_id")
private Long deptId;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@ToString.Exclude

View File

@ -3,9 +3,9 @@ server:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/2024_11_24_platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root
password: root
url: jdbc:mysql://192.168.1.111:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: deploy-ease-platform
password: qichen5210523
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate: