SpringCloud利用网关拦截做Token验证(JWT方式)

本文介绍了如何在SpringCloud架构中,利用网关拦截器进行JWT Token验证。首先,前端发送用户名和密码,网关拦截器在启动时加载。首次登录后,生成加密的Token并存储在Redis中,设置30分钟有效期。后续请求携带Token,拦截器解密鉴权,若Token有效则更新Redis中Token的过期时间。核心代码涉及网关拦截器的解密鉴权和登录接口的加密过程。

关于JWT的内容,请看以下链接,主要看它的原理,以及缺点!

https://blog.youkuaiyun.com/memmsc/article/details/78122931

步骤1:前端传userName+password到后端,后端为springcloud架构,经过网关的拦截器拦截请求,拦截器在项目启动的时候@Component进行加载。

步骤2:如果是第一次登陆,放行,进入JWT的加密生成Token阶段(还可以写入登陆用户的其他信息放在JWTmap中,之后可以利用Token来获取该用户信息),加密token需要一个随机数作为加密字段,将token的失效时间设置为一天,并且放到reids里面,设置该redis里面的token过期时间为30分钟,最后将Token返回给前端。

步骤3:以后任何的请求都带Token到后端去请求。

步骤4:拦截到非登陆请求,进行解密,鉴权,如果鉴权通过,更新redis里面token字段的失效时间,如果还有5分钟失效,再设置还有30分钟,目的就是让密码的过期时间变的活跃。

大致就是以上的过程,核心代码主要在网关拦截器解密鉴权和登陆接口的加密两部分

0,controller层的将得到的token做保存redis和设置过期时间的操作

  1. compactJws = authService.generateJwt(username, password, userBean);

  2. //将token存在redis里

  3. stringRedisTemplate.opsForValue().set("token", compactJws);

  4. //设置redis里面的数据失效时间为半小时

  5. stringRedisTemplate.expire("token",1800,TimeUnit.SECONDS);

 

 

1,登陆接口的加密:

 
  1. package com.movitech.user.service.imp;

  2.  
  3. import com.movitech.commons.entity.UserBean;

  4. import com.movitech.commons.utils.CommonConstants;

  5. import com.movitech.user.service.AuthService;

  6. import io.jsonwebtoken.Jwts;

  7. import io.jsonwebtoken.SignatureAlgorithm;

  8. import org.joda.time.DateTime;

  9. import org.springframework.stereotype.Service;

  10.  
  11. import java.util.Base64;

  12. import java.util.HashMap;

  13. import java.util.Map;

  14.  
  15. /**

  16. * 用户身份验证Service

  17. */

  18. @Service(value = "authService")

  19. public class AuthServiceImpl implements AuthService {

  20. @Override

  21. public String generateJwt(String userName, String userPassword, UserBean userBean) {

  22. // Base64编码后的secretKey

  23. byte[] secretKey = Base64.getEncoder().encode(CommonConstants.SECURITY_KEY.getBytes());

  24. // 设置失效时间

  25. DateTime expirationDate = new DateTime().plusDays(1);

  26. //DateTime expirationDate = new DateTime().plusMinutes(30);

  27. // Claims是需要保存到token中的信息,可以自定义,需要存什么就放什么,会保存到token的payload中

  28. Map<String, Object> claims = new HashMap<>();

  29. // 用户角色

  30. claims.put("role", "user");

  31. // 用户名

  32. claims.put("userName", userName);

  33. claims.put(CommonConstants.USER_ID, userBean.getId());

  34. claims.put("uuid",UUID.randomUUID().toString());

  35. String compactJws = Jwts.builder()

  36. // 设置subject,一般是用户的唯一标识,比如用户对象的ID,用户名等,目前设置的是userCode

  37. .setSubject(userName)

  38. // 设置失效时间

  39. .setExpiration(expirationDate.toDate())

  40. .addClaims(claims)

  41. // 加密算法是HS512,加密解密统一就可以

  42. .signWith(SignatureAlgorithm.HS512, secretKey)

  43. .compact();

  44. return compactJws;

  45. }

  46.  
  47. }

  • 1

