微服务改造

This commit is contained in:
litianxiang
2026-04-22 11:15:34 +08:00
parent 32a228485b
commit 92e7dbf258
26 changed files with 75 additions and 1398 deletions

View File

@@ -1,24 +1,24 @@
package com.ai.da;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@Slf4j
@SpringBootApplication
@EnableScheduling
@EnableAsync
@EnableFeignClients
@EnableDiscoveryClient
public class AiDaApplication {
public static void main(String[] args) {
SpringApplication.run(AiDaApplication.class, args);
log.info("AiDaApplication 启动完成!");
}
}
package com.ai.da;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@Slf4j
@SpringBootApplication
@EnableScheduling
@EnableAsync
@EnableFeignClients
@EnableDiscoveryClient
public class AiDaApplication {
public static void main(String[] args) {
SpringApplication.run(AiDaApplication.class, args);
log.info("AiDaApplication 启动完成!");
}
}

View File

@@ -1,13 +1,16 @@
package com.ai.da.common.config;
import com.ai.da.common.interceptor.UserContextInterceptor;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
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 jakarta.annotation.Resource;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
@@ -17,11 +20,20 @@ public class WebConfig implements WebMvcConfigurer {
static final String ORIGINS[] = new String[]{"GET", "POST", "PUT", "DELETE"};
@Resource
private UserContextInterceptor userContextInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOriginPatterns("*").allowCredentials(true).allowedMethods(ORIGINS).maxAge(3600);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userContextInterceptor)
.addPathPatterns("/**");
}
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)

View File

@@ -1,12 +0,0 @@
package com.ai.da.common.config.exception;
public class TokenMissingOrExpiredException extends RuntimeException {
public TokenMissingOrExpiredException(String message) {
super(message);
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}

View File

@@ -1,14 +0,0 @@
package com.ai.da.common.constant;
/**
* @author yanglei
* 异常类常量
*/
public class TokenConstant {
/**
* 固定session
*/
public static final String FIX_SESSION = "qrLS_003af9d8c1363fc4_6c97e932665c4460a1fdbfbf47ce3490";
public static final String PERMISSIONS = "9672233956";
}

View File

@@ -1,33 +0,0 @@
package com.ai.da.common.httpdata.token;
public enum TokenApis {
/**
* token
*/
GET_TOKEN("POST", "/api/openApi/v2/Weixin/QrCodeLoginCheck?session="),
GENERATE_USER("POST", "/api/openApi/v2/Welink/TopicGetjson");
private String method;
private String url;
TokenApis(String method, String url) {
this.method = method;
this.url = url;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}

View File

@@ -1,42 +0,0 @@
package com.ai.da.common.httpdata.token;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class TokenQuery {
private static final String GET_TOKEN_DOMAIN = "https://www.szsige.com";
private static final String GENERATE_USER_DOMAIN = "https://www.szsige.com";
public static JSONObject getToken(String session) {
String url = GET_TOKEN_DOMAIN + TokenApis.GET_TOKEN.getUrl() + session;
log.info("获取用户token接口请求url:" + url);
HttpResponse httpResponse = HttpUtil.createPost(url).execute();
log.info("获取用户token接口响应" + httpResponse);
if (httpResponse.isOk() && StrUtil.isNotEmpty(httpResponse.body())) {
return JSONObject.parseObject(httpResponse.body());
}
return null;
}
public static JSONObject generateUser(Map<String, Object> param, String token) {
HttpResponse httpResponse = HttpUtil.createPost(GENERATE_USER_DOMAIN + TokenApis.GENERATE_USER.getUrl())
.body(JSONObject.toJSONString(param != null ? param : new HashMap<>()))
.header("Authorization", "Bearer " + token)
.header("X-Promiss", "9672233956")
.execute();
log.info("生成用户信息接口响应:" + httpResponse);
if (httpResponse.isOk() && StrUtil.isNotEmpty(httpResponse.body())) {
return JSONObject.parseObject(httpResponse.body());
}
return null;
}
}

View File

@@ -1,27 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.response.Response;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.JSONResponseUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName UserAuthAccessDeniedHandler
* @Description 无权限处理类
* @Author dwjian
* @Date 2020/7/9 20:30
*/
@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException exception) throws IOException {
Response<String> response = Response.error(ResultEnum.NO_PERMISSION.getCode(), ResultEnum.NO_PERMISSION.getMsg());
JSONResponseUtils.build(httpServletResponse, response);
}
}

