🌟 在这系列文章中,我们将一起探索如何搭建一个支持大模型集成项目 NexLM 的开发过程,从 架构设计 到 代码实战,逐步搭建一个支持 多种大模型(GPT-4、DeepSeek 等) 的 一站式大模型集成与管理平台,并集成 认证中心、微服务、流式对话 等核心功能。
系列目录规划:
- NexLM:从零开始打造你的专属大模型集成平台 ✅
- Spring Boot + OpenAI/DeepSeek:如何封装多个大模型 API 调用 ✅
- 支持流式对话 SSE & WebSocket:让 AI 互动更丝滑 ✅
- 微服务 + 认证中心:如何保障大模型 API 的安全调用 ⏳
- 缓存与性能优化:提高 LLM API 响应速度
- NexLM 开源部署指南(Docker)
第六篇:微服务 + 认证中心:基于 JWT 实现大模型 API 的安全调用!
在上一章中,我们通过 Spring Security + Session 认证机制,为微服务架构中的登录调用提供了可靠的身份认证,确保只有授权用户能够访问大模型 API。然而,随着 微服务架构的扩展性要求,传统的 Session 认证 方式可能面临 性能瓶颈、分布式环境难题 等问题。
今天,我们将继续深入探讨 微服务 + 认证中心 的架构方案,通过 JWT 认证 实现无状态认证,确保 大模型 API 只能被认证登录且已授权的用户访问,从而提升系统的可扩展性、灵活性和安全性。
JWT(JSON Web Token)作为一种轻量级的认证方式,广泛应用于微服务架构中,能够在 无状态 的前提下,保证每次 API 调用都能有效验证用户身份。
📌 Spring Security + JWT 认证
1. 环境准备
在开始之前,确保项目中已经引入了以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2. Spring Security 配置
以下是基于 Spring Security 的核心配置类 SecurityConfig
,实现了 JWT 认证和权限控制。
2.1 配置类代码
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级权限控制(如 @PreAuthorize)
public class SecurityConfig {
@Autowired
private CustomAuthSuccessHandler customAuthSuccessHandler;
@Autowired
private CustomAuthFailureHandler customAuthFailureHandler;
@Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
@Autowired
private CustomLogoutHandler customLogoutHandler;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsService userDetailsService;
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CustomAuthenticationProvider customAuthenticationProvider(PasswordEncoder passwordEncoder) {
return new CustomAuthenticationProvider(userDetailsService, passwordEncoder);
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
CustomAuthenticationProvider customAuthenticationProvider = customAuthenticationProvider(passwordEncoder());
return new ProviderManager(customAuthenticationProvider);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 禁用httpBasic
.httpBasic(AbstractHttpConfigurer::disable)
.csrf().disable()
// 禁用会话管理, 通过每次请求携带认证信息(JWT)信息进行认证
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests() // security 5.7 版本可简写这里
.antMatchers("/", "/auth", "/auth/login", "/auth/logout", "/auth/confirm").permitAll() // 允许所有用户访问
.anyRequest().authenticated() // 其他请求需要认证
.and()
.formLogin(formLogin -> formLogin
.usernameParameter(AuthConstant.USERNAME) // 设置登录表单中的用户名字段的参数名称
.passwordParameter(AuthConstant.PASSWORD) // 设置登录表单中的密码字段的参数名称
.loginProcessingUrl(AuthConstant.LOGIN_URL) // 设置登录请求的 URL 地址(表单提交的 URL)
.permitAll() // 允许所有人访问登录页面,不需要认证
.successHandler(customAuthSuccessHandler) // 设置登录成功后的处理逻辑,可以定制成功后的跳转等
.failureHandler(customAuthFailureHandler) // 设置登录失败后的处理逻辑,可以定制失败后的提示等
)
// 添加注销配置
.logout(logout -> logout
.logoutUrl(AuthConstant.LOGOUT_URL) // 设置退出URL
.logoutSuccessHandler(customLogoutSuccessHandler) // 自定义退出成功逻辑
.addLogoutHandler(customLogoutHandler) // 处理自定义清理逻辑(如清除 Redis Token)
.invalidateHttpSession(true) // 使 Session 失效
.clearAuthentication(true) // 清空认证信息
.permitAll()
)
// 禁用缓存
.requestCache(cache -> cache.requestCache(new NullRequestCache()))
.authenticationProvider(customAuthenticationProvider(passwordEncoder())) // 添加自定义的 AuthenticationProvider
// 这行代码的作用是在 UsernamePasswordAuthenticationFilter 之前执行 JwtAuthenticationFilter,让 Spring Security 先解析 JWT 并设置用户信息,然后再继续处理请求。
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(exceptions -> exceptions
// 方式1:直接未登录跳转到登录页
.authenticationEntryPoint((request, response, authException) -> {
// 从 Session 读取错误信息
String errorMessage = (String) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
if (errorMessage == null) {
errorMessage = "未登录,请重新登录";
}
// 获取当前应用的 contextPath (动态前缀如 "/web")
String contextPath = request.getContextPath();
// 失败后重定向到登录页,并附带错误信息
response.sendRedirect(contextPath + "/auth/login?error=" + URLEncoder.encode(errorMessage, String.valueOf(StandardCharsets.UTF_8)));
})
)
;
return http.build();
}
}
有两个点需要注意:
(1)配置禁用会话管理, 通过每次请求携带认证信息(JWT)信息进行认证
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
(2)配置JWT校验拦截器 JwtAuthenticationFilter,让 Spring Security 先解析 JWT 并设置用户信息,然后再继续处理请求。
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
3. 核心组件说明
3.1 JWT 工具类
JWT 工具类(JwtTokenProvider)用于生成和解析 Token,以下是核心代码:
@Component
public class JwtTokenProvider {
private final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
/**
* 密钥
*/
private String secretKey = "secretKey@123456";
/**
* token失效时间,默认60分钟
*/
private long expirationTime = 60L;
public String generateToken(SessionUser sessionUser) {
synchronized (JwtTokenProvider.class) { // 并发问题
try {
Map<String, Object> userAttrs = getUserAttrs(sessionUser);
// 有效时间
userAttrs.put(Claims.ISSUED_AT, new Date());
userAttrs.put(Claims.EXPIRATION, new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(expirationTime)));
return getBuilder()
.addClaims(userAttrs)
.compact();
} catch (IllegalAccessException e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
}
public SessionUser validateUserToken(String token) {
Map<String, Object> data = getJwtParser().parseClaimsJws(token).getBody();
SessionUser sessionUser = new SessionUser();
data.forEach((key, value) -> {
try {
Field field = sessionUser.getClass().getDeclaredField(key);
if (Modifier.isStatic(field.getModifiers())) { // 静态变量不处理
return;
}
field.setAccessible(true);
field.set(sessionUser, value);
} catch (NoSuchFieldException e) {
logger.warn("Field not found: {}", key);
} catch (IllegalAccessException e) {
logger.error(e.getMessage(), e);
}
});
return sessionUser;
}
private Map<String, Object> getUserAttrs(SessionUser sessionUser) throws IllegalAccessException {
Map<String, Object> userAttrs = new HashMap<>();
for (Field field : sessionUser.getClass().getDeclaredFields()) {
field.setAccessible(true);
userAttrs.put(field.getName(), field.get(sessionUser));
}
return userAttrs;
}
private JwtBuilder getBuilder() {
return Jwts.builder()
.signWith(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8),
SignatureAlgorithm.HS256.getJcaName()));
}
private JwtParser getJwtParser() {
return Jwts.parserBuilder()
.setSigningKey((secretKey.getBytes()))
.setAllowedClockSkewSeconds(1)
.build();
}
}
JwtTokenProvider 工具类简介
JwtTokenProvider
是一个用于 生成和验证 JWT(JSON Web Token) 的工具类,适用于 微服务架构 中的身份认证。它的主要功能包括:
生成 JWT 令牌(
generateToken
方法)- 通过
sessionUser
对象提取用户信息,并存入 JWT 的 Claims 负载中。 - 设置签发时间(
ISSUED_AT
)和过期时间(EXPIRATION
)。 - 使用 HS256 算法进行签名,确保 Token 安全性。
- 注意:方法使用了
synchronized
,可能存在性能瓶颈,建议优化。
- 通过
解析 JWT 并获取用户信息(
validateUserToken
方法)- 解析 Token,获取存储的用户信息。
- 通过反射动态填充
SessionUser
对象。 - 忽略静态字段,防止不必要的解析错误。
- 如果解析失败或字段不存在,则记录日志。
辅助方法
getUserAttrs(SessionUser sessionUser)
: 通过反射提取SessionUser
的所有字段,并存入Map
。getBuilder()
: 创建 JWT 构建器,用于生成 Token。getJwtParser()
: 创建 JWT 解析器,用于解析 Token 并验证签名。
3.2 JWT 过滤器
JWT 过滤器用于拦截每次请求并验证 Token:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
@Resource
private JwtTokenProvider jwtTokenProvider;
public JwtAuthenticationFilter(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (StringUtils.isNotEmpty(token)) {
SessionUser sessionUser = jwtTokenProvider.validateUserToken(token);
if (sessionUser != null) {
String username = sessionUser.getUsername();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
// 请求头中获取
String bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION);
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
// 直接cookie中获取
return CookieUtils.getCookie(request, AuthConstant.COOKIE_TOKEN);
}
}
📌 AI 大模型接口服务 JWT 认证配置
在 AI 大模型建立 Websocket 连接时,通过校验请求头或者 Cookie 中是否有 Token 并且进行验证是否合法。
@Component
public class WebSocketChatHandler extends TextWebSocketHandler {
final Logger logger = LoggerFactory.getLogger(this.getClass());
private final JwtTokenProvider jwtTokenProvider;
private final DeepSeekClient deepSeekClient;
public WebSocketChatHandler(DeepSeekClient deepSeekClient, JwtTokenProvider jwtTokenProvider) {
this.deepSeekClient = deepSeekClient;
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
HttpHeaders headers = session.getHandshakeHeaders();
// 1、优先从请求头获取 Token
String token = extractTokenFromHeaders(headers);
if (token == null) {
// 2、如果请求头没有 Token,则从 Cookie 获取
token = extractTokenFromCookies(headers.get("cookie"));
}
// 3、校验 Token
if (!validToken(token)) {
logger.warn("WebSocket 连接失败,Token 无效:{}", session.getId());
session.close(CloseStatus.NOT_ACCEPTABLE);
return;
}
logger.info("WebSocket 连接成功,用户身份验证通过:{}", session.getId());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String userMessage = message.getPayload();
logger.info("用户发送消息:{}", userMessage);
try {
// 调用 AI 大模型接口,并流式返回消息
deepSeekClient.streamChatWs(userMessage, session);
} catch (Exception e) {
session.sendMessage(new TextMessage("AI 服务异常,请稍后重试。"));
logger.error(e.getMessage(), e);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
logger.info("WebSocket 连接关闭:{}", session.getId());
}
/**
* 从请求头中提取 Token
*/
private String extractTokenFromHeaders(HttpHeaders headers) {
List<String> authHeaders = headers.get("Authorization");
if (authHeaders != null && !authHeaders.isEmpty()) {
String authHeader = authHeaders.get(0);
if (authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
}
return null;
}
/**
* 从 Cookie 中提取 Token
*/
private String extractTokenFromCookies(List<String> cookieHeaders) {
if (cookieHeaders == null || cookieHeaders.isEmpty()) {
return null;
}
for (String cookieHeader : cookieHeaders) {
String[] cookies = cookieHeader.split("; ");
for (String cookie : cookies) {
if (cookie.startsWith("AUTH-TOKEN=")) {
return cookie.substring("AUTH-TOKEN=".length());
}
}
}
return null;
}
private boolean validToken(String token) {
if (StringUtils.isEmpty(token)) {
return false;
}
SessionUser sessionUser = jwtTokenProvider.validateUserToken(token);
return sessionUser != null;
}
}
配置完成,让我们直接访问AI大模型聊天页面看看效果。发现未登录则不可访问,并且API接口也需要进行身份票据验证。
可以看到登录成功之后浏览器 Cookie 中生成一个名称 AUTH-TOKEN 的 JWT。
如果将这个 JWT 放到其他地方带上(例如 Postman 中直接测试接口),这样的请求也是可以进行身份认证成功。如果客户端不支持 Cookie 那么可以直接将 JWT 设置到请求头中(有做兼容)也是可以进行认证成功。
🎯 总结
本文介绍了如何基于 Spring Security 和 JWT 实现大模型 API 的安全认证。通过 JWT 的无状态特性,我们可以轻松实现分布式系统的安全调用。如果您有任何问题或建议,欢迎在评论区留言!
✅ 采用 Spring Security + JWT 保障微服务 API 访问安全
✅ 认证中心负责用户身份验证 + Token 颁发
✅ JwtAuthenticationFilter 解析 Token,完成 API 请求的身份认证
✅ 这种设计保证了 API 无状态、易扩展、安全性强,适用于大规模微服务架构。
📢 你对这个项目感兴趣吗?欢迎 Star & 关注! 📌 GitHub 项目地址 🌟 你的支持是我持续创作的动力,欢迎点赞、收藏、分享!
📢 详细部署教程视频已更新