普通 SpringBoot 单体项目改造成微服务(Nacos+Gateway + 内部服务免鉴权)

普通 SpringBoot 单体项目改造成微服务(Nacos+Gateway + 内部服务免鉴权)

摘要

本文详细介绍如何将普通 SpringBoot 单体项目改造为微服务架构。涵盖 Nacos 服务注册发现、Gateway 网关路由配置、微服务内部信任免鉴权完整实现方案,同时解决改造过程中 401 未授权、循环依赖、过滤器优先级冲突等常见坑。

关键词

SpringBoot 微服务改造;Nacos;Spring Cloud Gateway;微服务内部免鉴权;SpringSecurity

🔥 前言

日常开发中很多项目都是从SpringBoot 单体应用起步,随着业务模块增多、并发量提升、团队协作复杂化,单体架构的弊端逐渐凸显:

  • 所有功能打包部署,改动需全量发布,风险高
  • 无法针对核心模块单独扩容
  • 无统一请求入口,接口直接暴露外网不安全

因此,将单体 SpringBoot 项目改造为微服务是架构演进的必经之路。

本文基于通用生产落地经验,从零完成整套微服务改造:

  1. SpringBoot 整合 Nacos 实现服务注册与发现
  2. Gateway 网关实现路由转发、路径重写、负载均衡
  3. 微服务内部建立信任通道,实现网关请求免登录、免 Token、免鉴权
  4. 规避 401 未授权、循环依赖、过滤器冲突等经典问题

📌 目录

  1. 环境版本说明
  2. 引入 Nacos 微服务依赖
  3. 项目配置 Nacos 注册中心
  4. Gateway 网关路由核心配置
  5. 后端自定义网关内部信任过滤器
  6. SpringSecurity 整合过滤器配置
  7. 可选扩展:网关透传账号自动登录
  8. 接口调用效果验证
  9. 改造常见问题与避坑总结
  10. 改造整体架构小结

一、环境版本说明

  • 框架版本:SpringBoot 3.x
  • 微服务版本:Spring Cloud Alibaba 适配 SpringBoot3 稳定版
  • 注册中心:Nacos 2.x
  • 网关组件:Spring Cloud Gateway
  • 安全框架:Spring Security 6.x

二、引入 Nacos 微服务依赖

pom.xml中引入 Spring Cloud Alibaba 版本管理和 Nacos 注册发现依赖,统一版本避免框架冲突。

xml 复制代码
	<dependencies>    
        <!-- Nacos 服务发现 --> 
		<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

    <!-- Spring Cloud 版本管理 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2023.0.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2023.0.1.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

三、项目配置 Nacos 注册中心

application.yml配置文件中添加 Nacos 基础配置。

yaml 复制代码
spring:
  application:
    name: business-service  # 自定义微服务名称,网关路由依赖此名称
  cloud:
    nacos:
      discovery:
        # 替换为自己的Nacos服务地址
        server-addr: 127.0.0.1:8848
        # 自定义命名空间,保持网关和微服务统一即可
        namespace: public
        # 脱敏默认账号密码,自行修改
        username: nacos
        password: nacos
        # 开启服务注册与发现
        enabled: true
        register-enabled: true

配置完成启动项目,可在 Nacos 控制台「服务管理 - 服务列表」查看服务注册状态。

四、Gateway 网关路由核心配置

网关作为微服务统一入口,负责请求路由、负载均衡、路径重写、透传内部秘钥,收口外网所有请求,保护后端服务不暴露公网。

