惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
Recent Commits to openclaw:main
Recent Commits to openclaw:main
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
Check Point Blog
S
Security Affairs
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Secure Thoughts
Recorded Future
Recorded Future
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
The Blog of Author Tim Ferriss
B
Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Google DeepMind News
Google DeepMind News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
A
Arctic Wolf
T
The Exploit Database - CXSecurity.com
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
AWS News Blog
AWS News Blog
MongoDB | Blog
MongoDB | Blog
Y
Y Combinator Blog
Google Online Security Blog
Google Online Security Blog
T
Troy Hunt's Blog
I
InfoQ
L
LINUX DO - 热门话题
WordPress大学
WordPress大学
C
Cisco Blogs
G
GRAHAM CLULEY
The Register - Security
The Register - Security
A
About on SuperTechFans
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Schneier on Security
Schneier on Security
Project Zero
Project Zero
H
Hackread – Cybersecurity News, Data Breaches, AI and More
P
Privacy & Cybersecurity Law Blog
Cloudbric
Cloudbric
H
Hacker News: Front Page
小众软件
小众软件
雷峰网
雷峰网
The Hacker News
The Hacker News
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Tor Project blog
博客园 - 聂微东
N
Netflix TechBlog - Medium
V
Vulnerabilities – Threatpost
The GitHub Blog
The GitHub Blog
腾讯CDC
P
Palo Alto Networks Blog
Scott Helme
Scott Helme

博客园 - nuccch

在Cursor中读取飞书文档 使用GIMP去除水印的有效方法 如何基于VSCode打造Java开发环境 在IDEA中配置注释模板 在Windows中使用Linux系统 树形层级结构的数据库表设计方案 如何理解和认识设计模式 申请Let's Encrypt免费HTTPS证书的方法 为GIT仓库项目设置独立配置参数 DBeaver设置不断开连接 构建工具Gradle入门实践 如何在Maven中排除依赖传递 87键键盘的数字键对应快捷键含义 关于Java JSON库的选择 解决mybatis批量更新慢问题 解决Spring Cloud Gateway中使用CompletableFuture.supplyAsync()执行Feign调用报错 Spring Boot框架中在Controller方法里获取Request和Response对象的2种方式 解读Spring Boot框架中不同位置抛出异常的处理流程 探究Spring Boot框架中访问不存在的接口时触发对error路径的访问 Spring Cloud工程中使用Nacos配置中心的2种方式
Swagger开启账号验证访问
nuccch · 2025-11-16 · via 博客园 - nuccch

背景概述

在一些小型的Java后端开发项目工程中集成Swagger生成接口文档是一个比较普遍的做法,默认情况下访问Swagger文档是没有限制的,任何人都可以访问并进行调试。这在某些场合下可能并不合适,特别是对于一些具备写数据的接口,随意暴露可能会被人恶意利用。因此,需要对访问接口的人进行一道认证拦截,只允许特定账户的人可以访问。

认证实现

只需要通过简单的配置即可实现对Swagger文档进行访问认证,如下阐述。

依赖配置:

<!-- 集成swagger -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

<!-- swagger UI主题:swagger-ui-layer -->
<!-- 访问路径:/docs.html -->
<dependency>
    <groupId>com.github.caspar-chen</groupId>
    <artifactId>swagger-ui-layer</artifactId>
    <version>1.1.3</version>
</dependency>

如下是一个未开启访问认证的Swagger配置示例(Spring Boot版本为2.6.13):

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(true)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xx.yy.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 用于定义API主界面的信息,比如可以声明所有的API的总标题、描述、版本
     */
    private ApiInfo apiInfo() {
        Contact contact = new Contact(
                "我是作者姓名", // 作者姓名
                "https://blog.csdn.net/", // 作者网址
                "123456789@163.com"); // 作者邮箱

        return new ApiInfoBuilder()
                .title("XX项目") //  可以用来自定义API的主标题
                //.description("XX项目接口") // 可以用来描述整体的API
                //.termsOfServiceUrl("https://www.baidu.com") // 用于定义服务的域名(跳转链接)
                .version("1.0") // 可以用来定义版本
                //.license("Swagger-的使用教程")
                //.licenseUrl("https://blog.csdn.net")
                //.contact(contact)
                .build(); //
    }

    // fix error: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
    @Bean
    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof WebMvcRequestHandlerProvider) {
                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
                }
                return bean;
            }

            private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
                List<T> copy = mappings.stream()
                        .filter(mapping -> mapping.getPatternParser() == null)
                        .collect(Collectors.toList());
                mappings.clear();
                mappings.addAll(copy);
            }

            @SuppressWarnings("unchecked")
            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
                try {
                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                    field.setAccessible(true);
                    return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
            }
        };
    }
}

如果要开启对Swagger文档的访问认证,需要在配置类SwaggerConfig上应用注解@EnableSwaggerBootstrapUI,如下:

@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
public class SwaggerConfig {
}