以上常量类和pojo此处省略。。。。

2,网关拦截器解密鉴权:

 
  1. package com.movitech.gateway.filter;

  2.  
  3. import com.movitech.commons.dto.ErrorResponseMap;

  4. import com.movitech.commons.enums.ErrorCode;

  5. import com.movitech.commons.utils.CommonConstants;

  6. import com.movitech.commons.utils.JsonUtil;

  7. import com.movitech.commons.utils.ResponseUtil;

  8. import com.netflix.zuul.ZuulFilter;

  9. import com.netflix.zuul.context.RequestContext;

  10. import io.jsonwebtoken.*;

  11. import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;

  12. import org.springframework.http.HttpHeaders;

  13. import org.springframework.http.HttpMethod;

  14. import org.springframework.stereotype.Component;

  15. import org.springframework.util.StringUtils;

  16.  
  17. import javax.servlet.http.HttpServletRequest;

  18. import java.util.Base64;

  19.  
  20. @Component

  21. public class SecurityFilter extends ZuulFilter {

  22. @Override

  23. public String filterType() {

  24. return FilterConstants.PRE_TYPE;

  25. }

  26.  
  27. @Override

  28. public int filterOrder() {

  29. return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;

  30. }

  31.  
  32. @Override

  33. public boolean shouldFilter() {

  34. RequestContext ctx = RequestContext.getCurrentContext();

  35. HttpServletRequest request = ctx.getRequest();

  36. if (request.getRequestURL().toString().contains("loginInfo") || request.getRequestURL().toString().contains("info")) {

  37. return false;

  38. }

  39. // TODO

  40. return true;

  41. }

  42.  
  43. @Override

  44. public Object run() {

  45. RequestContext ctx = RequestContext.getCurrentContext();

  46. HttpServletRequest request = ctx.getRequest();

  47. final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);

  48.  
  49. if (HttpMethod.OPTIONS.name().equals(request.getMethod())) {

  50. return null;

  51. } else {

  52. if (StringUtils.isEmpty(authorizationHeader) || !authorizationHeader.startsWith(CommonConstants.BEARER)) {

  53. // Missing or invalid Authorization header

  54. ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse(null, "Missing or invalid Authorization header!",

  55. ErrorCode.INVALID_AUTHORIZATION_HEADER, request, null);

  56. denyAccess(ctx,errorResponseMap);

  57. return JsonUtil.serializeToString(errorResponseMap);

  58. }

  59. final String token = authorizationHeader.substring(7);

  60. try {

  61. byte[] secretKey = Base64.getEncoder().encode(CommonConstants.SECURITY_KEY.getBytes());

  62. Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();

  63. if (claims != null) {

  64. //获取redis里面数据的存活时间

  65. Long expirationDate = stringRedisTemplate.getExpire("token",TimeUnit.SECONDS);

  66. //如果还剩余5分钟,重置redis里面数据的存活时间

  67. if(expirationDate > 300){

  68. stringRedisTemplate.expire("token",1800,TimeUnit.SECONDS);

  69. }else {

  70. ErrorResponseMap errorResponseMap = new ErrorResponseMap();

  71. Error error = new Error(null, "Token expired!", "", 1003,"");

  72. errorResponseMap.setSuccess(false);

  73. errorResponseMap.setMessage(null);

  74. errorResponseMap.setError(error);

  75. denyAccess(ctx,errorResponseMap);

  76. return JsonUtil.serializeToString(errorResponseMap);

  77. }

  78. String userName = (String) claims.get(CommonConstants.USER_CODE);

  79. Integer userId = (Integer) claims.get(CommonConstants.USER_ID);

  80. ctx.addZuulRequestHeader(CommonConstants.USER_CODE, userName);

  81. ctx.addZuulRequestHeader(CommonConstants.USER_ID, String.valueOf(userId));

  82. }

  83. } catch (MalformedJwtException ex) {

  84. ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse(null, "Invalid token!",

  85. ErrorCode.INVALID_AUTHORIZATION_HEADER, request, ex);

  86. denyAccess(ctx,errorResponseMap);

  87. return JsonUtil.serializeToString(errorResponseMap);

  88. } catch (SignatureException ex) {

  89. ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse(null, "Token Signature error!",

  90. ErrorCode.SIGNATURE_EXCEPTION, request, ex);

  91. denyAccess(ctx,errorResponseMap);

  92. return JsonUtil.serializeToString(errorResponseMap);

  93. } catch (ExpiredJwtException ex) {

  94. ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse(null, "Token expired!",

  95. ErrorCode.EXPIRED_JWT_EXCEPTION, request, ex);

  96. denyAccess(ctx,errorResponseMap);

  97. return JsonUtil.serializeToString(errorResponseMap);

  98. }

  99. }

  100. return null;

  101. }

  102.  
  103. private void denyAccess(RequestContext ctx, ErrorResponseMap authResult) {

  104. String result = JsonUtil.serializeToString(authResult);

  105. ctx.setSendZuulResponse(false);

  106. ctx.setResponseStatusCode(401);

  107. try {

  108. ctx.getResponse().getWriter().write(result);

  109. }catch (Exception e){}

  110. }

  111. }

  • 1