View File

@@ -1,29 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.response.Response;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.JSONResponseUtils;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName UserAuthenticationEntryPointHandler
* @Description 未登录处理类
* @Author dwjian
* @Date 2020/7/9 20:13
*/
@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
Response<String> response = Response.error(ResultEnum.NO_LOGIN.getCode(), ResultEnum.NO_LOGIN.getMsg());
httpServletResponse.setStatus(401);
JSONResponseUtils.build(httpServletResponse, response);
}
}

View File

@@ -1,25 +0,0 @@
package com.ai.da.common.security;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
/**
* @author: dangweijian
* @description: 认证管理器
* @create: 2020-07-10 15:58
**/
@Component
public class UserAuthenticationManager implements AuthenticationManager {
@Resource
private UserAuthenticationProvider userAuthenticationProvider;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return userAuthenticationProvider.authenticate(authentication);
}
}

View File

@@ -1,59 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.config.RsaProperties;
import com.ai.da.common.utils.RsaDecryptUtils;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
/**
* @author: dangweijian
* @description: 登录校验处理类
* @create: 2020-07-09 14:39
**/
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
try {
password = RsaDecryptUtils.decrypt(password, RsaProperties.privateKey);
} catch (Exception e) {
throw new BadCredentialsException("用户名或密码错误");
}
// User user = userService.getByUsername(userName);
// if (user == null) {
// throw new UsernameNotFoundException("用户名或密码错误");
// }
// //账号已冻结
// if(user.getStatus() == 1){
// throw new LockedException("账号已冻结");
// }
// if(!passwordEncoder.matches(password, user.getPassword())){
// throw new BadCredentialsException("用户名或密码错误");
// }
// //超级管理员
// Set<SimpleGrantedAuthority> authorities = new HashSet<>();
// if(user.getIsAdmin()) {
// authorities.add(new SimpleGrantedAuthority("admin"));
// return new UsernamePasswordAuthenticationToken(user, password, authorities);
// }else {
// List<RoleMenuDto> userMenus = menuService.getRoleMenusByUserId(user.getId(), null);
// if(CollUtil.isNotEmpty(userMenus)){
// authorities.addAll(userMenus.stream().map(RoleMenuDto::getPermission).filter(StringUtils::isNotEmpty).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
// }
// return new UsernamePasswordAuthenticationToken(user, password, authorities);
// }
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}
}

View File

@@ -1,48 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.response.Response;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.JSONResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName UserLoginFailureHandler
* @Description 登录失败处理类
* @Author dwjian
* @Date 2020/7/9 20:17
*/
@Slf4j
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
Response<String> response;
if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
response = Response.fail(e.getMessage());
} else if (e instanceof LockedException) {
response = Response.fail(ResultEnum.ACCOUNT_LOCK);
} else if (e instanceof CredentialsExpiredException) {
response = Response.fail("证书过期,请联系管理员!");
} else if (e instanceof AccountExpiredException) {
response = Response.fail("账户过期,请联系管理员!");
} else if (e instanceof DisabledException) {
response = Response.fail("账户被禁用,请联系管理员!");
} else if (e instanceof AuthenticationServiceException) {
response = Response.fail(e.getMessage());
} else {
log.error("登录失败:", e);
response = Response.fail("登录失败!");
}
JSONResponseUtils.build(httpServletResponse, response);
}
}

View File

