基于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
相关产品推荐
相关产品推荐

