活动公告

系统通知
07-14 23:24
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

如何在Swagger中轻松实现JWT身份验证保护API接口安全从配置到实践的全流程指南让您的API文档更加安全可靠

SunJu_FaceMall

3万

主题

3063

科技点

3万

积分

执行版主

碾压王

积分
32876

塔罗立华奏

执行版主 发表于 2025-9-30 22:50:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

在现代Web开发中,API(应用程序编程接口)已经成为不同系统之间通信的核心组件。随着API的广泛应用,确保API的安全性变得尤为重要。Swagger(现为OpenAPI)作为一种流行的API文档和开发工具,提供了强大的功能来描述、生产和消费RESTful Web服务。而JWT(JSON Web Token)则是一种开放标准的令牌,用于在网络应用环境间传递声明的安全方式。

本文将详细介绍如何在Swagger中集成JWT身份验证,为您的API接口提供安全保障。我们将从基础概念开始,逐步引导您完成整个配置过程,并通过实际案例展示如何实现这一安全机制。

基础概念解释

Swagger简介

Swagger是一套围绕OpenAPI规范构建的开源工具,可以帮助您设计、构建、记录和使用RESTful Web服务。它主要包括以下几个部分:

• Swagger Editor:一个基于浏览器的编辑器,可以在其中编写OpenAPI规范。
• Swagger UI:一个将OpenAPI规范呈现为交互式API文档的工具。
• Swagger Codegen:从OpenAPI规范生成服务器存根和客户端库的工具。

JWT简介

JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。JWT通常用于身份验证和信息交换。

一个JWT实际上由三部分组成,用点(.)分隔:

1. 头部(Header)
2. 载荷(Payload)
3. 签名(Signature)

例如:xxxxx.yyyyy.zzzzz

为什么需要在Swagger中集成JWT身份验证

在Swagger中集成JWT身份验证有以下几个主要原因:

1. 安全性:通过JWT身份验证,可以确保只有经过授权的用户才能访问API。
2. 文档完整性:在API文档中明确标识需要身份验证的接口,使开发者更容易理解API的使用方式。
3. 测试便利性:Swagger UI提供了直接在文档中测试API的功能,集成JWT后,开发者可以在文档中直接进行身份验证并测试受保护的接口。
4. 标准化:遵循行业标准的安全实践,提高API的专业性和可信度。

环境准备

在开始之前,您需要准备以下环境和工具:

必需工具

1. Java Development Kit (JDK):版本8或更高
2. Maven:用于项目构建和依赖管理
3. IDE:如IntelliJ IDEA或Eclipse
4. Postman(可选):用于API测试

项目依赖

如果您使用Maven构建项目,需要在pom.xml中添加以下依赖:
  1. <!-- Spring Boot Starter Web -->
  2. <dependency>
  3.     <groupId>org.springframework.boot</groupId>
  4.     <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <!-- Spring Boot Starter Security -->
  7. <dependency>
  8.     <groupId>org.springframework.boot</groupId>
  9.     <artifactId>spring-boot-starter-security</artifactId>
  10. </dependency>
  11. <!-- Spring Boot Starter Validation -->
  12. <dependency>
  13.     <groupId>org.springframework.boot</groupId>
  14.     <artifactId>spring-boot-starter-validation</artifactId>
  15. </dependency>
  16. <!-- JWT Support -->
  17. <dependency>
  18.     <groupId>io.jsonwebtoken</groupId>
  19.     <artifactId>jjwt-api</artifactId>
  20.     <version>0.11.5</version>
  21. </dependency>
  22. <dependency>
  23.     <groupId>io.jsonwebtoken</groupId>
  24.     <artifactId>jjwt-impl</artifactId>
  25.     <version>0.11.5</version>
  26.     <scope>runtime</scope>
  27. </dependency>
  28. <dependency>
  29.     <groupId>io.jsonwebtoken</groupId>
  30.     <artifactId>jjwt-jackson</artifactId>
  31.     <version>0.11.5</version>
  32.     <scope>runtime</scope>
  33. </dependency>
  34. <!-- Swagger Dependencies -->
  35. <dependency>
  36.     <groupId>io.springfox</groupId>
  37.     <artifactId>springfox-swagger2</artifactId>
  38.     <version>3.0.0</version>
  39. </dependency>
  40. <dependency>
  41.     <groupId>io.springfox</groupId>
  42.     <artifactId>springfox-swagger-ui</artifactId>
  43.     <version>3.0.0</version>
  44. </dependency>
  45. <dependency>
  46.     <groupId>io.springfox</groupId>
  47.     <artifactId>springfox-boot-starter</artifactId>
  48.     <version>3.0.0</version>
  49. </dependency>
