SpringCloud:Gateway之鉴权

x33g5p2x  于2021-11-03 转载在 Spring  
字(6.9k)|赞(0)|评价(0)|浏览(650)

一、JWT 实现微服务鉴权

JWT一般用于实现单点登录。单点登录:如腾讯下的游戏有很多,包括lol,飞车等,在qq游戏对战平台上登录一次,然后这些不同的平台都可以直接登陆进去了,这就是单点登录的使用场景。JWT就是实现单点登录的一种技术,其他的还有oath2等。

1 什么是微服务鉴权

我们之前已经搭建过了网关,使用网关在网关系统中比较适合进行权限校验。

那么我们可以采用JWT的方式来实现鉴权校验。

2.代码实现

思路分析

  1. 1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
  2. 2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
  3. 3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
  4. 4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
签发token

(1)创建类: JwtUtil

  1. package com.mye.nacosprovider.jwt;
  2. import com.alibaba.fastjson.JSON;
  3. import io.jsonwebtoken.Claims;
  4. import io.jsonwebtoken.JwtBuilder;
  5. import io.jsonwebtoken.Jwts;
  6. import io.jsonwebtoken.SignatureAlgorithm;
  7. import org.springframework.stereotype.Component;
  8. import javax.crypto.SecretKey;
  9. import javax.crypto.spec.SecretKeySpec;
  10. import java.util.Base64;
  11. import java.util.Date;
  12. import java.util.*;
  13. @Component
  14. public class JwtUtil {
  15. //加密 解密时的密钥 用来生成key
  16. public static final String JWT_KEY = "IT1995";
  17. /** * 生成加密后的秘钥 secretKey * @return */
  18. public static SecretKey generalKey() {
  19. byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
  20. SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
  21. return key;
  22. }
  23. public static String createJWT(String id, String subject, long ttlMillis){
  24. SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
  25. long nowMillis = System.currentTimeMillis();//生成JWT的时间
  26. Date now = new Date(nowMillis);
  27. SecretKey key = generalKey();//生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
  28. JwtBuilder builder = Jwts.builder() //这里其实就是new一个JwtBuilder,设置jwt的body
  29. // .setClaims(claims) //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
  30. .setId(id) //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
  31. .setIssuedAt(now) //iat: jwt的签发时间
  32. .setSubject(subject) //sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
  33. .signWith(signatureAlgorithm, key);//设置签名使用的签名算法和签名使用的秘钥
  34. if (ttlMillis >= 0) {
  35. long expMillis = nowMillis + ttlMillis;
  36. Date exp = new Date(expMillis);
  37. builder.setExpiration(exp); //设置过期时间
  38. }
  39. return builder.compact(); //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
  40. }
  41. public static Claims parseJWT(String jwt){
  42. SecretKey key = generalKey(); //签名秘钥,和生成的签名的秘钥一模一样
  43. Claims claims = Jwts.parser() //得到DefaultJwtParser
  44. .setSigningKey(key) //设置签名的秘钥
  45. .parseClaimsJws(jwt).getBody();//设置需要解析的jwt
  46. return claims;
  47. }
  48. public static void main(String[] args){
  49. Map<String, Object> user = new HashMap<>();
  50. user.put("username", "it1995");
  51. user.put("password", "123456");
  52. String jwt = createJWT(UUID.randomUUID().toString(), JSON.toJSONString(user), 3600 * 24);
  53. System.out.println("加密后:" + jwt);
  54. //解密
  55. Claims claims = parseJWT(jwt);
  56. System.out.println("解密后:" + claims.getSubject());
  57. }
  58. }

(2)修改login方法,用户登录成功 则 签发TOKEN

  1. @PostMapping("/login")
  2. public String login(@RequestBody User user){
  3. //在redis中根据用户名查找密码
  4. String password = redisTemplate.opsForValue().get(user.getUsername());
  5. System.out.println(password);
  6. boolean checkResult = BCrypt.checkpw(user.getPassword(), password);
  7. if (checkResult){
  8. Map<String, String> info = new HashMap<>();
  9. info.put("username", user.getUsername());
  10. String token = JwtUtil.createJWT(UUID.randomUUID().toString(), user.getUsername(), 3600L*1000);
  11. info.put("token",token);
  12. return JSONUtil.toJsonStr(info);
  13. }else {
  14. return "登录失败";
  15. }
  16. }

(3) 测试

网关过滤器验证token

(1)网关模块添加依赖

  1. <!--鉴权-->
  2. <dependency>
  3. <groupId>io.jsonwebtoken</groupId>
  4. <artifactId>jjwt</artifactId>
  5. <version>0.9.0</version>
  6. </dependency>