yaml 复制代码
spring:
  cloud:
    # 网关配置
    gateway:
      # 开启请求日志
      requestLog: true
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: true
      # 路由规则配置
      routes:
        - id: business-service-route
          # lb代表负载均衡,从Nacos拉取服务实例
          uri: lb://business-service
          predicates:
            - Path=/business/**
          filters:
            # 路径重写:前端 /business/xxx 转发到服务 /xxx
            - RewritePath=/business/(?<path>.*), /$\{path}
            # 固定内部通信秘钥,微服务间信任凭证
            - name: SetRequestHeader
              args:
                name: X-Internal-Gateway-Secret
                value: Gateway@MicroService2026

核心作用:

  • 所有请求统一经过网关,后端服务仅内网可见
  • 基于 Nacos 自动实现负载均衡、服务实例感知
  • 自动透传固定秘钥请求头,作为微服务内部信任标识

五、后端自定义网关内部信任过滤器

微服务改造最大痛点:网关转发的内部接口,仍被 SpringSecurity 拦截抛出 401

自定义全局过滤器,识别网关透传的合法秘钥,若业务需要网关前置完成用户登录,后端直接复用身份 ,可改造过滤器接收自定义账号请求头,自动完成系统登录认证,无需校验密码。不需要的话则直接授予系统认证权限,免登录、免 Token、免业务鉴权

java 复制代码
package com.xxx.business.auth.filter;

import com.iflytek.skillhub.auth.rbac.PlatformPrincipal;
import com.iflytek.skillhub.auth.session.PlatformSessionService;
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.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import com.iflytek.skillhub.auth.local.LocalAuthService;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

public class InternalAuthFilter extends OncePerRequestFilter {

    public static final String INTERNAL_SECRET = "SkillHub@2026RuoYiCloud";
    public static final String HEADER_NAME = "X-Internal-SkillHub-Secret";

    private final LocalAuthService localAuthService;
    private final PlatformSessionService platformSessionService;

    public InternalAuthFilter(LocalAuthService localAuthService, PlatformSessionService platformSessionService) {
        this.localAuthService = localAuthService;
        this.platformSessionService = platformSessionService;
    }

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

        String secret = request.getHeader(HEADER_NAME);
        String userName = request.getHeader("userName");
        String password = request.getHeader("password");

        if (INTERNAL_SECRET.equals(secret) && userName != null && password != null) {
            try {
                //调用服务中的登录接口进行登录认证
                PlatformPrincipal principal = localAuthService.login(userName, password);
                platformSessionService.establishSession(principal, request);

                List<SimpleGrantedAuthority> authorities = principal.platformRoles() == null
                        ? Collections.emptyList()
                        : principal.platformRoles().stream()
                        .map(r -> new SimpleGrantedAuthority("ROLE_" + r))
                        .toList();

                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                        principal, null, authorities
                );
                SecurityContextHolder.getContext().setAuthentication(auth);

                System.out.println("=== 网关自动登录成功:" + userName + " ===");
            } catch (Exception e) {
                System.err.println("网关登录失败:" + e.getMessage());
            }
        }
        filterChain.doFilter(request, response);

    }
}

六、SpringSecurity 整合过滤器配置

SecurityConfig配置类中,手动 Bean 注入过滤器,不加 @Component 规避循环依赖,同时调整过滤器优先级,保证最先执行。

java 复制代码
package com.xxx.business.auth.config;

import com.xxx.business.auth.filter.InternalGatewayAuthFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import static com.xxx.business.auth.filter.InternalGatewayAuthFilter.HEADER_NAME;
import static com.xxx.business.auth.filter.InternalGatewayAuthFilter.INTERNAL_SECRET;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    // 手动创建过滤器Bean,规避循环依赖
    @Bean
    public InternalGatewayAuthFilter internalGatewayAuthFilter() {
        return new InternalGatewayAuthFilter();
    }

    /**
     * 核心配置:过滤器顺序 + 放行规则
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, InternalGatewayAuthFilter internalGatewayAuthFilter) throws Exception {
        

        http
                .csrf(csrf -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .csrfTokenRequestHandler(csrfHandler)
                        .ignoringRequestMatchers(csrfIgnoreMatcher)
                )
                // 权限配置(正确合并版)
                .authorizeHttpRequests(auth -> {
                    // 网关带密钥的请求 → 直接放行
                    auth.requestMatchers(request ->
                            INTERNAL_SECRET.equals(request.getHeader(HEADER_NAME))
                    ).permitAll();

                    // 加载系统原有路由放行规则(如登录接口、静态资源等)
                    configureRoutePolicies(auth);

                    // 其他所有请求必须认证
                    auth.anyRequest().authenticated();
                })
                
                // 关键:把内部过滤器放在最前面
                .addFilterBefore(internalGatewayAuthFilter, UsernamePasswordAuthenticationFilter.class)
               

        return http.build();
    }
    private void configureRoutePolicies(org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry auth) {
        for (RouteSecurityPolicyRegistry.RouteAuthorizationPolicy policy : routeSecurityPolicyRegistry.authorizationPolicies()) {
            switch (policy.accessLevel()) {
                case PERMIT_ALL -> auth.requestMatchers(policy.toRequestMatcher()).permitAll();
                case AUTHENTICATED -> auth.requestMatchers(policy.toRequestMatcher()).authenticated();
                case ROLE_PROTECTED -> auth.requestMatchers(policy.toRequestMatcher()).hasAnyRole(policy.roles());
            }
        }
    }
}

七、接口调用效果验证

Postman 测试方式:

  • 请求地址:GET /business/api/xxx/xxx
  • 请求头:X-Internal-Gateway-Secret: Gateway@MicroService2026

调用结果:

  • 无需携带 Token、无需页面登录
  • 接口直接 200 正常返回业务数据
  • 控制台打印网关放行日志

八、改造常见问题与避坑总结

1、接口 401 未授权

  • 网关未配置透传内部秘钥请求头
  • 过滤器优先级过低,被 Token 认证过滤器覆盖
  • 前后端秘钥字符、大小写不一致

2、多过滤器执行冲突

  • 不使用@Order注解控制优先级
  • 依靠 SpringSecurity 的addFilterBefore手动指定执行顺序

3、Nacos 服务注册失败

  • Spring Cloud Alibaba 与 SpringBoot 版本不匹配
  • 命名空间、服务名称配置不一致
  • 服务器网络不通,无法访问 Nacos 注册中心

九、改造整体架构小结

通过以上通用方案,可快速完成普通 SpringBoot 单体 → 标准微服务架构改造:

  1. 接入 Nacos 实现服务注册、发现与服务治理
  2. Gateway 统一网关入口,实现路由转发、负载均衡
  3. 基于私有秘钥建立微服务内部信任通道,内部接口免鉴权
  4. 兼容原有 SpringSecurity 登录体系,无侵入业务代码
  5. 规避改造中 401、循环依赖、过滤器冲突等经典问题
相关推荐
小则又沐风a1 小时前
初步了解进程的概念
java·linux·服务器·前端
摩羯座-小齐1 小时前
java excel级联下拉框
java·excel
砍材农夫1 小时前
物联网 基于netty入门与线程模型探秘简述
java·物联网·struts
GentleDevin1 小时前
IntelliJ Idea常用快捷键(Window和Mac对照表)
java·ide·intellij-idea
Paxon Zhang1 小时前
JavaEE 初阶变强宝典 **线程安全问题,线程状态,锁synchronized**
java·java-ee
阿拉金alakin1 小时前
Java IO 核心类 File、InputStream/OutputStream 实战总结
java·开发语言
有梦想的小何1 小时前
Cursor AI 编程实战(篇二):Rules、速查与 Adapter/App 全文
java·大数据·elasticsearch·搜索引擎·ai·ai编程
噢,我明白了3 小时前
表单的完整 CRUD 练习【极简个人记账本】(含前端后端链接mySQL)
java·前端·数据库·mysql
通往曙光的路上3 小时前
mysql1
java