Spring Security集成Swagger实现接口权限控制

 更新时间:2026年06月25日 09:04:53   作者:知远漫谈  
在现代微服务架构和前后端分离开发模式下,API 接口的安全性与可维护性显得尤为重要,本文将深入探讨如何在 Spring Boot 项目中集成 Spring Security 和 Swagger,并基于用户角色动态控制接口的可见性与访问权限,需要的朋友可以参考下

引言

在现代微服务架构和前后端分离开发模式下,API 接口的安全性与可维护性显得尤为重要。Spring Boot 提供了强大的生态支持,其中 Spring Security 是保障应用安全的核心框架,而 Swagger(OpenAPI) 则是 API 文档自动生成与可视化的利器。将两者结合,不仅可以实现细粒度的接口权限控制,还能为不同角色提供差异化的文档访问能力。

本文将深入探讨如何在 Spring Boot 项目中集成 Spring Security 和 Swagger,并基于用户角色动态控制接口的可见性与访问权限。我们将通过完整的 Java 代码示例、权限模型设计以及 Mermaid 图表来帮助你构建一个安全、可控且易于维护的 RESTful API 系统。

为什么需要接口权限控制?

随着系统复杂度的提升,不同的用户角色对 API 的访问需求各不相同:

  • 普通用户只能查看自己的订单信息;
  • 管理员可以管理所有用户数据;
  • 审计人员只能读取日志但不能修改;
  • 第三方客户端仅能调用特定开放接口。

如果不加控制地暴露所有接口文档,可能会带来以下风险:

  1. 信息泄露:敏感接口路径被非授权人员知晓;
  2. 误操作风险:前端开发者或测试人员误调高危接口;
  3. 安全审计困难:缺乏清晰的权限边界定义;
  4. 用户体验差:无关接口干扰正常使用。

因此,在使用 Swagger 展示 API 文档时,应根据登录用户的身份动态过滤其可见的接口列表,做到“谁能看到什么”完全由权限决定。

技术选型说明

本项目采用如下技术栈:

组件版本建议作用
Spring Boot3.x+快速构建 Web 应用
Spring Security6.x+身份认证与授权管理
Spring Data JPA数据持久化
Springdoc OpenAPI (Swagger)2.x+替代 springfox 的新一代 Swagger 集成工具
JWT无状态 Token 认证机制

注意:从 Springfox 迁移到 springdoc-openapi 是当前主流趋势,因其兼容性更好,支持 Spring Boot 3 和 Jakarta EE。

我们选择 springdoc-openapi 作为 Swagger 的实现方案,它无需额外配置复杂的 Docket Bean,开箱即用,同时支持 SecurityScheme 注解自动识别。

项目结构初始化

首先创建一个标准的 Spring Boot 工程,包含基本模块:

src/
├── main/
│   ├── java/
│   │   └── com/example/apidemo/
│   │       ├── config/           # 配置类
│   │       ├── controller/       # 控制器
│   │       ├── security/         # 安全相关组件
│   │       ├── service/          # 业务逻辑
│   │       ├── repository/       # 数据访问
│   │       └── model/            # 实体类
│   └── resources/
│       ├── application.yml       # 配置文件
│       └── data.sql              # 初始化数据(可选)

添加必要的依赖项(以 Maven 为例):

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- H2 Database (for demo) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- Springdoc OpenAPI UI -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.5.0</version>
    </dependency>
    <!-- JWT Support -->
    <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>
</dependencies>

用户与角色模型设计

我们需要建立一个简单的 RBAC(Role-Based Access Control)模型。假设系统中有三种角色:

  • ROLE_USER:普通用户
  • ROLE_ADMIN:管理员
  • ROLE_AUDIT:审计员

实体类定义

// User.java
package com.example.apidemo.model;

import jakarta.persistence.*;
import java.util.Set;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String username;

    @Column(nullable = false)
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles;

    // getters and setters
}
// Role.java
package com.example.apidemo.model;

import jakarta.persistence.*;

@Entity
@Table(name = "roles")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String name; // e.g., ROLE_USER, ROLE_ADMIN

    // getters and setters
}

Spring Security 基础配置

接下来配置 Spring Security,启用基于 JWT 的认证流程。

自定义 UserDetailsService

// UserDetailsServiceImpl.java
package com.example.apidemo.security;

import com.example.apidemo.model.User;
import com.example.apidemo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Collection;
import java.util.stream.Collectors;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                mapRolesToAuthorities(user.getRoles())
        );
    }

    private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Set<Role> roles) {
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
    }
}

JWT 工具类