以上代码是核心代码,下面是自己项目中涉及到的异常包装类,以及util类,可以不管下面的,直接去封装

 

以上涉及到的类:

(1)ErrorResponseMap

 
  1. package com.movitech.commons.dto;

  2.  
  3. import com.fasterxml.jackson.annotation.JsonProperty;

  4. import com.movitech.commons.exception.Error;

  5. import lombok.Getter;

  6. import lombok.Setter;

  7.  
  8. @Getter

  9. @Setter

  10. public class ErrorResponseMap extends ResponseMap {

  11. @JsonProperty(value = "error")

  12. private Error error;

  13. @JsonProperty(value = "stackTrace")

  14. private String stackTrace;

  15. }

  • 1

(1.1)Error

 
  1. package com.movitech.commons.exception;

  2.  
  3. import lombok.AllArgsConstructor;

  4. import lombok.Getter;

  5. import lombok.NoArgsConstructor;

  6. import lombok.Setter;

  7.  
  8. @Getter

  9. @Setter

  10. @NoArgsConstructor

  11. @AllArgsConstructor

  12. public class Error {

  13. // 标准的 Http status code

  14. private Integer httpStatusCode;

  15. // 自定义的错误说明

  16. private String errorMsg;

  17. // 异常信息

  18. private String exceptionMsg;

  19. // 自定义的错误代码

  20. private Integer errorCode;

  21. // 异常的类名

  22. private String exceptionClassName;

  23. }

  • 1

(2)ErrorCode

 
  1. package com.movitech.commons.enums;

  2.  
  3. /**

  4. * 自定义的错误代码的枚举

  5. */

  6. public enum ErrorCode {

  7. // Token 签名错误

  8. SIGNATURE_EXCEPTION(1000),

  9. // Token 过期

  10. EXPIRED_JWT_EXCEPTION(1001),

  11. // 无效的Authorization header

  12. INVALID_AUTHORIZATION_HEADER(1002);

  13. private Integer errorCode;

  14. ErrorCode(Integer errorCode) {

  15. this.errorCode = errorCode;

  16. }

  17.  
  18. @Override

  19. public String toString() {

  20. return errorCode.toString();

  21. }

  22.  
  23. public Integer value() {

  24. return errorCode;

  25. }

  26. }

  • 1

