1. 程式人生 > >SpringCloud利用閘道器攔截做Token驗證(JWT方式) SpringCloud利用閘道器攔截做Token驗證(JWT方式)

SpringCloud利用閘道器攔截做Token驗證(JWT方式) SpringCloud利用閘道器攔截做Token驗證(JWT方式)

SpringCloud利用閘道器攔截做Token驗證(JWT方式)

2018年09月29日 15:51:50 閱讀數:23
																				<div class="tags-box space">
							<span class="label">個人分類:</span>
															<a class="tag-link" href="https://blog.csdn.net/qq_34707991/article/category/6696592" target="_blank">常見程式設計方法																</a>
						</div>
																							</div>
			<div class="operating">
													</div>
		</div>
	</div>
</div>
<article>
	<div id="article_content" class="article_content clearfix csdn-tracking-statistics" data-pid="blog" data-mod="popu_307" data-dsm="post">
							            <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-bb1edad192.css">
					<div class="htmledit_views">
            <p>背景:前後分離</p>

關於JWT的內容,請看以下連結,主要看它的原理,以及缺點!

https://blog.csdn.net/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. import com.movitech.commons.entity.UserBean;
  3. import com.movitech.commons.utils.CommonConstants;
  4. import com.movitech.user.service.AuthService;
  5. import io.jsonwebtoken.Jwts;
  6. import io.jsonwebtoken.SignatureAlgorithm;
  7. import org.joda.time.DateTime;
  8. import org.springframework.stereotype.Service;
  9. import java.util.Base64;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. /**
  13. * 使用者身份驗證Service
  14. */
  15. @ Service(value = "authService")
  16. public class AuthServiceImpl implements AuthService {
  17. @ Override
  18. public String generateJwt( String userName, String userPassword, UserBean userBean) {
  19. // Base64編碼後的secretKey
  20. byte[] secretKey = Base64.getEncoder().encode( CommonConstants. SECURITY_KEY.getBytes());
  21. // 設定失效時間
  22. DateTime expirationDate = new DateTime().plusDays( 1);
  23. //DateTime expirationDate = new DateTime().plusMinutes(30);
  24. // Claims是需要儲存到token中的資訊,可以自定義,需要存什麼就放什麼,會儲存到token的payload中
  25. Map< String, Object> claims = new HashMap<>();
  26. // 使用者角色
  27. claims.put( "role", "user");
  28. // 使用者名稱
  29. claims.put( "userName", userName);
  30. claims.put( CommonConstants. USER_ID, userBean.getId());
  31. claims.put( "uuid", UUID.randomUUID(). toString());
  32. String compactJws = Jwts.builder()
  33. // 設定subject,一般是使用者的唯一標識,比如使用者物件的ID,使用者名稱等,目前設定的是userCode
  34. .setSubject(userName)
  35. // 設定失效時間
  36. .setExpiration(expirationDate.toDate())
  37. .addClaims(claims)
  38. // 加密演算法是HS512,加密解密統一就可以
  39. .signWith( SignatureAlgorithm. HS512, secretKey)
  40. .compact();
  41. return compactJws;
  42. }
  43. }

以上常量類和pojo此處省略。。。。