// JwtUtil.java
package com.example.apidemo.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtUtil {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration.ms}")
    private long expirationMs;

    private SecretKey getSigningKey() {
        return Keys.hmacShaKeyFor(secret.getBytes());
    }

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = Jwts.parser()
                .verifyWith(getSigningKey())
                .build()
                .parseSignedClaims(token)
                .getPayload();
        return claimsResolver.apply(claims);
    }

    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .claims(claims)
                .subject(subject)
                .issuedAt(new Date(System.currentTimeMillis()))
                .expiration(new Date(System.currentTimeMillis() + expirationMs))
                .signWith(getSigningKey(), SignatureAlgorithm.HS512)
                .compact();
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}

JWT 过滤器

// JwtRequestFilter.java
package com.example.apidemo.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtUtil.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken authToken =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        chain.doFilter(request, response);
    }
}

Security 配置主类

// SecurityConfig.java
package com.example.apidemo.config;

import com.example.apidemo.security.JwtRequestFilter;
import org.springframework.beans.factory.annotation.Autowired;
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.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.http.SessionCreationPolicy;
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
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable())
           .authorizeHttpRequests(auth -> auth
               .requestMatchers("/auth/login").permitAll()
               .requestMatchers("/v3/api-docs/**", "/swagger-ui/**").permitAll() // 允许匿名访问文档资源
               .anyRequest().authenticated()
           )
           .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}

提示:@EnableMethodSecurity(prePostEnabled = true) 启用方法级安全注解如 @PreAuthorize。

Swagger(Springdoc OpenAPI)基础集成

添加配置类以定制 OpenAPI 行为:

// OpenApiConfig.java
package com.example.apidemo.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        final String securitySchemeName = "bearerAuth";
        return new OpenAPI()
                .addSecurityItem(new SecurityRequirement()
                        .addList(securitySchemeName))
                .components(new Components()
                        .addSecuritySchemes(securitySchemeName,
                                new SecurityScheme()
                                        .name(securitySchemeName)
                                        .type(SecurityScheme.Type.HTTP)
                                        .scheme("bearer")
                                        .bearerFormat("JWT")))
                .info(new Info()
                        .title("API Demo Service")
                        .version("1.0")
                        .description("A secure API service with role-based access control.")
                        .contact(new Contact()
                                .name("Developer Team")
                                .email("dev@example.com")));
    }
}

此时启动项目后访问:http://localhost:8080/swagger-ui.html 即可看到基础的 Swagger UI 页面。

实现基于角色的接口权限控制