(3)JsonUtil

 
  1. package com.movitech.commons.utils;

  2.  
  3. import com.fasterxml.jackson.core.JsonProcessingException;

  4. import com.fasterxml.jackson.databind.*;

  5. import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

  6. import org.springframework.stereotype.Component;

  7. import org.springframework.util.StringUtils;

  8.  
  9. import java.io.IOException;

  10.  
  11. /**

  12. * Json序列化和反序列化

  13. */

  14. @Component

  15. public class JsonUtil

  16. {

  17. public static ObjectMapper mapper;

  18. /* static {

  19. dao = new ObjectMapper();

  20. dao.configure(SerializationFeature.WRAP_ROOT_VALUE,true);

  21. dao.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);

  22. }*/

  23.  
  24. public JsonUtil(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {

  25. mapper = jackson2ObjectMapperBuilder.build();

  26. }

  27.  
  28. public static String serializeToString(Object object) {

  29. return serializeToString(object,false);

  30. }

  31.  
  32. public static String serializeToString(Object object,Boolean rootValueState) {

  33. SerializationConfig serializationConfig = mapper.getSerializationConfig();

  34. mapper.configure(SerializationFeature.WRAP_ROOT_VALUE,rootValueState);

  35. if (object != null) {

  36. try {

  37. return mapper.writeValueAsString(object);

  38. } catch (JsonProcessingException e) {

  39. e.printStackTrace();

  40. } finally {

  41. mapper.setConfig(serializationConfig);

  42. }

  43. }

  44. return "";

  45. }

  46.  
  47. public static byte[] serializeToBytes(Object object) {

  48. if (object != null) {

  49. try {

  50. return mapper.writeValueAsBytes(object);

  51. } catch (JsonProcessingException e) {

  52. e.printStackTrace();

  53. }

  54. }

  55. return null;

  56. }

  57.  
  58. /**

  59. * 反序列化Json数据

  60. * @param jsonData Json数据字符串

  61. * @param valueType 反序列化的类型

  62. * @param rootValueState 是否解析Json root name

  63. * @param <T>

  64. * @return 反序列化后的POJO

  65. */

  66. public static <T> T deserialize(String jsonData,Class<T> valueType,Boolean rootValueState) {

  67. if (StringUtils.isEmpty(jsonData) || rootValueState == null) {

  68. return null;

  69. }

  70. DeserializationConfig deserializationConfig = mapper.getDeserializationConfig();

  71. mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE,rootValueState);

  72. try {

  73. return mapper.readValue(jsonData, valueType);

  74. } catch (IOException e) {

  75. e.printStackTrace();

  76. } finally {

  77. mapper.setConfig(deserializationConfig);

  78. }

  79. return null;

  80. }

  81.  
  82. /**

  83. * 反序列化Json数据,默认解析Json root name

  84. * @param jsonData Json数据字符串

  85. * @param valueType 反序列化的类型

  86. * @param <T>

  87. * @return 反序列化后的POJO

  88. */

  89. public static <T> T deserialize(String jsonData,Class<T> valueType) {

  90. return deserialize(jsonData, valueType, true);

  91. }

  92.  
  93. public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {

  94. return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);

  95. }

  96.  
  97. /**

  98. * 用Json数据中的key获取对应的value

  99. * @param jsonString json数据字符串

  100. * @param field 需要取值的字段

  101. * @param rootValueState 是否解析Json root name

  102. * @return 字段对应的值

  103. */

  104. public static String getValue(String jsonString,String field,Boolean rootValueState) {

  105. JsonNode node = getJsonNode(jsonString,field,rootValueState);

  106. return node == null ? "" : node.toString();

  107. }

  108.  
  109. /**

  110. * 用Json数据中的key获取对应的value, 默认解析Json root name

  111. * @param jsonString json数据字符串

  112. * @param field 需要取值的字段

  113. * @return 字段对应的值

  114. */

  115. public static String getValue(String jsonString,String field) {

  116. return getValue(jsonString,field,true);

  117. }

  118.  
  119. /**

  120. * 用Json数据中的key获取对应的value

  121. * @param jsonString json数据字符串

  122. * @param field 需要取值的字段

  123. * @param rootValueState 是否解析Json root name

  124. * @return 字段对应的值

  125. */

  126. public static JsonNode getJsonNode(String jsonString, String field, Boolean rootValueState) {

  127. if (StringUtils.isEmpty(jsonString) || StringUtils.isEmpty(field) || rootValueState == null) {

  128. return null;

  129. }

  130. // 准备工作 传入vo请参照第一篇里面的实体。此处不再重新贴上代码 浪费大家时间

  131. JsonNode node = null;// 这里的JsonNode和XML里面的Node很像

  132. // 默认的反序列化配置

  133. DeserializationConfig deserializationConfig = mapper.getDeserializationConfig();

  134. mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE,rootValueState);

  135. try {

  136. node = mapper.readTree(jsonString);

  137. } catch (IOException e) {

  138. e.printStackTrace();

  139. return null;

  140. } finally {

  141. mapper.setConfig(deserializationConfig);

  142. }

  143. return node.get(field) == null ? null : node.get(field);

  144. }

  145. }

  • 1