复制代码

如果您使用Spring Boot 3.x或更高版本,Swagger依赖可能有所不同:
  1. <!-- SpringDoc OpenAPI Dependencies -->
  2. <dependency>
  3.     <groupId>org.springdoc</groupId>
  4.     <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  5.     <version>2.1.0</version>
  6. </dependency>
复制代码

配置步骤

1. 创建JWT工具类

首先,我们需要创建一个JWT工具类,用于生成和验证JWT令牌。
  1. import io.jsonwebtoken.Claims;
  2. import io.jsonwebtoken.Jwts;
  3. import io.jsonwebtoken.SignatureAlgorithm;
  4. import io.jsonwebtoken.security.Keys;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.security.core.userdetails.UserDetails;
  7. import org.springframework.stereotype.Component;
  8. import java.security.Key;
  9. import java.util.Date;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. import java.util.function.Function;
  13. @Component
  14. public class JwtTokenUtil {
  15.     @Value("${jwt.secret}")
  16.     private String secret;
  17.     @Value("${jwt.expiration}")
  18.     private Long expiration;
  19.     // 生成用于签名的密钥
  20.     private Key getSigningKey() {
  21.         return Keys.hmacShaKeyFor(secret.getBytes());
  22.     }
  23.     // 从令牌中提取用户名
  24.     public String getUsernameFromToken(String token) {
  25.         return getClaimFromToken(token, Claims::getSubject);
  26.     }
  27.     // 从令牌中提取过期时间
  28.     public Date getExpirationDateFromToken(String token) {
  29.         return getClaimFromToken(token, Claims::getExpiration);
  30.     }
  31.     // 从令牌中提取特定声明
  32.     public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
  33.         final Claims claims = getAllClaimsFromToken(token);
  34.         return claimsResolver.apply(claims);
  35.     }
  36.     // 解析令牌获取所有声明
  37.     private Claims getAllClaimsFromToken(String token) {
  38.         return Jwts.parserBuilder()
  39.                 .setSigningKey(getSigningKey())
  40.                 .build()
  41.                 .parseClaimsJws(token)
  42.                 .getBody();
  43.     }
  44.     // 检查令牌是否过期
  45.     private Boolean isTokenExpired(String token) {
  46.         final Date expiration = getExpirationDateFromToken(token);
  47.         return expiration.before(new Date());
  48.     }
  49.     // 生成令牌
  50.     public String generateToken(UserDetails userDetails) {
  51.         Map<String, Object> claims = new HashMap<>();
  52.         return doGenerateToken(claims, userDetails.getUsername());
  53.     }
  54.     private String doGenerateToken(Map<String, Object> claims, String subject) {
  55.         return Jwts.builder()
  56.                 .setClaims(claims)
  57.                 .setSubject(subject)
  58.                 .setIssuedAt(new Date(System.currentTimeMillis()))
  59.                 .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
  60.                 .signWith(getSigningKey(), SignatureAlgorithm.HS256)
  61.                 .compact();
  62.     }
  63.     // 验证令牌
  64.     public Boolean validateToken(String token, UserDetails userDetails) {
  65.         final String username = getUsernameFromToken(token);
  66.         return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
  67.     }
  68. }
复制代码

2. 创建JWT认证过滤器