(2)创建JWTUtil类

  1. package com.mye.nacosprovider.jwt;
  2. import com.alibaba.fastjson.JSON;
  3. import io.jsonwebtoken.Claims;
  4. import io.jsonwebtoken.JwtBuilder;
  5. import io.jsonwebtoken.Jwts;
  6. import io.jsonwebtoken.SignatureAlgorithm;
  7. import org.springframework.stereotype.Component;
  8. import javax.crypto.SecretKey;
  9. import javax.crypto.spec.SecretKeySpec;
  10. import java.util.Base64;
  11. import java.util.Date;
  12. import java.util.*;
  13. @Component
  14. public class JwtUtil {
  15. //加密 解密时的密钥 用来生成key
  16. public static final String JWT_KEY = "IT1995";
  17. /** * 生成加密后的秘钥 secretKey * @return */
  18. public static SecretKey generalKey() {
  19. byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
  20. SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
  21. return key;
  22. }
  23. public static String createJWT(String id, String subject, long ttlMillis){
  24. SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
  25. long nowMillis = System.currentTimeMillis();//生成JWT的时间
  26. Date now = new Date(nowMillis);
  27. SecretKey key = generalKey();//生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
  28. JwtBuilder builder = Jwts.builder() //这里其实就是new一个JwtBuilder,设置jwt的body
  29. // .setClaims(claims) //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
  30. .setId(id) //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
  31. .setIssuedAt(now) //iat: jwt的签发时间
  32. .setSubject(subject) //sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
  33. .signWith(signatureAlgorithm, key);//设置签名使用的签名算法和签名使用的秘钥
  34. if (ttlMillis >= 0) {
  35. long expMillis = nowMillis + ttlMillis;
  36. Date exp = new Date(expMillis);
  37. builder.setExpiration(exp); //设置过期时间
  38. }
  39. return builder.compact(); //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
  40. }
  41. public static Claims parseJWT(String jwt){
  42. SecretKey key = generalKey(); //签名秘钥,和生成的签名的秘钥一模一样
  43. Claims claims = Jwts.parser() //得到DefaultJwtParser
  44. .setSigningKey(key) //设置签名的秘钥
  45. .parseClaimsJws(jwt).getBody();//设置需要解析的jwt
  46. return claims;
  47. }
  48. public static void main(String[] args){
  49. Map<String, Object> user = new HashMap<>();
  50. user.put("username", "it1995");
  51. user.put("password", "123456");
  52. String jwt = createJWT(UUID.randomUUID().toString(), JSON.toJSONString(user), 3600 * 24);
  53. System.out.println("加密后:" + jwt);
  54. //解密
  55. Claims claims = parseJWT(jwt);
  56. System.out.println("解密后:" + claims.getSubject());
  57. }
  58. }

(3)创建过滤器,用于token验证

  1. /** * 鉴权过滤器 验证token */
  2. @Component
  3. public class AuthorizeFilter implements GlobalFilter, Ordered {
  4. private static final String AUTHORIZE_TOKEN = "token";
  5. @Override
  6. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  7. //1. 获取请求
  8. ServerHttpRequest request = exchange.getRequest();
  9. //2. 则获取响应
  10. ServerHttpResponse response = exchange.getResponse();
  11. //3. 如果是登录请求则放行
  12. if (request.getURI().getPath().contains("/admin/login")) {
  13. return chain.filter(exchange);
  14. }
  15. //4. 获取请求头
  16. HttpHeaders headers = request.getHeaders();
  17. //5. 请求头中获取令牌
  18. String token = headers.getFirst(AUTHORIZE_TOKEN);
  19. //6. 判断请求头中是否有令牌
  20. if (StringUtils.isEmpty(token)) {
  21. //7. 响应中放入返回的状态吗, 没有权限访问
  22. response.setStatusCode(HttpStatus.UNAUTHORIZED);
  23. //8. 返回
  24. return response.setComplete();
  25. }
  26. //9. 如果请求头中有令牌则解析令牌
  27. try {
  28. JwtUtil.parseJWT(token);
  29. } catch (Exception e) {
  30. e.printStackTrace();
  31. //10. 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现
  32. response.setStatusCode(HttpStatus.UNAUTHORIZED);
  33. //11. 返回
  34. return response.setComplete();
  35. }
  36. //12. 放行
  37. return chain.filter(exchange);
  38. }
  39. @Override
  40. public int getOrder() {
  41. return 0;
  42. }
  43. }

(4)测试:

首先进行登录测试

在进行鉴权测试

相关文章