SpringCloud中Gateway實現鑒權的方法
一、JWT 實現微服務鑒權
JWT一般用於實現單點登錄。單點登錄:如騰訊下的遊戲有很多,包括lol,飛車等,在qq遊戲對戰平臺上登錄一次,然後這些不同的平臺都可以直接登陸進去瞭,這就是單點登錄的使用場景。JWT就是實現單點登錄的一種技術,其他的還有oath2等。
1 什麼是微服務鑒權
我們之前已經搭建過瞭網關,使用網關在網關系統中比較適合進行權限校驗。
那麼我們可以采用JWT的方式來實現鑒權校驗。
2.代碼實現
思路分析
1. 用戶進入網關開始登陸,網關過濾器進行判斷,如果是登錄,則路由到後臺管理微服務進行登錄
2. 用戶登錄成功,後臺管理微服務簽發JWT TOKEN信息返回給用戶
3. 用戶再次進入網關開始訪問,網關過濾器接收用戶攜帶的TOKEN
4. 網關過濾器解析TOKEN ,判斷是否有權限,如果有,則放行,如果沒有則返回未認證錯誤
簽發token
(1)創建類: JwtUtil
package com.mye.nacosprovider.jwt; import com.alibaba.fastjson.JSON; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.*; @Component public class JwtUtil { //加密 解密時的密鑰 用來生成key public static final String JWT_KEY = "IT1995"; /** * 生成加密後的秘鑰 secretKey * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } public static String createJWT(String id, String subject, long ttlMillis){ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經將這部分內容封裝好瞭。 long nowMillis = System.currentTimeMillis();//生成JWT的時間 Date now = new Date(nowMillis); SecretKey key = generalKey();//生成簽名的時候使用的秘鑰secret,這個方法本地封裝瞭的,一般可以從本地配置文件中讀取,切記這個秘鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt瞭。 JwtBuilder builder = Jwts.builder() //這裡其實就是new一個JwtBuilder,設置jwt的body // .setClaims(claims) //如果有私有聲明,一定要先設置這個自己創建的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值之後,就是覆蓋瞭那些標準的聲明的 .setId(id) //設置jti(JWT ID):是JWT的唯一標識,根據業務需要,這個可以設置為一個不重復的值,主要用來作為一次性token,從而回避重放攻擊。 .setIssuedAt(now) //iat: jwt的簽發時間 .setSubject(subject) //sub(Subject):代表這個JWT的主體,即它的所有人,這個是一個json格式的字符串,可以存放什麼userid,roldid之類的,作為什麼用戶的唯一標志。 .signWith(signatureAlgorithm, key);//設置簽名使用的簽名算法和簽名使用的秘鑰 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); //設置過期時間 } return builder.compact(); //就開始壓縮為xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx這樣的jwt } public static Claims parseJWT(String jwt){ SecretKey key = generalKey(); //簽名秘鑰,和生成的簽名的秘鑰一模一樣 Claims claims = Jwts.parser() //得到DefaultJwtParser .setSigningKey(key) //設置簽名的秘鑰 .parseClaimsJws(jwt).getBody();//設置需要解析的jwt return claims; } public static void main(String[] args){ Map<String, Object> user = new HashMap<>(); user.put("username", "it1995"); user.put("password", "123456"); String jwt = createJWT(UUID.randomUUID().toString(), JSON.toJSONString(user), 3600 * 24); System.out.println("加密後:" + jwt); //解密 Claims claims = parseJWT(jwt); System.out.println("解密後:" + claims.getSubject()); } }
(2)修改login方法,用戶登錄成功 則 簽發TOKEN
@PostMapping("/login") public String login(@RequestBody User user){ //在redis中根據用戶名查找密碼 String password = redisTemplate.opsForValue().get(user.getUsername()); System.out.println(password); boolean checkResult = BCrypt.checkpw(user.getPassword(), password); if (checkResult){ Map<String, String> info = new HashMap<>(); info.put("username", user.getUsername()); String token = JwtUtil.createJWT(UUID.randomUUID().toString(), user.getUsername(), 3600L*1000); info.put("token",token); return JSONUtil.toJsonStr(info); }else { return "登錄失敗"; } }
(3) 測試
網關過濾器驗證token
(1)網關模塊添加依賴
<!--鑒權--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
(2)創建JWTUtil類
package com.mye.nacosprovider.jwt; import com.alibaba.fastjson.JSON; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.*; @Component public class JwtUtil { //加密 解密時的密鑰 用來生成key public static final String JWT_KEY = "IT1995"; /** * 生成加密後的秘鑰 secretKey * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } public static String createJWT(String id, String subject, long ttlMillis){ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經將這部分內容封裝好瞭。 long nowMillis = System.currentTimeMillis();//生成JWT的時間 Date now = new Date(nowMillis); SecretKey key = generalKey();//生成簽名的時候使用的秘鑰secret,這個方法本地封裝瞭的,一般可以從本地配置文件中讀取,切記這個秘鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt瞭。 JwtBuilder builder = Jwts.builder() //這裡其實就是new一個JwtBuilder,設置jwt的body // .setClaims(claims) //如果有私有聲明,一定要先設置這個自己創建的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值之後,就是覆蓋瞭那些標準的聲明的 .setId(id) //設置jti(JWT ID):是JWT的唯一標識,根據業務需要,這個可以設置為一個不重復的值,主要用來作為一次性token,從而回避重放攻擊。 .setIssuedAt(now) //iat: jwt的簽發時間 .setSubject(subject) //sub(Subject):代表這個JWT的主體,即它的所有人,這個是一個json格式的字符串,可以存放什麼userid,roldid之類的,作為什麼用戶的唯一標志。 .signWith(signatureAlgorithm, key);//設置簽名使用的簽名算法和簽名使用的秘鑰 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); //設置過期時間 } return builder.compact(); //就開始壓縮為xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx這樣的jwt } public static Claims parseJWT(String jwt){ SecretKey key = generalKey(); //簽名秘鑰,和生成的簽名的秘鑰一模一樣 Claims claims = Jwts.parser() //得到DefaultJwtParser .setSigningKey(key) //設置簽名的秘鑰 .parseClaimsJws(jwt).getBody();//設置需要解析的jwt return claims; } public static void main(String[] args){ Map<String, Object> user = new HashMap<>(); user.put("username", "it1995"); user.put("password", "123456"); String jwt = createJWT(UUID.randomUUID().toString(), JSON.toJSONString(user), 3600 * 24); System.out.println("加密後:" + jwt); //解密 Claims claims = parseJWT(jwt); System.out.println("解密後:" + claims.getSubject()); } }
(3)創建過濾器,用於token驗證
/** * 鑒權過濾器 驗證token */ @Component public class AuthorizeFilter implements GlobalFilter, Ordered { private static final String AUTHORIZE_TOKEN = "token"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //1. 獲取請求 ServerHttpRequest request = exchange.getRequest(); //2. 則獲取響應 ServerHttpResponse response = exchange.getResponse(); //3. 如果是登錄請求則放行 if (request.getURI().getPath().contains("/admin/login")) { return chain.filter(exchange); } //4. 獲取請求頭 HttpHeaders headers = request.getHeaders(); //5. 請求頭中獲取令牌 String token = headers.getFirst(AUTHORIZE_TOKEN); //6. 判斷請求頭中是否有令牌 if (StringUtils.isEmpty(token)) { //7. 響應中放入返回的狀態嗎, 沒有權限訪問 response.setStatusCode(HttpStatus.UNAUTHORIZED); //8. 返回 return response.setComplete(); } //9. 如果請求頭中有令牌則解析令牌 try { JwtUtil.parseJWT(token); } catch (Exception e) { e.printStackTrace(); //10. 解析jwt令牌出錯, 說明令牌過期或者偽造等不合法情況出現 response.setStatusCode(HttpStatus.UNAUTHORIZED); //11. 返回 return response.setComplete(); } //12. 放行 return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
(4)測試:
首先進行登錄測試
在進行鑒權測試
到此這篇關於SpringCloud中Gateway實現鑒權的方法的文章就介紹到這瞭,更多相關SpringCloud Gateway鑒權內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 如何使用JJWT及JWT講解和工具類
- 一文瞭解什麼是JWT
- Spring Cloud Gateway 使用JWT工具類做用戶登錄校驗功能
- JWT 設置token過期時間無效的解決
- 如何利用jwt來保護你的接口服務