微服务 - 网关统一鉴权

一、什么是网关统一鉴权?

网关统一鉴权,顾名思义,就是将原本分散在各个微服务中的身份验证和权限校验逻辑,抽取出来并集中到API网关这一层来统一处理。

  • 传统方式(无网关): 每个微服务(如用户服务、订单服务、支付服务)都需要自己实现一套鉴权逻辑,检查Token是否有效、用户是否有权限访问等。这会导致以下一系列问题:
    • 代码重复与维护困难:每个服务都需要编写和维护相似的鉴权代码,违反了"Don't Repeat Yourself"原则。
    • 标准不一:不同的开发团队可能实现不同的鉴权逻辑或安全标准,导致系统整体安全性不一致。
    • 性能瓶颈:每次请求都需要在多个服务中进行重复的鉴权操作(如JWT解析、数据库查询),增加延迟。
    • 耦合度增加:业务服务需要关心非业务性的安全逻辑,与服务无状态、高内聚的设计理念相悖。
  • 统一鉴权方式: 鉴权逻辑只在API网关实现一次。请求到达网关后,网关先进行鉴权,只有通过鉴权的请求才会被转发到后端的微服务。后端微服务可以"信任"网关,无需再次鉴权,只需处理业务逻辑。

二、为什么需要网关统一鉴权?

网关统一鉴权就是为了解决上述问题而生的。它的核心思想是:将鉴权这个横切关注点从各个业务服务中剥离出来,集中到API网关这一层进行处理。其主要好处如下:

  1. 安全性集中管理:
    • 将敏感的安全逻辑集中在一处,避免了安全漏洞分散在各个服务中。一旦发现安全策略需要调整,只需在网关修改,所有服务立即生效。
    • 更容易实施统一的安全标准和审计。
  2. 架构解耦与业务纯净:
    • 后端微服务不再需要关心复杂的鉴权逻辑,可以专注于实现业务功能,使得服务更加"纯净"和"高内聚"。
    • 服务间的耦合度降低,更容易开发和维护。
  3. 提升性能:
    • 对于非法请求(如Token无效、权限不足),网关可以在最外层直接拦截并拒绝,避免了请求穿透到后端服务,节省了宝贵的后端资源。
  4. 统一管控与监控:
    • 可以方便地在网关层统一添加日志、限流、熔断等管控措施。所有认证和授权失败都可以在网关层面被监控和报警。

三、核心思想与流程

  • 核心思想:

    • 前置鉴权。在请求到达内部微服务之前,由网关作为一个统一的"安检站",对所有请求进行身份验证和权限校验。只有通过检查的请求才会被路由到后端的业务服务;失败的请求则直接被网关拦截并返回错误响应。
  • 基本流程:

    1. 客户端请求:客户端(Web、App等)携带访问令牌(通常是JWT)发起请求。

    2. 请求到达网关:所有外部请求首先到达API网关。

    3. 令牌提取与验证:

      • 网关从请求头(通常是 Authorization: Bearer )或Cookie中提取令牌。
      • 进行基础验证,例如检查令牌结构、签名、是否过期等。
    4. 身份认证:

      • 验证令牌真伪:使用预先配置的密钥或公钥验证JWT的签名。
      • (可选)检查黑名单:查询令牌是否已被注销(如用户已登出)。
    5. 权限鉴定:

      • 从验证通过的令牌中解析出用户信息(如用户ID、角色、权限列表)。
      • 根据请求的路径(URL) 和方法(HTTP Method),判断当前用户是否拥有访问该资源的权限。这一步通常需要查询权限规则或与专门的鉴权服务交互。
    6. 网关决策:

      • 成功:网关将请求(通常会附加解析出的用户信息)路由到目标微服务。微服务无需再次鉴权,可直接处理业务逻辑。
      • 失败:网关直接返回 401 Unauthorized(未认证)或 403 Forbidden(无权限)响应,请求不会到达后端服务。
    7. 请求转发:通过鉴权的请求被转发到相应的业务微服务。

四、 关键技术组件与实现

  1. API网关:

    • Spring Cloud Gateway: 基于Spring 5、Project Reactor的响应式网关,性能高,是当前Spring Cloud生态的首选。
    • Netflix Zuul: Spring Cloud旧版本的网关组件,目前已进入维护模式。
    • Kong / Apache APISIX: 基于Nginx/OpenResty的高性能、云原生API网关,功能强大,插件生态丰富。
  2. 认证与授权协议/技术:

    • JWT: 最流行的无状态令牌。鉴权服务器签发JWT后,网关只需使用公钥验证其签名即可,无需每次请求都去查询数据库或鉴权服务,性能极高。
    • OAuth 2.0 / OIDC: 行业标准的授权框架。网关可以扮演OAuth 2.0资源服务器的角色,验证Access Token。
    • 自定义Token: 也可以使用自定义的Token,但需要网关每次去查询鉴权服务来验证Token的有效性,是有状态的。