@@ -1,51 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.JSONResponseUtils;
import com.ai.da.common.response.Response;
import com.ai.da.common.security.jwt.JWTTokenHelper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: dangweijian
* @description: 登录成功处理类
* @create: 2020-07-09 14:58
**/
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private JWTTokenHelper jwtTokenHelper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// User user = (User) authentication.getPrincipal();
// AuthPrincipalVo principal = new AuthPrincipalVo();
// BeanUtils.copyProperties(user, principal);
// // 获取用户角色
// List<UserRoleDto> userRoles = roleService.getUserRoles(user.getId(), 0);
// principal.setRoles(userRoles);
// // 获取角色部门
// if(CollUtil.isNotEmpty(userRoles)){
// principal.setDepts(deptService.getDeptByRoleIds(userRoles.stream().map(UserRoleDto::getRoleId).collect(Collectors.toList())));
// }
// // 用户角色权限
// List<String> authorities = new ArrayList<>(authentication.getAuthorities()).stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
// principal.setAuthorities(authorities);
// AuthVo authVo = new AuthVo();
// authVo.setAuthorities(authorities);
// authVo.setToken(jwtTokenHelper.createToken(principal));
// authVo.setPrincipal(principal);
// user.setLastLoginTime(new Date());
// userService.updateById(user);
JSONResponseUtils.build(response, Response.success(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMsg(), null));
}
}

View File

@@ -1,34 +0,0 @@
package com.ai.da.common.security;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* @ClassName UserPermissionEvaluator
* @Description 权限校验处理器
* @Author dwjian
* @Date 2020/7/12 10:16
*/
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
System.out.println(authentication);
System.out.println(targetUrl);
System.out.println(permission);
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
System.out.println(authentication);
System.out.println(targetId);
System.out.println(targetType);
System.out.println(permission);
return false;
}
}

View File

@@ -1,107 +0,0 @@
package com.ai.da.common.security.config;
import com.ai.da.common.security.*;
import com.ai.da.common.security.filter.AuthenticationFilter;
import com.ai.da.common.security.filter.UserAuthenticationProcessingFilter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import jakarta.annotation.Resource;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityConfig {
@Resource
private SecurityProperties securityProperties;
@Resource
private UserLoginSuccessHandler userLoginSuccessHandler;
@Resource
private UserLoginFailureHandler userLoginFailureHandler;
@Resource
private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
@Resource
private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
@Resource
private UserAuthenticationManager userAuthenticationManager;
@Resource
private UserAuthenticationProcessingFilter userAuthenticationProcessingFilter;
/**
* 不通过注入spring管理 让Security来管理 这样自定义的Filter就不会走,.permitAll()才能起作用
*/
@Resource
private AuthenticationFilter authenticationFilter;
@Resource
private UserPermissionEvaluator userPermissionEvaluator;
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return this.userAuthenticationManager;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
.requestMatchers(
new AntPathRequestMatcher("/doc.html"),
new AntPathRequestMatcher("/swagger-ui.html"),
new AntPathRequestMatcher("/swagger-ui/**"),
new AntPathRequestMatcher("/swagger-resources/**"),
new AntPathRequestMatcher("/v2/api-docs"),
new AntPathRequestMatcher("/v3/api-docs/**"),
new AntPathRequestMatcher("/webjars/**")
).permitAll()
.requestMatchers(securityProperties.getIgnorePaths()).permitAll()
.anyRequest().authenticated()
)
.headers(headers -> headers
.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)
.cacheControl(cache -> cache.disable())
)
.exceptionHandling(exception -> exception
.authenticationEntryPoint(userAuthenticationEntryPointHandler)
.accessDeniedHandler(userAuthAccessDeniedHandler)
)
.formLogin(form -> form
.loginProcessingUrl(securityProperties.getAuthApi())
.successHandler(userLoginSuccessHandler)
.failureHandler(userLoginFailureHandler)
)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
//自定义过滤器在登录时认证用户名、密码
httpSecurity.addFilterAt(userAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(authenticationFilter, BasicAuthenticationFilter.class);
return httpSecurity.build();
}
@Bean
public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(userPermissionEvaluator);
return handler;
}
}

