登录生成 Token + 网关解析 Token + 微服务透传 userId

0. 整体流程(先看这个,你就全懂)

  1. 前端用户登录,登陆成功后会返回Token给前端,前端要进行保存。

  2. 前端 每次请求在请求头里带 TokenAuthorization: Bearer xxxxx

  3. 网关 GatewayGlobalFilter 全局过滤器:

    • 拿到 Token
    • 校验、解析出 userId
    • 重新构造请求头user-info: 1001
    • 转发给下游微服务(user-service /item-service 等)
  4. 每个微服务(比如 B 服务)SpringMVC 的 preHandle 拦截器

    • 从请求头拿到 user-info
    • 存入 UserContext
    • 业务代码直接用:UserContext.getUser()
  5. 微服务之间互相调用(A → B)Feign 拦截器

    • 自动把 user-info 带到下一个服务
    • 全程不用你手动传参

1. 公共模块(common)通用代码

1.1 UserContext.java(A、B、网关、所有服务都用)

java 复制代码
package com.hmall.common.utils;

public class UserContext {
    private static final ThreadLocal<Long> TL = new ThreadLocal<>();

    public static void setUser(Long userId) {
        TL.set(userId);
    }

    public static Long getUser() {
        return TL.get();
    }

    public static void remove() {
        TL.remove();
    }
}

1.2 JWT 工具类(生成、解析 Token)

java 复制代码
package com.hmall.common.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Map;

public class JwtTool {
    // 密钥,实际项目放配置文件
    private static final String SECRET = "your-32-byte-long-secret-key-here!!!!";
    private static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes());
    private static final long EXPIRATION = 1000 * 60 * 60 * 24 * 7; // 7天

    // 生成Token
    public static String createToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                .signWith(KEY, SignatureAlgorithm.HS256)
                .compact();
    }

    // 解析Token
    public static Claims parseToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(KEY)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
}

2. 登录接口(auth-service /user-service)

2.1 登录成功生成 Token

java 复制代码
@PostMapping("/login")
public Result<String> login(@RequestBody LoginDTO dto) {
    // 1. 校验用户名密码
    User user = userService.lambdaQuery()
            .eq(User::getPhone, dto.getPhone())
            .one();
    if (user == null || !passwordMatch(user.getPassword(), dto.getPassword())) {
        return Result.error("账号或密码错误");
    }

    // 2. 生成Token
    Map<String, Object> claims = new HashMap<>();
    claims.put("userId", user.getId());
    String token = JwtTool.createToken(claims);

    return Result.success(token);
}

3. 网关 Gateway 解析 Token(最重要!)

3.1 网关过滤器:校验 Token → 传递 userId

java 复制代码
package com.hmall.gateway.filter;

import com.hmall.common.utils.JwtTool;
import io.jsonwebtoken.Claims;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private static final String[] ALLOW_PATHS = {
            "/login", "/register", "/search", "/test"
    };

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().toString();

        // 1. 白名单直接放行
        for (String allow : ALLOW_PATHS) {
            if (path.contains(allow)) {
                return chain.filter(exchange);
            }
        }

        // 2. 获取Token
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            return unauthorized(exchange);
        }
        token = token.substring(7);

        // 3. 解析Token
        Claims claims;
        try {
            claims = JwtTool.parseToken(token);
        } catch (Exception e) {
            return unauthorized(exchange);
        }

        // 4. 拿到userId,放进请求头传给下游微服务
        Long userId = claims.get("userId", Long.class);

        ServerHttpRequest newRequest = request.mutate()
                .header("user-info", userId.toString())
                .build();

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

    private Mono<Void> unauthorized(ServerWebExchange exchange) {
        exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

4. 你之前那套:Feign 透传(A 调 B 自动带 userId)

4.1 Feign 拦截器(A 服务 → 自动传)

java 复制代码
package com.hmall.api.config;

import com.hmall.common.utils.UserContext;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DefaultFeignConfig {
    @Bean
    public RequestInterceptor userInfoInterceptor() {
        return template -> {
            Long userId = UserContext.getUser();
            if (userId != null) {
                template.header("user-info", userId.toString());
            }
        };
    }
}

4.2 微服务接收(B 服务 → 自动收)

拦截器

java 复制代码
package com.hmall.item.interceptor;

import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String userIdStr = request.getHeader("user-info");
        if (userIdStr != null) {
            UserContext.setUser(Long.valueOf(userIdStr));
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        UserContext.remove();
    }
}

注册拦截器

java 复制代码
package com.hmall.item.config;

import com.hmall.item.interceptor.UserInfoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor())
                .addPathPatterns("/**");
    }
}

5. 最终使用(任何微服务直接用)

java 复制代码
Long userId = UserContext.getUser();

6. 一句话总结你现在的体系

✅ 登录生成 Token

✅ 网关 解析 Token → 拿 userId

✅ 网关把 userId 放入请求头

✅ Feign 自动 透传到所有微服务

✅ 任何服务直接 UserContext.getUser() 拿用户

这就是标准企业级微服务登录鉴权体系

前端带 token → 网关 GlobalFilter 解析 token → 放入 user-info 请求头 → 传给微服务 → 微服务用 preHandle 接收 user-info → Feign 自动透传。

相关推荐
2301_822703202 小时前
生命科学大分子资产模拟交易系统:基于鸿蒙Flutter跨端架构的高频订单簿与K线图渲染引擎
flutter·华为·架构·开源·harmonyos·鸿蒙
fire-flyer2 小时前
ClickHouse系列(五):ClickHouse 写入链路全解析(Insert 到 Merge)
大数据·clickhouse·架构
恼书:-(空寄2 小时前
K8s 网关(Ingress-Nginx/Envoy/云原生网关)20 个高频故障速查手册
云原生·k8s·ingress
大数据新鸟2 小时前
微服务之Spring Cloud OpenFeign
spring cloud·微服务·架构
大数据新鸟2 小时前
微服务之Spring Cloud LoadBalancer
java·spring cloud·微服务
heimeiyingwang2 小时前
【架构实战】时序数据库选型:InfluxDB vs TDengine
架构·时序数据库·tdengine
lishutong10062 小时前
Android 性能诊断 V2:基于 Agent Skill 的原生 IDE 融合架构
android·ide·架构
wok1572 小时前
WebMVC 和 WebFlux 架构选型
java·spring·架构·mvc
fire-flyer3 小时前
ClickHouse系列(六):Kafka 到 ClickHouse 的生产级写入架构
clickhouse·架构·kafka