同时,在项目配置文件中添加Swagger认证信息:

# 启用Swagger文档
springfox:
  documentation:
    enabled: true

# 开启swagger认证
swagger:
  production: false ## 在生产环境不开启Swagger文档
  basic: ## 配置Swagger访问认证信息
    enable: true
    username: admin
    password: 123456

完成如上配置后即可开启Swagger访问认证,再次访问接口文档时会要求输入用户名和密码信息,如下图:
开启Swagger认证

支持多个认证账户

在Swagger的默认认证实现中,只支持配置单个用户。如果需要给不同的人分配不同的认证账户,就需要自定义实现。
另外,通过自定义实现还可以在服务端日志中记录相应账户访问接口的信息。

要自定义Swagger访问认证,核心就是自定义一个新的@EnableSwaggerBootstrapUI注解,在如下示例中自定义一个名为MyEnableSwaggerBootstrapUI的注解。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({SwaggerBootstrapUIConfiguration.class, SwaggerSecurityConfiguration.class, MarkdownFileConfiguration.class})
public @interface MyEnableSwaggerBootstrapUI {
}

引入的配置类SwaggerSecurityConfiguration定义如下:

@Configuration
public class SwaggerSecurityConfiguration {
    private Logger logger= LoggerFactory.getLogger(SwaggerSecurityConfiguration.class);

    @Autowired
    private Environment environment;

    @Bean
    public ProductionSecurityFilter productionSecurityFilter(){
        boolean prod=false;
        if (environment!=null){
            String prodStr=environment.getProperty("swagger.production");
            if (logger.isDebugEnabled()){
                logger.debug("swagger.production:{}",prodStr);
            }
            prod=Boolean.valueOf(prodStr);
        }
        ProductionSecurityFilter p=new ProductionSecurityFilter(prod);
        return p;
    }

    @Bean
    public SwaggerSecurityBasicAuthFilter securityBasicAuthFilter(){
        boolean enableSwaggerBasicAuth=false;
        Map<String, String> basicInfoMap = new HashMap<String, String>();
        if (environment!=null){
            String enableAuth=environment.getProperty("swagger.basic.enable");
            enableSwaggerBasicAuth=Boolean.valueOf(enableAuth);
            if (enableSwaggerBasicAuth){
                // 如果开启basic验证,从配置文件中获取用户名和密码
                // 在这里实现从配置文件中读取多个账户信息
                String pUser=environment.getProperty("swagger.basic.username");
                String pPass=environment.getProperty("swagger.basic.password");

                String[] pUserArr = pUser.split(",");
                String[] pPassArr = pPass.split(",");
                for (int i = 0; i < pUserArr.length; i++) {
                    basicInfoMap.put(pUserArr[i], pPassArr[i]);
                }
            }
        }
        SwaggerSecurityBasicAuthFilter securityBasicAuthFilter= new SwaggerSecurityBasicAuthFilter(enableSwaggerBasicAuth, basicInfoMap);
        return securityBasicAuthFilter;
    }
}

核心的SwaggerSecurityBasicAuthFilter定义如下:

public class SwaggerSecurityBasicAuthFilter extends SwaggerBasicFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(SwaggerSecurityBasicAuthFilter.class);

    /***
     * 是否开启basic验证,默认不开启
     */
    private boolean enableBasicAuth=false;

    /** 认证信息集合 */
    private Map<String, String> basicInfoMap = new HashMap<String, String>();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Enumeration<String> enumeration=filterConfig.getInitParameterNames();
        //SpringMVC环境中,由此init方法初始化此Filter,SpringBoot环境中则不同
        if (enumeration.hasMoreElements()){
            setEnableBasicAuth(Boolean.valueOf(filterConfig.getInitParameter("enableBasicAuth")));
            this.basicInfoMap.put(filterConfig.getInitParameter("userName"), filterConfig.getInitParameter("password"));
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest=(HttpServletRequest)request;
        HttpServletResponse httpServletResponse=(HttpServletResponse)response;
        //针对swagger资源请求过滤
        if (enableBasicAuth){
            if (match(servletRequest.getRequestURI())){
                //判断Session中是否存在
                Object swaggerSessionValue=servletRequest.getSession().getAttribute(SwaggerBootstrapUiBasicAuthSession);
                if (swaggerSessionValue!=null){
                    chain.doFilter(request,response);
                }else{
                    //匹配到,判断auth
                    //获取请求头Authorization
                    String auth=servletRequest.getHeader("Authorization");
                    if (auth==null||"".equals(auth)){
                        writeForbiddenCode(httpServletResponse);
                        return;
                    }
                    String userAndPass=decodeBase64(auth.substring(6));
                    String[] upArr=userAndPass.split(":");
                    if (upArr.length!=2){
                        writeForbiddenCode(httpServletResponse);
                    }else{
                        String iptUser=upArr[0];
                        String iptPass=upArr[1];

                        String basicPass = this.basicInfoMap.get(iptUser);
                        if (basicPass == null){
                            logger.error("basicUser not found:{}", iptUser);
                            writeForbiddenCode(httpServletResponse);
                            return;
                        }

                        //匹配服务端用户名及密码
                        if (iptPass.equals(basicPass)){
                            // 将认证信息保存到会话中
                            servletRequest.getSession().setAttribute(SwaggerBootstrapUiBasicAuthSession,iptUser);
                            chain.doFilter(request,response);
                        }else{
                            writeForbiddenCode(httpServletResponse);
                            return;
                        }
                    }
                }
            }else{
                chain.doFilter(request,response);
            }
        }else{
            chain.doFilter(request,response);
        }
    }

    @Override
    public void destroy() {

    }

    private void writeForbiddenCode(HttpServletResponse httpServletResponse) throws IOException {
        httpServletResponse.setStatus(401);
        httpServletResponse.setHeader("WWW-Authenticate","Basic realm=\"input Swagger Basic userName & password \"");
        httpServletResponse.getWriter().write("You do not have permission to access this resource");
    }

    // 通过参数basicInfoMap设置多个认证账户信息
    public SwaggerSecurityBasicAuthFilter(boolean enableBasicAuth, Map<String, String> basicInfoMap) {
        this.enableBasicAuth = enableBasicAuth;
        if (basicInfoMap != null){
            this.basicInfoMap.putAll(basicInfoMap);
        }
    }

    public SwaggerSecurityBasicAuthFilter(boolean enableBasicAuth) {
        this.enableBasicAuth = enableBasicAuth;
    }

    public SwaggerSecurityBasicAuthFilter() {
    }

    public boolean isEnableBasicAuth() {
        return enableBasicAuth;
    }

    public void setEnableBasicAuth(boolean enableBasicAuth) {
        this.enableBasicAuth = enableBasicAuth;
    }
}

