登录生成 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 自动透传。

相关推荐
天空属于哈夫克32 天前
企业微信二次开发:精准实现关键词自动回复
架构·企业微信
分布式存储与RustFS2 天前
MinIO 官方 Docker 镜像被移除:依赖它的项目该怎么办
docker·云原生·devops·对象存储·minio·分布式存储
晨米酱2 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
这个DBA有点耶2 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构
codeejun2 天前
每日一Go·MySQL-5、锁机制全解析
云原生·golang
码流子2 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
moMo2 天前
从固定流程到问题路由:让 LangGraph RAG 按需检索
架构
张洛闻Eren2 天前
k8s云原生【第十课】:水平 Pod 自动扩缩容
运维·数据库·云原生·kubernetes·github
白远山2 天前
上海24小时自助健身房解决方案实战指南与经验分享
java·数据库·架构·需求分析
LorryJovens2 天前
【LAAP科研】双系统具身AGI范式研究——基于LAAP认知架构与Jev概率决策模型的系统性技术调研与范式验证
人工智能·gpt·安全·架构