接下来,创建一个JWT认证过滤器,用于拦截请求并验证JWT令牌。
  1. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  2. import org.springframework.security.core.context.SecurityContextHolder;
  3. import org.springframework.security.core.userdetails.UserDetails;
  4. import org.springframework.security.core.userdetails.UserDetailsService;
  5. import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
  6. import org.springframework.stereotype.Component;
  7. import org.springframework.web.filter.OncePerRequestFilter;
  8. import javax.servlet.FilterChain;
  9. import javax.servlet.ServletException;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. @Component
  14. public class JwtAuthenticationFilter extends OncePerRequestFilter {
  15.     private final UserDetailsService userDetailsService;
  16.     private final JwtTokenUtil jwtTokenUtil;
  17.     public JwtAuthenticationFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) {
  18.         this.userDetailsService = userDetailsService;
  19.         this.jwtTokenUtil = jwtTokenUtil;
  20.     }
  21.     @Override
  22.     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
  23.             throws ServletException, IOException {
  24.         
  25.         final String requestTokenHeader = request.getHeader("Authorization");
  26.         String username = null;
  27.         String jwtToken = null;
  28.         // 检查请求头中是否有JWT令牌
  29.         if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
  30.             jwtToken = requestTokenHeader.substring(7);
  31.             try {
  32.                 username = jwtTokenUtil.getUsernameFromToken(jwtToken);
  33.             } catch (Exception e) {
  34.                 logger.error("Unable to get JWT Token", e);
  35.             }
  36.         }
  37.         // 验证令牌
  38.         if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
  39.             UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
  40.             if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
  41.                 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
  42.                         userDetails, null, userDetails.getAuthorities());
  43.                 authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  44.                 SecurityContextHolder.getContext().setAuthentication(authentication);
  45.             }
  46.         }
  47.         
  48.         chain.doFilter(request, response);
  49.     }
  50. }
复制代码

3. 配置Spring Security

现在,我们需要配置Spring Security以使用JWT进行身份验证。
  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.security.authentication.AuthenticationManager;
  4. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  5. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  6. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  7. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  8. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  9. import org.springframework.security.config.http.SessionCreationPolicy;
  10. import org.springframework.security.core.userdetails.UserDetailsService;
  11. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  12. import org.springframework.security.crypto.password.PasswordEncoder;
  13. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  14. @Configuration
  15. @EnableWebSecurity
  16. @EnableGlobalMethodSecurity(prePostEnabled = true)
  17. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  18.     private final UserDetailsService userDetailsService;
  19.     private final JwtAuthenticationFilter jwtAuthenticationFilter;
  20.     public SecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationFilter jwtAuthenticationFilter) {
  21.         this.userDetailsService = userDetailsService;
  22.         this.jwtAuthenticationFilter = jwtAuthenticationFilter;
  23.     }
  24.     @Override
  25.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  26.         auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
  27.     }
  28.     @Bean
  29.     public PasswordEncoder passwordEncoder() {
  30.         return new BCryptPasswordEncoder();
  31.     }
  32.     @Bean
  33.     @Override
  34.     public AuthenticationManager authenticationManagerBean() throws Exception {
  35.         return super.authenticationManagerBean();
  36.     }
  37.     @Override
  38.     protected void configure(HttpSecurity http) throws Exception {
  39.         http.csrf().disable()
  40.             .authorizeRequests()
  41.             .antMatchers("/api/authenticate", "/api/register", "/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**").permitAll()
  42.             .anyRequest().authenticated()
  43.             .and()
  44.             .exceptionHandling()
  45.             .and()
  46.             .sessionManagement()
  47.             .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
  48.         
  49.         http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
  50.     }
  51. }
复制代码

4. 创建认证控制器