2,閘道器攔截器解密鑑權:


  
  1. package com.movitech.gateway.filter;
  2. import com.movitech.commons.dto.ErrorResponseMap;
  3. import com.movitech.commons.enums.ErrorCode;
  4. import com.movitech.commons.utils.CommonConstants;
  5. import com.movitech.commons.utils.JsonUtil;
  6. import com.movitech.commons.utils.ResponseUtil;
  7. import com.netflix.zuul.ZuulFilter;
  8. import com.netflix.zuul.context.RequestContext;
  9. import io.jsonwebtoken.*;
  10. import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
  11. import org.springframework.http.HttpHeaders;
  12. import org.springframework.http.HttpMethod;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.util.StringUtils;
  15. import javax.servlet.http.HttpServletRequest;
  16. import java.util.Base64;
  17. @Component
  18. public class SecurityFilter extends ZuulFilter {
  19. @Override
  20. public String filterType() {
  21. return FilterConstants.PRE_TYPE;
  22. }
  23. @Override
  24. public int filterOrder() {
  25. return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
  26. }
  27. @Override
  28. public boolean shouldFilter() {
  29. RequestContext ctx = RequestContext.getCurrentContext();
  30. HttpServletRequest request = ctx.getRequest();
  31. if (request.getRequestURL().toString().contains( "loginInfo") || request.getRequestURL().toString().contains( "info")) {
  32. return false;
  33. }
  34. // TODO
  35. return true;
  36. }
  37. @Override
  38. public Object run() {
  39. RequestContext ctx = RequestContext.getCurrentContext();
  40. HttpServletRequest request = ctx.getRequest();
  41. final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
  42. if (HttpMethod.OPTIONS.name().equals(request.getMethod())) {
  43. return null;
  44. } else {
  45. if (StringUtils.isEmpty(authorizationHeader) || !authorizationHeader.startsWith(CommonConstants.BEARER)) {
  46. // Missing or invalid Authorization header
  47. ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse( null, "Missing or invalid Authorization header!",
  48. ErrorCode.INVALID_AUTHORIZATION_HEADER, request, null);
  49. denyAccess(ctx,errorResponseMap);
  50. return JsonUtil.serializeToString(errorResponseMap);
  51. }
  52. final String token = authorizationHeader.substring( 7);
  53. try {
  54. byte[] secretKey = Base64.getEncoder().encode(CommonConstants.SECURITY_KEY.getBytes());
  55. Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
  56. if (claims != null) {
  57. //獲取redis裡面資料的存活時間
  58. Long expirationDate = stringRedisTemplate.getExpire( "token",TimeUnit.SECONDS);
  59. //如果還剩餘5分鐘,重置redis裡面資料的存活時間
  60. if(expirationDate > 300){
  61. stringRedisTemplate.expire( "token", 1800,TimeUnit.SECONDS);
  62. } else {
  63. ErrorResponseMap errorResponseMap = new ErrorResponseMap();
  64. Error error = new Error( null, "Token expired!", "", 1003, "");
  65. errorResponseMap.setSuccess( false);
  66. errorResponseMap.setMessage( null);
  67. errorResponseMap.setError(error);
  68. denyAccess(ctx,errorResponseMap);
  69. return JsonUtil.serializeToString(errorResponseMap);
  70. }
  71. String userName = (String) claims. get(CommonConstants.USER_CODE);
  72. Integer userId = (Integer) claims. get(CommonConstants.USER_ID);
  73. ctx.addZuulRequestHeader(CommonConstants.USER_CODE, userName);
  74. ctx.addZuulRequestHeader(CommonConstants.USER_ID, String.valueOf(userId));
  75. }
  76. } catch (MalformedJwtException ex) {
  77. ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse( null, "Invalid token!",
  78. ErrorCode.INVALID_AUTHORIZATION_HEADER, request, ex);
  79. denyAccess(ctx,errorResponseMap);
  80. return JsonUtil.serializeToString(errorResponseMap);
  81. } catch (SignatureException ex) {
  82. ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse( null, "Token Signature error!",
  83. ErrorCode.SIGNATURE_EXCEPTION, request, ex);
  84. denyAccess(ctx,errorResponseMap);
  85. return JsonUtil.serializeToString(errorResponseMap);
  86. } catch (ExpiredJwtException ex) {
  87. ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse( null, "Token expired!",
  88. ErrorCode.EXPIRED_JWT_EXCEPTION, request, ex);
  89. denyAccess(ctx,errorResponseMap);
  90. return JsonUtil.serializeToString(errorResponseMap);
  91. }
  92. }
  93. return null;
  94. }
  95. private void denyAccess(RequestContext ctx, ErrorResponseMap authResult) {
  96. String result = JsonUtil.serializeToString(authResult);
  97. ctx.setSendZuulResponse( false);
  98. ctx.setResponseStatusCode( 401);
  99. try {
  100. ctx.getResponse().getWriter().write(result);
  101. } catch (Exception e){}
  102. }
  103. }

以上程式碼是核心程式碼,下面是自己專案中涉及到的異常包裝類,以及util類,可以不管下面的,直接去封裝

 

以上涉及到的類:

(1)ErrorResponseMap


  
  1. package com .movitech .commons .dto;
  2. import com .fasterxml .jackson .annotation .JsonProperty;
  3. import com .movitech .commons .exception .Error;
  4. import lombok .Getter;
  5. import lombok .Setter;
  6. @ Getter
  7. @Setter
  8. public class ErrorResponseMap extends ResponseMap {
  9. @ JsonProperty( value = "error")
  10. private Error error;
  11. @ JsonProperty( value = "stackTrace")
  12. private String stackTrace;
  13. }

(1.1)Error


  
  1. package com.movitech.commons.exception;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Getter;
  4. import lombok.NoArgsConstructor;
  5. import lombok.Setter;
  6. @Getter
  7. @Setter
  8. @NoArgsConstructor
  9. @AllArgsConstructor
  10. public class Error {
  11. // 標準的 Http status code
  12. private Integer httpStatusCode;
  13. // 自定義的錯誤說明
  14. private String errorMsg;
  15. // 異常資訊
  16. private String exceptionMsg;
  17. // 自定義的錯誤程式碼
  18. private Integer errorCode;
  19. // 異常的類名
  20. private String exceptionClassName;
  21. }

(2)ErrorCode


  
  1. package com.movitech.commons.enums;
  2. /**
  3. * 自定義的錯誤程式碼的列舉
  4. */
  5. public enum ErrorCode {
  6. // Token 簽名錯誤
  7. SIGNATURE_EXCEPTION( 1000),
  8. // Token 過期
  9. EXPIRED_JWT_EXCEPTION( 1001),
  10. // 無效的Authorization header
  11. INVALID_AUTHORIZATION_HEADER( 1002);
  12. private Integer errorCode;
  13. ErrorCode(Integer errorCode) {
  14. this.errorCode = errorCode;
  15. }
  16. @Override
  17. public String toString() {
  18. return errorCode.toString();