SwaggerBasicFilter定义如下:

public class SwaggerBasicFilter implements Consts {
    private Logger logger= LoggerFactory.getLogger(BasicFilter.class);

    protected List<Pattern> urlFilters=null;

    public SwaggerBasicFilter(){
        urlFilters=new ArrayList<>();
        // 添加对swagger UI主题(swagger-ui-layer)的路径:/docs.html 拦截
        urlFilters.add(Pattern.compile(".*?/docs\\.html.*",Pattern.CASE_INSENSITIVE));
        urlFilters.add(Pattern.compile(".*?/doc\\.html.*",Pattern.CASE_INSENSITIVE));
        urlFilters.add(Pattern.compile(".*?/v2/api-docs.*",Pattern.CASE_INSENSITIVE));
        urlFilters.add(Pattern.compile(".*?/v2/api-docs-ext.*",Pattern.CASE_INSENSITIVE));
        urlFilters.add(Pattern.compile(".*?/swagger-resources.*",Pattern.CASE_INSENSITIVE));
        urlFilters.add(Pattern.compile(".*?/swagger-ui\\.html.*",Pattern.CASE_INSENSITIVE));
        urlFilters.add(Pattern.compile(".*?/swagger-resources/configuration/ui.*",Pattern.CASE_INSENSITIVE));
        urlFilters.add(Pattern.compile(".*?/swagger-resources/configuration/security.*",Pattern.CASE_INSENSITIVE));
    }

    protected boolean match(String uri){
        boolean match=false;
        if (uri!=null){
            for (Pattern pattern:getUrlFilters()){
                if (pattern.matcher(uri).matches()){
                    match=true;
                    break;
                }
            }
        }
        return match;
    }
    protected String decodeBase64(String source){
        String decodeStr = null;
        if (source!=null){
            //BASE64Decoder decoder=new BASE64Decoder();
            try {
                //byte[] bytes=decoder.decodeBuffer(source);
                byte[] bytes= Base64.getDecoder().decode(source);
                decodeStr=new String(bytes);
            } catch (Exception e) {
                logger.error(e.getMessage(),e);
            }
        }
        return decodeStr;
    }

    public List<Pattern> getUrlFilters() {
        return urlFilters;
    }
}

完成上述自定义组件准备后,将SwaggerConfig配置类中的EnableSwaggerBootstrapUI注解换成MyEnableSwaggerBootstrapUI即可,即:

@Configuration
@EnableSwagger2
@MyEnableSwaggerBootstrapUI
public class SwaggerConfig {
}

同时,还可以在项目配置文件中添加多个账户信息:

# 开启swagger认证
swagger:
  production: false ## 在生产环境不开启Swagger文档
  basic: ## 配置Swagger访问认证信息
    enable: true
    username: zhangsan,lisi # 多个账户用户名,用英文逗号分隔
    password: 123456,524163 # 多个账户密码,用英文逗号分隔

这样,就可以给不同访问Swagger接口文档的人分配相应的账户信息,也便于在服务端记录不同账户访问接口的情况。

【参考】
Springboot整合swagger,以及开启环境、账号权限验证访问