View File

@@ -1,26 +0,0 @@
package com.ai.da.common.security.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author: dangweijian
* @description: JWT配置类
* @create: 2020-07-09 09:38
**/
@Data
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
private String jwtSecret;
private String jwtTokenHeader;
private String jwtTokenPrefix;
private long jwtExpiration;
private String[] ignorePaths;
private String authApi;
}

View File

@@ -1,162 +0,0 @@
package com.ai.da.common.security.filter;
import cn.hutool.core.util.StrUtil;
import com.ai.da.common.config.exception.TokenMissingOrExpiredException;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.security.config.SecurityProperties;
import com.ai.da.common.security.jwt.JWTTokenHelper;
import com.ai.da.common.utils.LocalCacheUtils;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.common.utils.MultiReadHttpServletRequest;
import com.ai.da.common.utils.MultiReadHttpServletResponse;
import com.ai.da.common.utils.RequestInfoUtil;
import com.ai.da.model.vo.AuthPrincipalVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* @author: dangweijian
* @description: 认证拦截器
* @create: 2020-07-10 16:50
**/
@Slf4j
@Configuration
public class AuthenticationFilter extends OncePerRequestFilter {
@Resource
private JWTTokenHelper jwtTokenHelper;
@Resource
private SecurityProperties properties;
@Resource
private RedisUtil redisUtil;
private static final List<String> FILTER_URL =
Arrays.asList("/favicon.ico", "/doc.html", "/swagger-ui.html",
"/swagger-resources", "/swagger-resources/", "/swagger-resources/configuration/ui", "/swagger-resources/configuration/security",
"/webjars/", "/v2/api-docs", "/v3/api-docs", "/v3/api-docs/swagger-config",
"/api/account/login", "/api/account/preLogin", "api/account/sendEmail","api/account/noLoginRequired",
"/api/account/resetPwd",
"/api/python/saveGeneratePicture", "/api/python/getLibraryByUserId",
"/api/third/party/addUser","/api/third/party/addTrialUser", "/api/third/party/editUser", "/api/element/initDefaultSysFile",
"/api/third/party/addNoLoginRequiredNew","/api/third/party/deleteNoLoginRequiredNew","/api/third/party/updateNoLoginRequiredNew",
"/api/third/party/existNoLoginRequired","/api/third/party/getRedirectUrl",
"/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify","/api/paypal/ipn/back","/api/alipay-hk/trade/notify",
"/api/portfolio/page", "/api/portfolio/detail", "/api/portfolio/commentPage", "/api/portfolio/viewsIncrease",
"/api/account/designWorksRegister","/api/account/questionnaire","/api/stripe/trade/notify",
"/notification","/api/account/activateNewEmail","/api/third/party/auth/google_callback","/api/third/party/parseGoogleCredential","/api/third/party/receiveDesignResults","/api/third/party/parseWeChatCode","/api/third/party/receiveDesignParams"
, "/api/account/schoolLogin", "/api/account/enterpriseLogin", "/api/account/organizationNameSearch",
"/api/llm/stream",
//GlobalAwardController
"/api/global-award"
);
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, @NonNull FilterChain filterChain) throws ServletException, IOException {
String requestURI = httpServletRequest.getRequestURI();
if (calculateUrl(requestURI)/* || hasAuthorizationToken(httpServletRequest)*/) {
StopWatch stopWatch = new StopWatch();
HttpServletRequest wrappedRequest = httpServletRequest;
HttpServletResponse wrappedResponse = httpServletResponse;
try {
stopWatch.start();
if ((httpServletRequest.getContentType() == null && httpServletRequest.getContentLength() > 0) || (httpServletRequest.getContentType() != null && !httpServletRequest.getContentType().contains("application/json"))) {
extracted(wrappedRequest);
filterChain.doFilter(wrappedRequest, wrappedResponse);
} else {
wrappedRequest = new MultiReadHttpServletRequest(httpServletRequest);
wrappedResponse = new MultiReadHttpServletResponse(httpServletResponse);
extracted(wrappedRequest);
// excel导出使用原始response,不对响应做包装
if (requestURI.equals("/api/account/exportAccountsToExcel")) {
filterChain.doFilter(httpServletRequest, httpServletResponse); // 不包装
} else {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
}
} catch (Exception e) {
SecurityContextHolder.clearContext();
throw e;
} finally {
stopWatch.stop();
}
} else {
//先清空当前线程变量,防止上一个线程遗留
UserContext.delete();
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
private Boolean calculateUrl(String requestURI) {
String filterUrl = FILTER_URL.stream().filter(url -> requestURI.contains(url)).findFirst().orElse(null);
return null == filterUrl ? Boolean.TRUE : Boolean.FALSE;
}
private boolean hasAuthorizationToken(HttpServletRequest request) {
String authorizationHeader = request.getHeader("Authorization");
return authorizationHeader != null && authorizationHeader.startsWith("Bearer");
}
private void extracted(HttpServletRequest request) {
String jwtToken = request.getHeader(properties.getJwtTokenHeader());
// log.debug("后台检查令牌:{}", jwtToken);
if (StrUtil.isBlank(jwtToken)) {
String ipAddress = RequestInfoUtil.getIpAddress(request);
log.info("本次请求的ip为 " + ipAddress);
// throw new RuntimeException("请传入token");
throw new TokenMissingOrExpiredException("请传入token");
}
if(jwtToken.equals("Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiIyIiwic3ViIjoie1wiaWRcIjoyLFwidXNlcm5hbWVcIjpcImxpcnNcIn0iLCJpYXQiOjE2NjU3NDEwODcsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE2NzQzODEwODd9.ShM9R_NNFD7oo1OvxrEgg7PFeWinOuAKkuInUCMQupp66s64Hhv8tN0Wwr83nIN4rHPqtn95wmd4msWcvaFYJA")){
//写死 暂时放行
return;
}
// 检查token
boolean validate = jwtTokenHelper.validateToken(jwtToken);
if (validate) {
AuthPrincipalVo principal = jwtTokenHelper.parserToUser(jwtToken);
if (principal == null) {
// throw new RuntimeException("TOKEN已过期请重新登录");
throw new TokenMissingOrExpiredException("TOKEN已过期请重新登录(token without userInfo)");
}
//先清空当前线程变量,防止上一个线程遗留
UserContext.delete();
//存取用户信息到缓存
UserContext.setUserHolder(principal);
// 校验 token先查本地缓存再查 Redis保证服务重启后仍然有效
String userIdStr = String.valueOf(principal.getId());
String cacheToken = LocalCacheUtils.getTokenCache(userIdStr);
if (StringUtils.isEmpty(cacheToken)) {
// 本地缓存为空时,尝试从 Redis 读取
cacheToken = redisUtil.getLoginToken(principal.getId());
if (StringUtils.isEmpty(cacheToken)) {
// throw new RuntimeException("TOKEN已过期请重新登录");
throw new TokenMissingOrExpiredException("TOKEN已过期请重新登录(cache & redis empty)");
}
// 将 Redis 中的 token 回填到本地缓存,减少后续 Redis 访问
LocalCacheUtils.setTokenCache(userIdStr, cacheToken);
}
if(!cacheToken.equals(jwtToken) ){
// throw new RuntimeException("TOKEN已过期请重新登录");
throw new TokenMissingOrExpiredException("TOKEN已过期请重新登录(token not match local cache)");
}
// UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(null, null);
// SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}

View File

@@ -1,69 +0,0 @@
package com.ai.da.common.security.filter;
import cn.hutool.core.util.StrUtil;
import com.ai.da.common.security.UserLoginSuccessHandler;
import com.ai.da.common.security.config.SecurityProperties;
import com.ai.da.common.utils.RedisCacheUtils;
import com.alibaba.fastjson.JSONObject;
import com.ai.da.common.security.UserAuthenticationManager;
import com.ai.da.common.security.UserLoginFailureHandler;
import com.ai.da.common.utils.MultiReadHttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* @author: dangweijian
* @description: 用户认证过滤器
* @create: 2020-07-10 15:58
**/
@Slf4j
@Component
public class UserAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
/**
* @param securityProperties 配置从配置中读取登录url
* @param authenticationManager 认证管理器
* @param adminAuthenticationSuccessHandler 认证成功处理器
* @param adminAuthenticationFailureHandler 认证失败处理器
*/
public UserAuthenticationProcessingFilter(SecurityProperties securityProperties, UserAuthenticationManager authenticationManager, UserLoginSuccessHandler adminAuthenticationSuccessHandler, UserLoginFailureHandler adminAuthenticationFailureHandler) {
super(new AntPathRequestMatcher(securityProperties.getAuthApi(), HttpMethod.POST.name()));
this.setAuthenticationManager(authenticationManager);
this.setAuthenticationSuccessHandler(adminAuthenticationSuccessHandler);
this.setAuthenticationFailureHandler(adminAuthenticationFailureHandler);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType() == null || !request.getContentType().contains("application/json")) {
throw new AuthenticationServiceException("请求头类型不支持: " + request.getContentType());
}
UsernamePasswordAuthenticationToken authRequest;
try {
MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(request);
// 将前端传递的数据转换成jsonBean数据格式
JSONObject jsonObject = JSONObject.parseObject(wrappedRequest.getBodyJsonStrByJson(wrappedRequest));
String code = jsonObject.getString("code");
String uuid = jsonObject.getString("uuid");
if (StrUtil.isEmpty(code) || StrUtil.isEmpty(uuid) || !code.equals(RedisCacheUtils.get("code-key-" + uuid, String.class))) {
throw new AuthenticationServiceException("验证码错误");
}
RedisCacheUtils.delete("code-key-" + uuid);
authRequest = new UsernamePasswordAuthenticationToken(jsonObject.get("username"), jsonObject.get("password"), null);
authRequest.setDetails(authenticationDetailsSource.buildDetails(wrappedRequest));
} catch (Exception e) {
throw new AuthenticationServiceException(e.getMessage());
}
return this.getAuthenticationManager().authenticate(authRequest);
}
}

View File

@@ -1,108 +0,0 @@
package com.ai.da.common.security.jwt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.common.security.config.SecurityProperties;
import com.ai.da.model.vo.AuthPrincipalVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
/**
* @author: dangweijian
* @description: JWT工具
* @create: 2020-07-09 09:27
**/
@Slf4j
@Component
public class JWTTokenHelper {
@Resource
private SecurityProperties securityProperties;
private static final String ISSUER = "DWJ";
private static final String AUTHORITIES = "authorities";
private static final String CHANGE_MAILBOX = "changeMailbox";
public String createToken(AuthPrincipalVo principal) {
SecretKey key = buildSigningKey();
String token = Jwts.builder()
.id(String.valueOf(principal.getId()))
.subject(JSONObject.toJSONString(principal))
.issuedAt(new Date())
.issuer(ISSUER)
.claim(AUTHORITIES, JSON.toJSONString(new ArrayList<>()))//自定义属性 权限
.expiration(new Date(System.currentTimeMillis() + securityProperties.getJwtExpiration()))
.signWith(key)
.compact();
token = securityProperties.getJwtTokenPrefix() + token;
return token;
}
public boolean validateToken(String token) {
Claims claims = parser(token);
if (MapUtil.isEmpty(claims)) {
return false;
}
return true;
}
public AuthPrincipalVo parserToUser(String token) {
String subject = parser(token).getSubject();
if (StrUtil.isNotEmpty(subject)) {
return JSONObject.parseObject(subject, AuthPrincipalVo.class);
}
return null;
}
public Claims parser(String token) {
token = token.replaceAll(securityProperties.getJwtTokenPrefix(), "");
SecretKey key = buildSigningKey();
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload();
}
public String createToken(Long userId, String userEmail){
SecretKey key = buildSigningKey();
String token = Jwts.builder()
.id(String.valueOf(userId))
.subject(userEmail + "_" + userId)
.issuedAt(new Date())
.issuer(ISSUER)
.claim(CHANGE_MAILBOX, JSON.toJSONString(new ArrayList<>()))//自定义属性 权限
.expiration(new Date(System.currentTimeMillis() + CommonConstant.CHANGE_MAILBOX_LINK_VALIDITY))
.signWith(key)
.compact();
return token;
}
public String parseToEmailAndId(String token) {
return parser(token).getSubject();
}
/**
* JWT 要求 HMAC-SHA 的密钥至少 256 bit这里统一扩展/哈希密钥长度避免 WeakKeyException。
*/
private SecretKey buildSigningKey() {
byte[] raw = securityProperties.getJwtSecret().getBytes(StandardCharsets.UTF_8);
if (raw.length < 32) {
raw = DigestUtil.sha256(raw);
}
return Keys.hmacShaKeyFor(raw);
}
}

View File

@@ -1,32 +0,0 @@
package com.ai.da.common.utils;
import com.ai.da.model.vo.AuthPrincipalVo;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityContextUtils {
public static AuthPrincipalVo getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() != null) {
return (AuthPrincipalVo) authentication.getPrincipal();
}
return null;
}
public static Long getCurrentUserId() {
AuthPrincipalVo currentUser = getCurrentUser();
if (currentUser != null) {
return currentUser.getId();
}
return null;
}
public static String getCurrentUsername() {
AuthPrincipalVo currentUser = getCurrentUser();
if (currentUser != null) {
return currentUser.getUsername();
}
return null;
}
}

View File

@@ -10,7 +10,8 @@ import com.ai.da.common.enums.LoginTypeEnum;
import com.ai.da.common.enums.ProductEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.security.jwt.JWTTokenHelper;
import com.ai.da.common.utils.TokenGenerateUtils;
import com.ai.da.feign.gateway.GatewayFeignClient;
import com.ai.da.feign.seller.SellerFeignClient;
import com.ai.da.common.utils.*;
import com.ai.da.mapper.primary.*;
@@ -95,11 +96,14 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
private AccountExtendMapper accountExtendMapper;
@Resource
private JWTTokenHelper jwtTokenHelper;
private TokenGenerateUtils tokenGenerateUtils;
@Resource
private SellerFeignClient sellerFeignClient;
@Resource
private GatewayFeignClient gatewayFeignClient;
@Resource
private AccountLoginLogService accountLoginLogService;
@@ -140,9 +144,6 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
@Resource
private RedisUtil redisUtil;
@Resource
private com.ai.da.common.security.config.SecurityProperties securityProperties;
@Resource
private UserFollowService userFollowService;
@@ -357,11 +358,11 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
principal.setUsername(account.getUserName());
principal.setLanguage(account.getLanguage());
principal.setCountry(account.getCountry());
String token2 = jwtTokenHelper.createToken(principal);
String token2 = tokenGenerateUtils.createToken(principal);
// 本地 JVM 缓存(适配旧逻辑)
LocalCacheUtils.setTokenCache(String.valueOf(account.getId()), token2);
// 同步写入 Redis重启后仍然可用
long jwtExpiration = securityProperties.getJwtExpiration();
long jwtExpiration = tokenGenerateUtils.getJwtExpiration();
redisUtil.setLoginToken(account.getId(), token2, jwtExpiration);
return token2;
}
@@ -618,12 +619,18 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
@Override
public Boolean logout(AccountLogoutDTO accountLogoutDTO) {
// jwt 本身失效比较难做,统一用缓存实现:删除缓存即失效
// 1. 删除本地缓存(保留,防止 Gateway 未启动时还能本地验证)
String userIdStr = String.valueOf(accountLogoutDTO.getUserId());
LocalCacheUtils.delTokenCache(userIdStr);
// 同时删除 Redis 中的 token,防止服务重启后仍然有效
// 2. 删除 Redis 中的 tokenGateway 黑名单会接力生效)
redisUtil.deleteLoginToken(accountLogoutDTO.getUserId());
// 同步调用 seller 清除本地缓存
// 3. 调用 Gateway 黑名单接口,将 token 加入 Redis 黑名单
try {
gatewayFeignClient.logout(accountLogoutDTO.getUserId());
} catch (Exception e) {
log.warn("调用 Gateway 黑名单接口失败userId={}, error={}", accountLogoutDTO.getUserId(), e.getMessage());
}
// 4. 同步调用 seller 清除本地缓存(兼容未接入 Gateway 的节点)
try {
sellerFeignClient.clearTokenCache(accountLogoutDTO.getUserId());
} catch (Exception e) {
@@ -2163,7 +2170,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
redisUtil.addToString(key, newMailbox, CommonConstant.CHANGE_MAILBOX_LINK_VALIDITY / 1000);
String username = userHolder.getUsername();
String token = jwtTokenHelper.createToken(accountId, newMailbox);
String token = tokenGenerateUtils.createMailboxToken(accountId, newMailbox);
// 准备激活链接,链接应该要有有效期
String link = "?" + token;
// 向新邮箱发送邮件,邮件附带激活链接,点击链接进行验证
@@ -2173,7 +2180,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
// 验证激活链接
public void activateNewEmail(String token){
// 获取链接地址信息,更新指定用户邮箱
String emailAndId = jwtTokenHelper.parseToEmailAndId(token);
String emailAndId = tokenGenerateUtils.parseMailboxToken(token);
String newMailbox = emailAndId.substring(0, emailAndId.lastIndexOf("_"));
String accountId = emailAndId.substring(emailAndId.lastIndexOf("_") + 1);

View File

@@ -7,7 +7,7 @@ import com.ai.da.common.enums.CollectionLevel1TypeEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.response.PageResponse;
import com.ai.da.common.response.Response;
import com.ai.da.common.security.jwt.JWTTokenHelper;
import com.ai.da.common.utils.TokenGenerateUtils;
import com.ai.da.common.utils.MinioUtil;
import com.ai.da.mapper.primary.*;
import com.ai.da.mapper.primary.entity.*;
@@ -70,7 +70,7 @@ public class LLMServiceImpl implements LLMService {
@Resource
private WorkspaceRelStyleMapper workspaceRelStyleMapper;
@Resource
private JWTTokenHelper jwtTokenHelper;
private TokenGenerateUtils tokenGenerateUtils;
@Resource
private DesignService designService;
private final ExecutorService executor = Executors.newCachedThreadPool();
@@ -89,9 +89,9 @@ public class LLMServiceImpl implements LLMService {
executor.submit(() -> {
try {
boolean validate = jwtTokenHelper.validateToken(token); //
boolean validate = tokenGenerateUtils.validateToken(token); //
if (validate) {
AuthPrincipalVo principal = jwtTokenHelper.parserToUser(token);
AuthPrincipalVo principal = tokenGenerateUtils.parserToUser(token);
Long accountId = principal.getId();
// String url = "http://18.167.251.121:10002/chat-stream";
String url = "http://10.1.1.240:1013/chat-stream";
@@ -237,10 +237,10 @@ public class LLMServiceImpl implements LLMService {
executor.submit(() -> {
try {
boolean validate = jwtTokenHelper.validateToken(token);
boolean validate = tokenGenerateUtils.validateToken(token);
// boolean validate = true;
if (validate) {
AuthPrincipalVo principal = jwtTokenHelper.parserToUser(token);
AuthPrincipalVo principal = tokenGenerateUtils.parserToUser(token);
Long accountId = principal.getId();
// String url = "http://18.167.251.121:10002/api/chat_stream";
String url = "http://18.167.251.121:2011/api/chat_stream";