You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于JWT认证的微服务用户专属模板访问方案咨询

最优实现方案:基于JWT自定义Claim+网关透传用户信息

咱们先明确核心需求:用户通过网关认证后,调用模板接口时无需手动传递用户名/姓名,自动只能获取自己的模板。不用额外生成ID Token,结合你现有的JWT网关和Feign就能搞定,而且最优路径是减少跨服务调用次数,直接从JWT里拿到关键信息透传给模板服务。

步骤1:在JWT中嵌入用户姓名(避免重复调用认证服务)

你现在的JWT只存了username,而模板表需要用户姓名来筛选。与其每次调用模板服务时再去认证服务查姓名,不如在生成JWT时就把用户姓名作为自定义Claim放进去,这样网关解析Token时直接就能拿到,省一次RPC调用。

修改JwtUtil类,添加用户姓名到Claim

@Service
public class JwtUtil {
    private String SECRET_KEY = "helloworld";
    // ... 原有方法不变

    public String generateToken(UserDetails userDetails) {
        Map<String,Object> claims =new HashMap<>();
        // 从自定义的UserDetails里取出用户姓名
        if(userDetails instanceof MyUserDetails) {
            claims.put("fullName", ((MyUserDetails) userDetails).getFullName());
        }
        return createToken(claims,userDetails.getUsername());
    }

    // 新增一个提取用户姓名的方法(可选,也可以直接用extractClaim)
    public String extractFullName(String token) {
        return extractClaim(token, claims -> claims.get("fullName", String.class));
    }

    // ... 原有方法不变
}

修改MyUserDetails类,添加用户姓名字段

public class MyUserDetails implements UserDetails{
    private String username;
    private String password;
    private boolean active;
    private List<GrantedAuthority> authorities;
    private String fullName; // 新增用户姓名字段

    public MyUserDetails() {
    }

    public MyUserDetails(User user) {
        this.username=user.getUsername();
        this.password=user.getPassword();
        this.active=user.isActive();
        this.fullName = user.getFullName(); // 假设你的User实体有getFullName()方法
        this.authorities =Arrays.stream(user.getRoles().split(","))
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    // 新增getter方法
    public String getFullName() {
        return fullName;
    }

    // ... 原有方法不变
}

步骤2:网关解析JWT,将用户姓名透传给模板微服务

你的网关已经有JwtRequestFilter处理Token认证,现在需要在Filter里把解析出来的用户姓名放到请求头里,这样下游的模板服务就能拿到。

修改JwtRequestFilter类,添加透传逻辑

@Component
public class JwtRequestFilter extends OncePerRequestFilter{
    @Autowired
    private MyUserDetailsService myUserDetailsService;
    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String authorizationHeader =request.getHeader("Authorization");
        String username = null;
        String jwt = null;
        String userFullName = null;

        if(authorizationHeader !=null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username= jwtUtil.extractUsername(jwt);
            // 从JWT里直接提取用户姓名
            userFullName = jwtUtil.extractFullName(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication()==null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                
                // 将用户姓名放入请求头,传递给下游服务
                request.setAttribute("X-USER-FULLNAME", userFullName);
                // 注意:如果是网关转发请求到模板服务,要确保这个请求头被传递(Spring Cloud Gateway默认会转发自定义头)
                ((HttpServletRequest) request).addHeader("X-USER-FULLNAME", userFullName);
            }
        }
        chain.doFilter(request,response);
    }
}

步骤3:模板微服务接收用户姓名,实现数据筛选

现在模板服务的接口不需要再接收name参数了,直接从请求头里拿用户姓名,然后筛选数据,确保用户只能拿到自己的模板。

修改模板查询接口

@GetMapping("/templates/{template_id}")
public Template retrieveTemplate(@PathVariable("template_id") int template_id,
                                 @RequestHeader("X-USER-FULLNAME") String userFullName) {
    // 校验用户信息是否存在(防止非法请求绕过网关)
    if(userFullName == null || userFullName.isBlank()) {
        throw new IllegalArgumentException("用户身份信息缺失");
    }
    return templateRepository.findByTemplateIdAndName(template_id, userFullName);
}

步骤4:(可选)如果网关通过Feign调用模板服务,配置Feign拦截器透传头

如果你的网关是通过Feign调用模板微服务,需要配置Feign拦截器,把网关里的用户姓名头传递过去:

@Component
public class FeignUserHeaderInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            String userFullName = request.getHeader("X-USER-FULLNAME");
            if (userFullName != null) {
                template.header("X-USER-FULLNAME", userFullName);
            }
        }
    }
}

额外安全保障:防止模板服务被直接调用

为了避免有人绕过网关直接调用模板服务,你可以给模板服务加一层防护:

  • 配置Spring Security,只允许网关的IP地址访问
  • 或者在模板服务里校验请求头X-USER-FULLNAME是否存在,不存在直接返回401

内容的提问来源于stack exchange,提问作者Doncarlito87

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 14:17:44