创建一个控制器,用于处理用户认证和生成JWT令牌。
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.http.ResponseEntity;
  3. import org.springframework.security.authentication.AuthenticationManager;
  4. import org.springframework.security.authentication.BadCredentialsException;
  5. import org.springframework.security.authentication.DisabledException;
  6. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  7. import org.springframework.security.core.userdetails.UserDetails;
  8. import org.springframework.web.bind.annotation.PostMapping;
  9. import org.springframework.web.bind.annotation.RequestBody;
  10. import org.springframework.web.bind.annotation.RequestMapping;
  11. import org.springframework.web.bind.annotation.RestController;
  12. @RestController
  13. @RequestMapping("/api")
  14. public class AuthenticationController {
  15.     private final AuthenticationManager authenticationManager;
  16.     private final JwtTokenUtil jwtTokenUtil;
  17.     private final UserDetailsService userDetailsService;
  18.     @Autowired
  19.     public AuthenticationController(AuthenticationManager authenticationManager,
  20.                                    JwtTokenUtil jwtTokenUtil,
  21.                                    UserDetailsService userDetailsService) {
  22.         this.authenticationManager = authenticationManager;
  23.         this.jwtTokenUtil = jwtTokenUtil;
  24.         this.userDetailsService = userDetailsService;
  25.     }
  26.     @PostMapping("/authenticate")
  27.     public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
  28.         try {
  29.             authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
  30.                     authenticationRequest.getUsername(), authenticationRequest.getPassword()));
  31.         } catch (DisabledException e) {
  32.             throw new Exception("USER_DISABLED", e);
  33.         } catch (BadCredentialsException e) {
  34.             throw new Exception("INVALID_CREDENTIALS", e);
  35.         }
  36.         
  37.         final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
  38.         final String jwt = jwtTokenUtil.generateToken(userDetails);
  39.         
  40.         return ResponseEntity.ok(new AuthenticationResponse(jwt));
  41.     }
  42. }
  43. // 请求和响应DTO
  44. class AuthenticationRequest {
  45.     private String username;
  46.     private String password;
  47.    
  48.     // Getters and Setters
  49.     public String getUsername() {
  50.         return username;
  51.     }
  52.    
  53.     public void setUsername(String username) {
  54.         this.username = username;
  55.     }
  56.    
  57.     public String getPassword() {
  58.         return password;
  59.     }
  60.    
  61.     public void setPassword(String password) {
  62.         this.password = password;
  63.     }
  64. }
  65. class AuthenticationResponse {
  66.     private String jwt;
  67.    
  68.     public AuthenticationResponse(String jwt) {
  69.         this.jwt = jwt;
  70.     }
  71.    
  72.     // Getter
  73.     public String getJwt() {
  74.         return jwt;
  75.     }
  76. }
复制代码

5. 配置Swagger

现在,让我们配置Swagger以支持JWT身份验证。
  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import springfox.documentation.builders.ApiInfoBuilder;
  4. import springfox.documentation.builders.PathSelectors;
  5. import springfox.documentation.builders.RequestHandlerSelectors;
  6. import springfox.documentation.service.*;
  7. import springfox.documentation.spi.DocumentationType;
  8. import springfox.documentation.spi.service.contexts.SecurityContext;
  9. import springfox.documentation.spring.web.plugins.Docket;
  10. import java.util.Arrays;
  11. import java.util.Collections;
  12. import java.util.List;
  13. @Configuration
  14. public class SwaggerConfig {
  15.     @Bean
  16.     public Docket api() {
  17.         return new Docket(DocumentationType.SWAGGER_2)
  18.                 .select()
  19.                 .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
  20.                 .paths(PathSelectors.any())
  21.                 .build()
  22.                 .apiInfo(apiInfo())
  23.                 .securitySchemes(Arrays.asList(apiKey()))
  24.                 .securityContexts(Collections.singletonList(securityContext()));
  25.     }
  26.     private ApiInfo apiInfo() {
  27.         return new ApiInfoBuilder()
  28.                 .title("API with JWT Authentication")
  29.                 .description("Spring Boot REST API with JWT Authentication")
  30.                 .version("1.0")
  31.                 .contact(new Contact("Your Name", "www.example.com", "your.email@example.com"))
  32.                 .build();
  33.     }
  34.     private ApiKey apiKey() {
  35.         return new ApiKey("JWT", "Authorization", "header");
  36.     }
  37.     private SecurityContext securityContext() {
  38.         return SecurityContext.builder()
  39.                 .securityReferences(defaultAuth())
  40.                 .forPaths(PathSelectors.regex("/api/.*"))
  41.                 .build();
  42.     }
  43.     private List<SecurityReference> defaultAuth() {
  44.         AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
  45.         AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
  46.         authorizationScopes[0] = authorizationScope;
  47.         return Collections.singletonList(new SecurityReference("JWT", authorizationScopes));
  48.     }
  49. }