五、 实践示例(以Spring Cloud Gateway + JWT为例)

步骤1:生成与验证JWT

首先,你需要一个认证服务(通常是独立的微服务,如 auth-service),负责用户登录并颁发JWT。

java 复制代码
// 伪代码:在 auth-service 中登录成功后生成JWT
@Service
public class AuthService {
    public String login(String username, String password) {
        // 1. 验证用户名密码
        User user = userService.authenticate(username, password);
        // 2. 生成JWT
        String token = Jwts.builder()
                .setSubject(user.getId()) // 用户标识
                .claim("roles", user.getRoles()) // 用户角色
                .claim("authorities", user.getAuthorities()) // 用户权限
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1小时过期
                .signWith(SignatureAlgorithm.HS512, secretKey) // 使用密钥签名
                .compact();
        return token;
    }
}
步骤2:网关统一鉴权过滤器

在网关服务中,创建一个全局过滤器 AuthGlobalFilter,实现 GlobalFilter 接口。

过滤器逻辑:

  • 排除登录、注册等白名单路径。
  • 从请求头获取 Authorization。
  • 使用JWT库(如jjwt)验证Token的签名和过期时间。
  • 从JWT的Payload中解析出用户角色和权限。
  • 查询权限规则(可以从数据库或配置中心加载),判断用户权限是否能匹配请求路径+方法。
  • 通过验证,则将用户信息放入请求头,转发请求;否则,直接返回错误响应。

伪代码示例:

java 复制代码
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private JwtUtil jwtUtil; // 一个自定义的JWT工具类

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 1. 判断是否为无需鉴权的白名单路径(如登录、注册、公开API)
        String path = request.getURI().getPath();
        if (isExcludePath(path)) {
            return chain.filter(exchange); // 直接放行
        }

        // 2. 提取JWT令牌
        String token = getTokenFromRequest(request);
        if (StringUtils.isEmpty(token)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete(); // 返回401
        }

        // 3. 验证并解析JWT
        Claims claims;
        try {
            claims = jwtUtil.parseToken(token); // 验证签名和过期时间
        } catch (Exception e) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete(); // 令牌无效,返回401
        }

        // 4. (可选)权限鉴定 - 这里以基于路径的简单RBAC为例
        String userRoles = (String) claims.get("roles");
        if (!hasPermission(userRoles, path, request.getMethodValue())) {
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete(); // 权限不足,返回403
        }

        // 5. 鉴权通过,将用户信息添加到请求头,传递给下游服务
        String userId = claims.getSubject();
        ServerHttpRequest newRequest = request.mutate()
                .header("X-User-Id", userId)
                .header("X-User-Roles", userRoles)
                .build();

        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
        return chain.filter(newExchange);
    }

    private boolean isExcludePath(String path) {
        // 从配置文件中读取白名单
        return Arrays.asList("/auth/login", "/auth/register", "/public/**").contains(path);
    }

    private String getTokenFromRequest(ServerHttpRequest request) {
        String authHeader = request.getHeaders().getFirst("Authorization");
        if (StringUtils.hasText(authHeader) && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }

    private boolean hasPermission(String userRoles, String path, String method) {
        // 实现你的权限逻辑:例如,查询数据库或缓存,判断该角色是否有权访问此API
        // 这里可以简化成:检查 userRoles 是否包含访问此路径所需的角色
        // 更复杂的可以使用像Spring Security的AccessDecisionManager
        return permissionService.checkPermission(userRoles, path, method);
    }

    @Override
    public int getOrder() {
        return -100; // 过滤器执行顺序,数字越小优先级越高
    }
}
步骤3:业务微服务(无需鉴权)

下游的业务微服务(如 user-service)接收到请求后,可以直接从请求头中获取用户信息,并信任该信息。

java 复制代码
@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable String id, 
                       @RequestHeader("X-User-Id") String currentUserId) {
        // 直接从网关传递的请求头中获取当前用户ID,无需再次解析JWT
        // 处理业务逻辑...
        return userService.getUserById(id);
    }
}
相关推荐
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
网络研究院1 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理
treesforest1 天前
AI安全系统如何识别异常访问?IP风险识别正在成为关键能力
网络·人工智能·tcp/ip·安全·web安全
shushangyun_1 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
2601_961845151 天前
粉笔行测题库|系统班|刷题
网络·百度·微信·微信公众平台·facebook·新浪微博
施努卡机器视觉2 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造