AI 大模型统一集成|微服务 + 认证中心:基于 JWT 实现大模型 API 的安全调用!

发布于:2025-03-26 ⋅ 阅读:(29) ⋅ 点赞:(0)

🌟 在这系列文章中,我们将一起探索如何搭建一个支持大模型集成项目 NexLM 的开发过程,从 架构设计代码实战,逐步搭建一个支持 多种大模型(GPT-4、DeepSeek 等)一站式大模型集成与管理平台,并集成 认证中心、微服务、流式对话 等核心功能。

系列目录规划:

  1. NexLM:从零开始打造你的专属大模型集成平台
  2. Spring Boot + OpenAI/DeepSeek:如何封装多个大模型 API 调用
  3. 支持流式对话 SSE & WebSocket:让 AI 互动更丝滑
  4. 微服务 + 认证中心:如何保障大模型 API 的安全调用
  5. 缓存与性能优化:提高 LLM API 响应速度
  6. 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) 的工具类,适用于 微服务架构 中的身份认证。它的主要功能包括:

  1. 生成 JWT 令牌(generateToken 方法)

    • 通过 sessionUser 对象提取用户信息,并存入 JWT 的 Claims 负载中。
    • 设置签发时间(ISSUED_AT)和过期时间(EXPIRATION)。
    • 使用 HS256 算法进行签名,确保 Token 安全性。
    • 注意:方法使用了 synchronized,可能存在性能瓶颈,建议优化。
  2. 解析 JWT 并获取用户信息(validateUserToken 方法)

    • 解析 Token,获取存储的用户信息。
    • 通过反射动态填充 SessionUser 对象。
    • 忽略静态字段,防止不必要的解析错误。
    • 如果解析失败或字段不存在,则记录日志。
  3. 辅助方法

    • 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 项目地址 🌟 你的支持是我持续创作的动力,欢迎点赞、收藏、分享!
📢 详细部署教程视频已更新

在这里插入图片描述