(4)ResponseUtil

 
  1. package com.movitech.commons.utils;

  2.  
  3. import com.movitech.commons.dto.ErrorResponseMap;

  4. import com.movitech.commons.enums.ErrorCode;

  5. import com.movitech.commons.exception.Error;

  6. import org.springframework.http.HttpStatus;

  7. import org.springframework.http.ResponseEntity;

  8.  
  9. import javax.servlet.http.HttpServletRequest;

  10.  
  11. public class ResponseUtil {

  12. public static ResponseEntity createResponseEntity(String errorMsg, ErrorCode errorCode, HttpServletRequest request, Throwable ex) {

  13. HttpStatus status = getStatus(request);

  14. ErrorResponseMap errorResponseMap = createErrorResponse(status, errorMsg, errorCode, request, ex);

  15. return new ResponseEntity<>(errorResponseMap, status);

  16. }

  17.  
  18. public static ErrorResponseMap createErrorResponse(HttpStatus status, String errorMsg, ErrorCode errorCode, HttpServletRequest request, Throwable ex) {

  19. if (status == null) {

  20. status = getStatus(request);

  21. }

  22. ErrorResponseMap errorResponseMap = new ErrorResponseMap();

  23. Error error = new Error(status.value(), errorMsg, ex == null ? "" : ex.getMessage(),

  24. errorCode == null ? HttpStatus.INTERNAL_SERVER_ERROR.value() : errorCode.value(), ex == null ? "" : ex.getClass().getCanonicalName());

  25. errorResponseMap.setSuccess(false);

  26. errorResponseMap.setMessage(ex.getMessage());

  27. errorResponseMap.setError(error);

  28. // String stackTrace = StringUtils.arrayToDelimitedString(ex.getStackTrace(), "hahaha");

  29. // errorResponseMap.setStackTrace(stackTrace);

  30. return errorResponseMap;

  31. }

  32.  
  33. public static HttpStatus getStatus(HttpServletRequest request) {

  34. Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");

  35. if (statusCode == null) {

  36. return HttpStatus.INTERNAL_SERVER_ERROR;

  37. }

  38. return HttpStatus.valueOf(statusCode);

  39. }

  40. }

  • 1
