如何在OpenAPI Generator生成的服务器中验证JWT
问题描述
我编写了包含JWT Bearer认证的OpenAPI YAML规范,使用openapi-generator-cli生成Java Spring服务器代码后,发现代码中没有JWT有效性校验逻辑,也无可供实现校验的接口。目前疑惑是否所有请求都会被无条件接受,想了解如何实现JWT有效性验证,在JWT无效时返回错误给客户端。
我的OpenAPI规范如下:
openapi: 3.0.0 info: version: 1.0.0 title: JWT-test description: An API for testing JWT authentication. components: securitySchemes: bearerAuth: description: Authentication with a JWT. type: http scheme: bearer bearerFormat: JWT responses: Unauthorized: description: The JWT is either missing or is invalid security: - bearerAuth: [] servers: - url: "http://localhost/test" paths: /hello: get: responses: '200': description: Successful response
生成命令:
java -jar ~/openapi-generator-cli.jar generate -g spring -i spec.yaml -o output
解决方案
1. 明确生成代码的现状
openapi-generator生成的Spring代码仅声明了安全约束(比如控制器方法上的@SecurityRequirement(name = "bearerAuth")注解),但不会自动实现JWT校验逻辑——它只会检查请求头是否存在Authorization: Bearer <token>格式的内容,不会验证token的签名、过期时间等有效性。所以当前确实所有带Bearer头的请求都会被放行,无效token也能通过。
2. 引入依赖
在项目的pom.xml(Maven)或build.gradle(Gradle)中添加Spring Security和JWT相关依赖:
Maven
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
Gradle
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
3. 配置Spring Security
创建Spring Security配置类,替换生成代码中默认的空安全配置:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final UserDetailsService userDetailsService; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, UserDetailsService userDetailsService) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.userDetailsService = userDetailsService; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .exceptionHandling(ex -> ex .authenticationEntryPoint((request, response, authException) -> { response.sendError(401, "Invalid or missing JWT token"); }) ); http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } }
4. 编写JWT校验过滤器
创建JwtAuthenticationFilter,负责解析请求头中的Bearer token并验证其有效性:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.crypto.SecretKey; import java.io.IOException; import java.util.Date; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { // 替换为实际业务中的密钥,建议从配置文件读取 private final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); private final UserDetailsService userDetailsService; public JwtAuthenticationFilter(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } String token = authHeader.substring(7); try { Claims claims = Jwts.parserBuilder() .setSigningKey(SECRET_KEY) .build() .parseClaimsJws(token) .getBody(); String username = claims.getSubject(); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (new Date().before(claims.getExpiration())) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } } catch (Exception e) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT token"); return; } filterChain.doFilter(request, response); } }
5. 实现UserDetailsService(按需调整)
如果JWT中包含用户名信息,需要实现UserDetailsService来加载用户数据(示例从模拟数据源获取,实际可对接数据库):
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; @Service public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 此处替换为实际用户数据查询逻辑 return new User(username, "", new ArrayList<>()); } }
6. 测试验证
启动服务后测试:
- 不带
Authorization头:返回401错误 - 携带无效token(过期、签名错误等):返回401错误
- 携带有效token:正常访问
/hello接口
内容的提问来源于stack exchange,提问作者Kivvil
相关产品推荐
相关产品推荐