复制代码
  1. import io.swagger.v3.oas.models.Components;
  2. import io.swagger.v3.oas.models.OpenAPI;
  3. import io.swagger.v3.oas.models.info.Contact;
  4. import io.swagger.v3.oas.models.info.Info;
  5. import io.swagger.v3.oas.models.info.License;
  6. import io.swagger.v3.oas.models.security.SecurityRequirement;
  7. import io.swagger.v3.oas.models.security.SecurityScheme;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. @Configuration
  11. public class OpenApiConfig {
  12.     private static final String SECURITY_SCHEME_NAME = "Bearer Token";
  13.     @Bean
  14.     public OpenAPI customOpenAPI() {
  15.         return new OpenAPI()
  16.                 .info(new Info()
  17.                         .title("API with JWT Authentication")
  18.                         .version("1.0")
  19.                         .description("Spring Boot REST API with JWT Authentication")
  20.                         .contact(new Contact()
  21.                                 .name("Your Name")
  22.                                 .url("www.example.com")
  23.                                 .email("your.email@example.com"))
  24.                         .license(new License()
  25.                                 .name("Apache 2.0")
  26.                                 .url("http://springdoc.org")))
  27.                 .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
  28.                 .components(new Components()
  29.                         .addSecuritySchemes(SECURITY_SCHEME_NAME,
  30.                                 new SecurityScheme()
  31.                                         .name(SECURITY_SCHEME_NAME)
  32.                                         .type(SecurityScheme.Type.HTTP)
  33.                                         .scheme("bearer")
  34.                                         .bearerFormat("JWT")));
  35.     }
  36. }
复制代码

6. 创建示例API控制器

创建一个示例API控制器,用于演示JWT身份验证的保护效果。
  1. import org.springframework.http.ResponseEntity;
  2. import org.springframework.security.access.prepost.PreAuthorize;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. @RestController
  7. @RequestMapping("/api")
  8. public class DemoController {
  9.     @GetMapping("/public")
  10.     public ResponseEntity<String> publicEndpoint() {
  11.         return ResponseEntity.ok("This is a public endpoint that anyone can access.");
  12.     }
  13.     @GetMapping("/user")
  14.     @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
  15.     public ResponseEntity<String> userEndpoint() {
  16.         return ResponseEntity.ok("This is a user endpoint that requires USER or ADMIN role.");
  17.     }
  18.     @GetMapping("/admin")
  19.     @PreAuthorize("hasRole('ADMIN')")
  20.     public ResponseEntity<String> adminEndpoint() {
  21.         return ResponseEntity.ok("This is an admin endpoint that requires ADMIN role.");
  22.     }
  23. }
复制代码

7. 配置应用程序属性

在application.properties或application.yml文件中添加JWT相关配置:
  1. # JWT Configuration
  2. jwt.secret=mySecretKey
  3. jwt.expiration=86400
复制代码

或使用YAML格式:
  1. # JWT Configuration
  2. jwt:
  3.   secret: mySecretKey
  4.   expiration: 86400
复制代码

实践案例

现在,让我们通过一个完整的示例项目来演示如何在Swagger中实现JWT身份验证。

项目结构
  1. src/main/java/com/example/demo/
  2. ├── config/
  3. │   ├── SecurityConfig.java
  4. │   ├── SwaggerConfig.java
  5. │   └── OpenApiConfig.java (for Spring Boot 3.x)
  6. ├── controller/
  7. │   ├── AuthenticationController.java
  8. │   └── DemoController.java
  9. ├── filter/
  10. │   └── JwtAuthenticationFilter.java
  11. ├── model/
  12. │   ├── AuthenticationRequest.java
  13. │   └── AuthenticationResponse.java
  14. ├── security/
  15. │   └── JwtTokenUtil.java
  16. ├── service/
  17. │   └── UserDetailsServiceImpl.java
  18. └── DemoApplication.java
复制代码

用户详情服务实现
  1. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  2. import org.springframework.security.core.userdetails.User;
  3. import org.springframework.security.core.userdetails.UserDetails;
  4. import org.springframework.security.core.userdetails.UserDetailsService;
  5. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  6. import org.springframework.stereotype.Service;
  7. import java.util.Collections;
  8. @Service
  9. public class UserDetailsServiceImpl implements UserDetailsService {
  10.     @Override
  11.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  12.         // 在实际应用中,这里应该从数据库或其他存储中获取用户信息
  13.         // 这里我们使用硬编码的用户信息作为示例
  14.         if ("user".equals(username)) {
  15.             return new User("user", "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6",
  16.                     Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
  17.         } else if ("admin".equals(username)) {
  18.             return new User("admin", "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6",
  19.                     Collections.singletonList(new SimpleGrantedAuthority("ROLE_ADMIN")));
  20.         } else {
  21.             throw new UsernameNotFoundException("User not found with username: " + username);
  22.         }
  23.     }
  24. }