<think>我们正在处理一个关于SpringCloud微服务系统中登录验证返回token为undefined的问题。根据用户提供的引用信息,我们可以构建一个解决方案的思路。首先,我们需要明确问题:在微服务登录验证过程中,返回的token为undefined。可能的原因包括:1.生成token的环节出现问题,没有正确生成token。2.返回token的环节出现问题,比如在响应体中未正确设置token字段。3.网关(Zuul)或负载均衡等中间件在转发过程中丢失了token信息。4.客户端(如前端)在解析响应时出错,导致获取的token为undefined。根据引用[1],我们提到了统一响应体的结构。在微服务中,我们通常会定义统一的响应格式,如果响应体结构定义不一致,可能导致前端无法正确获取token。引用[2]提到了Zuul网关的配置,如果网关有过滤器中修改了响应体,也可能导致token丢失。引用[5]提到了使用JWT(无状态token)而不使用redis存储token的好处,所以这里我们假设使用的是JWT。因此,我们可以按照以下步骤进行排查和解决:步骤1:检查生成token的微服务-确保在用户登录成功后,服务端正确生成了token,并且将token放入响应体中。-可以使用Postman等工具直接请求登录接口,查看返回的响应体是否包含token。步骤2:检查统一响应体结构-根据引用[1],我们定义了统一响应体的结构。检查登录接口返回的响应体是否符合该结构。-例如,响应体可能包含code、message和data字段,而token可能放在data中。前端需要根据这个结构来获取token。步骤3:检查网关过滤器-引用[2]中提到了Zuul网关网关中可能设置了过滤器(如PreFilter、PostFilter)对请求和响应进行修改。-检查是否有过滤器(特别是Post过滤器)修改了响应体,导致token被移除或修改。步骤4:检查客户端代码-确认前端在接收到登录响应后,解析token的字段名是否正确(比如是否与后端返回的字段名一致)。-例如,如果后端返回的token字段名为"token",前端应该使用`response.data.token`(假设使用axios)来获取,而不是其他字段名。步骤5:检查网络请求-使用浏览器的开发者工具查看网络请求,确认登录请求的响应中是否确实包含token。下面我们针对这些步骤给出具体的解决方案:1.生成token的微服务代码示例(使用JWT):在登录验证成功后,生成token并放入统一响应体中。```java//假设我们使用如下方式生成JWTtokenimportio.jsonwebtoken.Jwts;importio.jsonwebtoken.SignatureAlgorithm;publicStringgenerateToken(Stringusername){returnJwts.builder().setSubject(username).setExpiration(newDate(System.currentTimeMillis()+EXPIRATION_TIME)).signWith(SignatureAlgorithm.HS512,SECRET).compact();}//在登录接口中@PostMapping("/login")publicResponseResultlogin(@RequestBodyUseruser){//验证用户名密码//...Stringtoken=generateToken(user.getUsername());//返回统一响应体,假设我们的统一响应体类为ResponseResult,其中包含data字段returnResponseResult.success("登录成功",token);}```注意:这里返回的ResponseResult的data字段就是token(字符串)。但也可以将token放在一个对象中,比如:```javaMap<String,String>tokenMap=newHashMap<>();tokenMap.put("token",token);returnResponseResult.success("登录成功",tokenMap);```这样前端需要从data.token中获取。2.统一响应体结构定义(参考引用[1]):确保所有微服务都使用相同的响应体结构。例如:```javapublicclassResponseResult<T>{privateintcode;privateStringmessage;privateTdata;//构造方法、getter、setter省略publicstatic<T>ResponseResult<T>success(Stringmessage,Tdata){ResponseResult<T>result=newResponseResult<>();result.setCode(200);result.setMessage(message);result.setData(data);returnresult;}}```3.检查Zuul网关的过滤器:如果使用了Zuul网关,检查是否有自定义的过滤器修改了响应体。特别是Post类型的过滤器,可能会对响应体进行重新封装。确保在过滤器中不要破坏原有的响应体结构。例如,在Zuul的Post过滤器中,我们可能会这样写:```javapublicclassPostFilterextendsZuulFilter{@OverridepublicStringfilterType(){return"post";}@OverridepublicintfilterOrder(){return10;}@OverridepublicbooleanshouldFilter(){returntrue;}@OverridepublicObjectrun(){RequestContextcontext=RequestContext.getCurrentContext();HttpServletResponseresponse=context.getResponse();//注意:这里不要修改响应体的内容,除非有必要returnnull;}}```4.前端代码示例(使用axios):```javascriptaxios.post('/api/auth/login',{username:'admin',password:'123456'}).then(response=>{//假设我们的统一响应体结构为{code,message,data}if(response.data.code===200){//如果登录接口返回的data直接是token字符串consttoken=response.data.data;//或者如果data是一个对象,里面包含token字段,则使用response.data.data.token//存储token,例如放入localStoragelocalStorage.setItem('token',token);}else{console.error('登录失败',response.data.message);}}).catch(error=>{console.error('请求失败',error);});```5.如果以上步骤都检查无误,仍然出现undefined,请使用开发者工具查看网络请求的响应内容,确认token是否确实返回。另外,引用[5]提到使用JWT而不使用redis存放token,所以这里我们默认使用JWT。如果使用redis存放token,那么返回给前端的可能是一个sessionId(或tokenId),然后前端每次请求携带该id,服务端从redis中获取用户信息。这种情况下,也要确保返回的token字段不为空。最后,如果问题仍然存在,建议在生成token的微服务中打印日志,确认token生成和返回的步骤是否正确,以及网关是否正确地转发了响应。根据以上分析,我们可以为用户提供详细的解决方案。生成相关问题:1.如何确保SpringCloud微服务间安全传递token?2.在微服务架构中,如何实现统一的认证和授权?3.如何设计一个高可用的API网关来管理微服务请求?4.JWTtoken的生成和验证SpringCloud中如何实现?5.使用Zuul网关时,如何自定义过滤器来处理认证信息?</think>### Spring Cloud微服务登录验证Token未定义(undefined)解决方案 在Spring Cloud微服务架构中,遇到登录返回`token: undefined`的问题,通常涉及以下环节的问题: #### 1. **Token生成环节验证** - **检查JWT生成逻辑**: ```java // 示例JWT生成代码 public String generateToken(UserDetails userDetails) { return Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1小时有效期 .signWith(SignatureAlgorithm.HS512, "your-secret-key") // 密钥需统一 .compact(); } ``` - 确保密钥(`your-secret-key`)在所有微服务中保持一致 - 验证`UserDetails`对象是否包含正确的用户名和权限信息 - **调试建议**: - 在认证服务添加日志:`log.info("Generated token: {}", token);` - 使用单元测试验证token生成功能 #### 2. **响应体封装检查**(参考引用[1]) - **统一响应体结构**: ```java @Data public class ResponseResult<T> { private int code; private String message; private T data; // token应放在data字段中 public static ResponseResult<String> success(String token) { ResponseResult<String> result = new ResponseResult<>(); result.setCode(200); result.setMessage("登录成功"); result.setData(token); // 确保token赋值给data字段 return result; } } ``` - 前端接收的token字段名需与`data`内的属性名一致(如`response.data.token`) #### 3. **网关(Zuul)配置验证**(参考引用[2]) - **检查网关过滤器**: ```java public class PostFilter extends ZuulFilter { @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletResponse response = ctx.getResponse(); // 确保不修改响应体 log.info("Response body: {}", ctx.getResponseBody()); return null; } } ``` - 在`post`类型过滤器中禁止修改响应体 - 启用`/routes`端点检查路由是否正确指向认证服务 - **网关配置要点**: ```properties # application.yml zuul: routes: auth-service: path: /auth/** serviceId: auth-service sensitive-headers: Cookie,Set-Cookie # 允许Authorization头传递 ``` #### 4. **跨服务传递问题** - **Header传递验证**: - 使用Feign拦截器确保token传递: ```java public class FeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String token = attributes.getRequest().getHeader("Authorization"); template.header("Authorization", token); } } ``` #### 5. **前端交互调试** - **Axios请求示例**: ```javascript axios.post('/api/login', {username, password}) .then(response => { // 检查响应结构是否匹配 console.log(response.data); if (response.data && response.data.data) { const token = response.data.data.token; // 字段路径需一致 localStorage.setItem('token', token); } }) ``` - **关键检查点**: - 浏览器开发者工具检查Network响应体 - 确认CORS配置允许`Authorization`头 #### 6. **日志与监控** - 启用Spring Boot Actuator监控端点 - 分布式日志追踪(ELK/Sleuth) - 认证服务添加DEBUG日志: ```properties logging.level.org.springframework.security=DEBUG ``` ### 常见问题排查表 | 问题根源 | 现象 | 解决方案 | |-------------------------|-------------------------------|------------------------------| | 响应体字段名不一致 | 前端收到data但无token字段 | 统一DTO字段命名 | | 网关修改响应体 | 网关日志显示修改过响应 | 禁用网关的响应修改逻辑 | | JWT密钥不匹配 | 其他服务验证token失败 | 统一配置密钥 | | CORS配置问题 | 浏览器控制台显示CORS错误 | 配置`@CrossOrigin` | | Feign调用丢失Header | 下游服务收不到token | 实现Feign请求拦截器 | ### 推荐调试步骤 1. **隔离测试**:直接访问认证服务(跳过网关) 2. **日志追踪**:从认证服务->网关->前端逐层检查日志 3. **单元测试**:验证`generateToken()`方法输出 4. **Postman验证**:手动检查登录接口响应结构 > 根据引用[5]的建议,在无状态架构中优先使用JWT而非Redis存储token,避免将无状态服务退化为有状态服务[^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值