现在我们希望在 Swagger UI 中根据不同用户角色显示不同的接口。例如:

  • ROLE_USER:只能看到 /api/user/** 相关接口;
  • ROLE_ADMIN:可以看到全部接口;
  • ROLE_AUDIT:只能看到 /api/logs 类只读接口。

但默认情况下,Swagger 会展示所有带有 @Operation 注解的接口,无法感知 Spring Security 的权限规则。

解决思路

我们可以利用 Springdoc 提供的 GroupedOpenApi 功能,按角色分组生成多个 API 分组文档,并结合拦截器判断当前用户是否有权访问该分组。

创建多组 API 文档

// ApiGroupConfig.java
package com.example.apidemo.config;

import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApiGroupConfig {

    @Bean
    public GroupedOpenApi userApi() {
        return GroupedOpenApi.builder()
                .group("user")
                .pathsToMatch("/api/user/**")
                .build();
    }

    @Bean
    public GroupedOpenApi adminApi() {
        return GroupedOpenApi.builder()
                .group("admin")
                .pathsToMatch("/api/admin/**")
                .build();
    }

    @Bean
    public GroupedOpenApi auditApi() {
        return GroupedOpenApi.builder()
                .group("audit")
                .pathsToMatch("/api/logs/**")
                .build();
    }
}

这样会在 Swagger UI 中出现三个分组标签页:

  • user
  • admin
  • audit

每个分组只包含对应路径前缀的接口。

控制分组可见性(核心难点)

虽然我们可以按路径划分分组,但如何让未授权用户看不到某些分组?比如普通用户不应该看到 admin 分组。

Springdoc 本身不直接支持“按权限隐藏分组”,但我们可以通过自定义 OpenApiCustomizer 或结合 Spring Security 上下文实现动态过滤。

方案:使用OpenApiCustomiser动态移除无权访问的 API 分组

不过更优雅的方式是——在前端层面控制导航菜单的渲染,或者借助反向代理做路由隔离。

但在单体应用中,我们可以通过扩展 /v3/api-docs/{group} 接口的行为,结合当前用户权限进行响应控制。

然而,最实用的做法是在前端集成时判断用户角色,然后动态加载对应的 Swagger 分组页面。

使用@ParameterObject和@Hidden控制单个接口可见性

除了分组外,还可以使用 @Hidden 注解隐藏某个接口:

// AdminController.java
package com.example.apidemo.controller;

import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/admin")
@PreAuthorize("hasRole('ADMIN')")
public class AdminController {

    @GetMapping("/users")
    @Operation(summary = "获取所有用户信息", description = "仅管理员可访问")
    public String getAllUsers() {
        return "All users data...";
    }

    @GetMapping("/settings")
    @Hidden // 不在 Swagger 中显示此接口
    public String systemSettings() {
        return "System settings hidden from docs.";
    }
}

配合方法级权限注解 @PreAuthorize("hasRole('ADMIN')"),即使有人知道 URL,也无法调用该接口。

在控制器中使用权限表达式

// UserController.java
package com.example.apidemo.controller;

import io.swagger.v3.oas.annotations.Operation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/user")
public class UserController {

    @GetMapping("/profile")
    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    @Operation(summary = "获取个人资料", description = "用户和管理员均可查看")
    public String getProfile() {
        return "Your profile info.";
    }

    @GetMapping("/change-password")
    @PreAuthorize("hasRole('USER')")
    @Operation(summary = "修改密码", description = "仅普通用户可操作")
    public String changePassword() {
        return "Password changed.";
    }
}
// AuditController.java
package com.example.apidemo.controller;

import io.swagger.v3.oas.annotations.Operation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/logs")
@PreAuthorize("hasRole('AUDIT')")
public class AuditController {

    @GetMapping("/access")
    @Operation(summary = "查询访问日志", description = "仅供审计员使用")
    public String getAccessLogs() {
        return "Audit logs...";
    }
}

登录接口实现

// AuthController.java
package com.example.apidemo.controller;

import com.example.apidemo.security.JwtUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@RestController
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @PostMapping("/auth/login")
    public ResponseEntity<?> createAuthenticationToken(@RequestBody LoginRequest loginRequest) throws Exception {
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())
            );
        } catch (Exception e) {
            throw new Exception("Invalid credentials", e);
        }

        final UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername());
        final String jwt = jwtUtil.generateToken(userDetails);

        return ResponseEntity.ok(new JwtResponse(jwt));
    }

    // DTO Classes
    static class LoginRequest {
        private String username;
        private String password;

        // getters and setters
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password; }
    }

    static class JwtResponse {
        private String token;

        public JwtResponse(String token) {
            this.token = token;
        }

        public String getToken() { return token; }
        public void setToken(String token) { this.token = token; }
    }
}

权限控制效果验证流程

步骤一:准备测试用户

data.sql 中插入初始数据:

INSERT INTO roles (id, name) VALUES 
(1, 'ROLE_USER'),
(2, 'ROLE_ADMIN'),
(3, 'ROLE_AUDIT');

INSERT INTO users (id, username, password) VALUES 
(1, 'user1', '$2a$10$vQWxNtWyP9KzZJYgFmXe.e5pGqyHlLkQnRrNsOtDfjVvL8M0uWwQC'), -- password: 123456
(2, 'admin1', '$2a$10$vQWxNtWyP9KzZJYgFmXe.e5pGqyHlLkQnRrNsOtDfjVvL8M0uWwQC'),
(3, 'audit1', '$2a$10$vQWxNtWyP9KzZJYgFmXe.e5pGqyHlLkQnRrNsOtDfjVvL8M0uWwQC');

INSERT INTO user_roles (user_id, role_id) VALUES 
(1, 1),  -- user1 -> USER
(2, 1), (2, 2),  -- admin1 -> USER + ADMIN
(3, 3);  -- audit1 -> AUDIT

密码加密方式为 BCrypt,明码为 123456

步骤二:获取 Token

发送 POST 请求到 /auth/login

{
  "username": "user1",
  "password": "123456"
}

返回:

{
  "token": "eyJhbGciOiJIUzUxMiJ9.xxxxx"
}

步骤三:携带 Token 访问接口

在 Swagger UI 中点击 “Authorize” 按钮,输入:

Bearer eyJhbGciOiJIUzUxMiJ9.xxxxx

然后尝试访问:

接口 角色是否可访问
/api/user/profileUSER ✅✔️
/api/admin/usersUSER ❌❌ 403 Forbidden
/api/logs/accessUSER ❌❌ 403 Forbidden

只有具备相应角色的用户才能成功调用。

权限模型可视化(Mermaid 图表)

下面用 Mermaid 展示系统的整体权限控制流程:

再来看一下 API 分组与角色之间的映射关系:

渲染错误: Mermaid 渲染失败: Lexical error on line 3. Unrecognized text. ...USER| C[/api/user/**] B -->|ROLE_ADM -----------------------^

这些图表清晰地表达了权限流向与接口归属关系。

高级技巧:动态隐藏 Swagger 分组

虽然 Springdoc 不原生支持“运行时隐藏分组”,但我们可以通过自定义 OpenApi Bean 结合 SecurityContext 实现条件注册。

但由于 OpenApi 是静态构建的,无法在每次请求时动态改变,因此推荐做法是:

方案一:为不同角色提供独立的文档入口

例如:

  • /swagger-user.html → 仅显示 user 分组
  • /swagger-admin.html → 显示所有分组

这可通过自定义 HTML 页面 + JS 控制实现。

方案二:前端集成时动态请求/v3/api-docs/groups并过滤

前端可以先调用 /v3/api-docs 获取所有分组信息,再根据当前用户角色决定加载哪些 api-docs/{group}

示例 JS 伪代码:

fetch('/v3/api-docs')
  .then(res => res.json())
  .then(data => {
    const groups = data.groups.filter(g =>
      hasAccessToGroup(currentUserRole, g.name)
    );
    renderSwagger(groups);
  });

function hasAccessToGroup(role, groupName) {
  switch(groupName) {
    case 'user': return ['USER', 'ADMIN'].includes(role);
    case 'admin': return role === 'ADMIN';
    case 'audit': return ['AUDIT', 'ADMIN'].includes(role);
    default: return false;
  }
}

安全最佳实践建议

永远不要在生产环境暴露 Swagger UI

使用 @Profile("!prod") 注解限制仅开发环境启用

@Configuration
@Profile("!prod")
public class SwaggerConfig { ... }

对敏感接口使用 @Hidden

  • 即使有权限控制,也不应在文档中暴露内部调试接口

启用 HTTPS

  • 所有包含 JWT 的通信必须走 HTTPS

定期轮换 JWT 密钥

  • 避免长期使用同一密钥导致安全隐患

记录 API 调用日志

  • 结合 AOP 实现接口调用审计

避免过度暴露实体字段

  • 使用 DTO 而非 Entity 直接返回

总结:Spring Security + Swagger 的完美协作

通过本文的实践,我们实现了:

✅ 基于 Spring Security 的角色权限控制
✅ 使用 JWT 实现无状态认证
✅ 利用 Springdoc OpenAPI 自动生成文档
✅ 按角色划分 API 分组
✅ 方法级权限注解 @PreAuthorize 控制访问
✅ 接口级别的隐藏与保护

虽然目前无法在 Swagger UI 内部完全“自动隐藏”无权访问的分组,但结合前端控制与合理的设计模式,依然可以达到理想的权限隔离效果。

未来随着 OpenAPI 规范的发展,或许会出现更智能的“权限感知文档生成器”。但在当下,掌握这套组合拳已经足以应对绝大多数企业级应用场景。

结语

API 是现代软件系统的神经中枢,其安全性不容忽视。Spring Security 提供了坚实的权限基石,而 Swagger 则让接口变得透明易用。两者的融合不是简单的功能叠加,而是安全与效率的平衡艺术。

希望本文能为你在实际项目中落地“安全可控的 API 文档体系”提供有力支持。记住:好的安全设计,既不让坏人得逞,也不让好人受阻。

以上就是Spring Security集成Swagger实现接口权限控制的详细内容,更多关于Spring Security Swagger接口权限控制的资料请关注脚本之家其它相关文章!

相关文章

  • 浅谈Java线程Thread.join方法解析

    浅谈Java线程Thread.join方法解析

    本篇文章主要介绍了浅谈Java线程Thread.join方法解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • 解决jasperreport导出的pdf每页显示的记录太少问题

    解决jasperreport导出的pdf每页显示的记录太少问题

    这篇文章主要介绍了解决jasperreport导出的pdf每页显示的记录太少问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • java实战之飞机大战小游戏(源码加注释)

    java实战之飞机大战小游戏(源码加注释)

    这篇文章主要介绍了java实战之飞机大战小游戏(源码加注释),文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • java实现Floyd算法

    java实现Floyd算法

    这篇文章主要为大家详细介绍了java实现Floyd算法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • Java中具有映射关系的容器:数组和Map的区别说明

    Java中具有映射关系的容器:数组和Map的区别说明

    这篇文章主要介绍了Java中具有映射关系的容器:数组和Map的区别说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Java语言中flush()函数作用及使用方法详解

    Java语言中flush()函数作用及使用方法详解

    这篇文章主要介绍了Java语言中flush函数作用及使用方法详解,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • 浅谈Spring 中 @EnableXXX 注解的套路

    浅谈Spring 中 @EnableXXX 注解的套路

    本文主要介绍了浅谈Spring 中 @EnableXXX 注解的套路,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • 浅析java中Pair和Map的区别

    浅析java中Pair和Map的区别

    这篇文章主要介绍了java中Pair和Map的区别,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • 详解Springboot+React项目跨域访问问题

    详解Springboot+React项目跨域访问问题

    这篇文章主要介绍了详解Springboot+React项目跨域访问问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • java正则表达式校验日期格式实例代码

    java正则表达式校验日期格式实例代码

    如果使用得当,正则表达式是匹配各种模式的强大工具,下面这篇文章主要给大家介绍了关于java正则表达式校验日期格式的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-05-05

最新评论