复制代码

主应用程序类
  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. @SpringBootApplication
  4. public class DemoApplication {
  5.     public static void main(String[] args) {
  6.         SpringApplication.run(DemoApplication.class, args);
  7.     }
  8. }
复制代码

运行和测试

1. 启动应用程序
2. 访问Swagger UI:http://localhost:8080/swagger-ui.html(对于Springfox) 或http://localhost:8080/swagger-ui.html(对于SpringDoc)
3. 在Swagger UI界面中,您会看到一个”Authorize”按钮,点击它
4. 在弹出的对话框中,输入JWT令牌(格式为Bearer <your_jwt_token>)
5. 现在,您可以测试受保护的API端点了

首先,使用/api/authenticate端点获取JWT令牌:
  1. {
  2.   "username": "user",
  3.   "password": "password"
  4. }
复制代码

响应将包含JWT令牌:
  1. {
  2.   "jwt": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNjE2MjM5MDI2LCJleHAiOjE2MTYzMjU0MjZ9.5Q5WZsKc8vZrI5iL_2iX5yV5x5g5h5j5k5l5m5n5o5p"
  3. }
复制代码

复制这个令牌,然后在Swagger UI的”Authorize”对话框中输入Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNjE2MjM5MDI2LCJleHAiOjE2MTYzMjU0MjZ9.5Q5WZsKc8vZrI5iL_2iX5yV5x5g5h5j5k5l5m5n5o5p。

现在,您可以测试受保护的端点,如/api/user和/api/admin。

常见问题及解决方案

1. JWT令牌过期问题

问题:JWT令牌有一定的有效期,过期后需要重新获取。

解决方案:

• 实现令牌刷新机制,允许用户使用过期的令牌获取新的令牌。
• 在客户端实现令牌过期检测和自动刷新逻辑。
  1. // 在JwtTokenUtil类中添加刷新令牌的方法
  2. public String refreshToken(String token) {
  3.     final Date createdDate = new Date();
  4.     final Date expirationDate = calculateExpirationDate(createdDate);
  5.    
  6.     final Claims claims = getAllClaimsFromToken(token);
  7.     claims.setIssuedAt(createdDate);
  8.     claims.setExpiration(expirationDate);
  9.    
  10.     return Jwts.builder()
  11.             .setClaims(claims)
  12.             .signWith(getSigningKey(), SignatureAlgorithm.HS256)
  13.             .compact();
  14. }
  15. private Date calculateExpirationDate(Date createdDate) {
  16.     return new Date(createdDate.getTime() + expiration * 1000);
  17. }
复制代码

2. Swagger UI无法显示

问题:访问Swagger UI时出现404错误。

解决方案:

• 确保已正确添加Swagger依赖。
• 检查Spring Security配置,确保允许访问Swagger相关路径。
  1. @Override
  2. protected void configure(HttpSecurity http) throws Exception {
  3.     http.csrf().disable()
  4.         .authorizeRequests()
  5.         .antMatchers("/api/authenticate", "/api/register",
  6.                      "/v2/api-docs", "/configuration/**",
  7.                      "/swagger*/**", "/webjars/**").permitAll()
  8.         .anyRequest().authenticated()
  9.         .and()
  10.         // ... 其他配置
  11. }
复制代码

3. JWT令牌验证失败

问题:即使提供了有效的JWT令牌,API请求仍然返回401未授权错误。

解决方案:

• 检查JWT令牌是否正确添加到请求头中,格式应为Bearer <token>。
• 确保JWT令牌未过期。
• 检查服务器端的JWT验证逻辑,确保密钥和算法匹配。
  1. // 在JwtAuthenticationFilter中添加调试日志
  2. @Override
  3. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
  4.         throws ServletException, IOException {
  5.    
  6.     final String requestTokenHeader = request.getHeader("Authorization");
  7.    
  8.     logger.debug("Authorization Header: " + requestTokenHeader);
  9.    
  10.     // ... 其余代码
  11. }
复制代码

4. 角色权限不生效

问题:即使用户具有特定角色,访问需要该角色的端点时仍然被拒绝。

解决方案:

• 确保在用户详情服务中正确设置了用户角色。
• 检查角色名称是否以ROLE_前缀开头,这是Spring Security的默认要求。
• 确保在控制器上正确使用了@PreAuthorize注解。
  1. // 在UserDetailsServiceImpl中确保角色名称正确
  2. return new User("admin", "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6",
  3.         Collections.singletonList(new SimpleGrantedAuthority("ROLE_ADMIN")));
复制代码

最佳实践和安全性建议

1. 使用HTTPS

始终使用HTTPS来保护API通信,防止JWT令牌被中间人攻击截获。

2. 设置合理的令牌过期时间

根据应用的安全需求,设置合理的JWT令牌过期时间。对于高安全性要求的应用,可以使用较短的过期时间(如30分钟),并实现令牌刷新机制。

3. 使用强密钥

使用足够长且随机的密钥来签名JWT令牌。避免使用简单或可预测的密钥。
  1. # 使用至少256位(32字节)的随机密钥
  2. jwt.secret=5A7B3C9D2E4F6A8B1C3D5E7F9A2B4C6D8E1F3A5B7C9D2E4F6A8B1C3D5E7F9A
复制代码

4. 实现令牌撤销机制

虽然JWT是无状态的,但在某些情况下(如用户注销或令牌泄露),您可能需要实现令牌撤销机制。这可以通过维护令牌黑名单来实现。
  1. @Component
  2. public class JwtTokenBlacklist {
  3.     private final Set<String> blacklist = ConcurrentHashMap.newKeySet();
  4.    
  5.     public void addToBlacklist(String token) {
  6.         blacklist.add(token);
  7.     }
  8.    
  9.     public boolean isBlacklisted(String token) {
  10.         return blacklist.contains(token);
  11.     }
  12. }
复制代码

5. 限制登录尝试

实现登录尝试限制,防止暴力破解攻击。
  1. import org.springframework.context.event.EventListener;
  2. import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
  3. import org.springframework.stereotype.Component;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. import java.util.concurrent.atomic.AtomicInteger;
  7. @Component
  8. public class AuthenticationFailureListener {
  9.    
  10.     private final Map<String, AtomicInteger> failedAttempts = new HashMap<>();
  11.     private final int MAX_ATTEMPTS = 5;
  12.    
  13.     @EventListener
  14.     public void authenticationFailed(AuthenticationFailureBadCredentialsEvent event) {
  15.         String username = event.getAuthentication().getName();
  16.         failedAttempts.computeIfAbsent(username, k -> new AtomicInteger(0)).incrementAndGet();
  17.         
  18.         if (failedAttempts.get(username).get() >= MAX_ATTEMPTS) {
  19.             // 锁定账户或采取其他安全措施
  20.             // 例如,可以设置一个标志,在UserDetailsService中检查该标志
  21.         }
  22.     }
  23. }
复制代码

6. 定期更新依赖

定期更新Spring Security、JWT库和其他相关依赖,以获取最新的安全修复和功能改进。

7. 敏感信息不要存储在JWT中

避免在JWT载荷中存储敏感信息,如密码、信用卡号等。JWT只是Base64编码的,不是加密的,可以被任何人解码。

8. 使用适当的算法

使用强签名算法(如HS256或RS256)来签名JWT令牌。避免使用无签名算法(如”none”)。

总结

在本文中,我们详细介绍了如何在Swagger中实现JWT身份验证,以保护API接口的安全。我们从基础概念开始,逐步引导您完成了整个配置过程,并通过实际案例展示了如何实现这一安全机制。

主要内容包括:

1. 介绍了Swagger和JWT的基本概念及其在API安全中的重要性。
2. 准备了必要的环境和依赖。
3. 详细说明了如何创建JWT工具类、认证过滤器和Spring Security配置。
4. 展示了如何在Swagger中配置JWT身份验证。
5. 提供了一个完整的示例项目,包括用户认证、API保护和Swagger文档集成。
6. 讨论了常见问题及其解决方案。
7. 分享了最佳实践和安全性建议。

通过遵循本文中的步骤和建议,您可以轻松地在Swagger中实现JWT身份验证,为您的API接口提供强大的安全保障。这不仅有助于保护您的API免受未授权访问,还能提供更好的开发者体验,使API文档更加完整和易于使用。

希望本文对您有所帮助,祝您在API开发中取得成功!
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则