Spring Cloud Gateway 使用JWT工具類做用戶登錄校驗功能

1. JWT測試

/**
 * @Auther: csp1999
 * @Date: 2021/01/24/19:29
 * @Description: JWT測試
 */
public class JwtTest {

 /**
  * 創建Jwt令牌:
  *
  * JWT = 頭部Header + 載荷playload + 簽名signature
  */
 @Test
 public void testCreateJwt() {
  // 構建jwt令牌
  // 1.頭部Header: 描述關於該JWT的最基本的信息,例如其類型以及簽名所用的算法等
  JwtBuilder builder = Jwts.builder()
    .setId("8989")      // 設置令牌唯一編號
    .setIssuer("csp1999")    // 設置令牌頒發者
    .setSubject("JWT加密測試")   // 設置令牌主題 可以是JSON數據
    .setIssuedAt(new Date())   // 設置令牌簽發日期
    .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 3));// 設置令牌過期時間 3分鐘

  // 2.自定義載荷playload: 存放有效信息的地方
  Map<String,Object> userInfo = new HashMap<>();
  userInfo.put("username","csp");
  userInfo.put("password","123456");
  userInfo.put("school","河南科技大學");
  userInfo.put("age","22");
  // 將載荷添加到JWT令牌中
  builder.addClaims(userInfo);

  // 3.為令牌設置 簽名signature
  builder.signWith(SignatureAlgorithm.HS256, "haust");// 設置令牌的簽名 使用HS256算法,並設置SecretKey密鑰(字符串)

  // 構建 並返回一個字符串
  String jwtStr = builder.compact();
  System.out.println(jwtStr);
 }

 /**
  * 解析Jwt令牌數據
  */
 @Test
 public void testParseJwt() {
  // jwt字符串
  String jwtStr = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4OTg5IiwiaXNzIjoiY3NwMTk5OSIsInN1YiI6IkpXVOWKoOWvhua1i-ivlSIsImlhdCI6MTYxMTQ4ODc1MSwiZXhwIjoxNjExNDg4OTMxLCJwYXNzd29yZCI6IjEyMzQ1NiIsInNjaG9vbCI6Iuays-WNl-enkeaKgOWkp-WtpiIsImFnZSI6IjIyIiwidXNlcm5hbWUiOiJjc3AifQ.uH28G9MSHfzaKBAOyr8AdksYLVvy8O5P8g7TORZIUFY";

  // 解析jwt字符串
  Claims claims = Jwts.parser().
    setSigningKey("haust").  // 密鑰(鹽)
    parseClaimsJws(jwtStr).  // 要解析的令牌對象
    getBody();     // 獲取解析後的結果

  // {jti=8989, iss=csp1999, sub=JWT加密測試, iat=1611488751, exp=1611488931, password=123456, school=河南科技大學, age=22, username=csp}
  System.out.println(claims);
 }
}

2. JWT工具類

/**
 * @Auther: csp1999
 * @Date: 2021/01/24/19:29
 * @Description: JWT工具類
 */
public class JwtUtil {
 // 有效期為
 public static final Long JWT_TTL = 3600000L;// 60 * 60 * 1000 一個小時

 // Jwt令牌信息
 public static final String JWT_KEY = "itcast";

 /**
  * 生成令牌
  * @param id
  * @param subject
  * @param ttlMillis
  * @return
  */
 public static String createJWT(String id, String subject, Long ttlMillis) {
  // 指定算法
  SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

  // 當前系統時間
  long nowMillis = System.currentTimeMillis();
  // 令牌簽發時間
  Date now = new Date(nowMillis);

  // 如果令牌有效期為null,則默認設置有效期1小時
  if (ttlMillis == null) {
   ttlMillis = JwtUtil.JWT_TTL;
  }

  // 令牌過期時間設置
  long expMillis = nowMillis + ttlMillis;
  Date expDate = new Date(expMillis);

  // 生成秘鑰
  SecretKey secretKey = generalKey();

  // 封裝Jwt令牌信息
  JwtBuilder builder = Jwts.builder()
    .setId(id)         //唯一的ID
    .setSubject(subject)      // 主題 可以是JSON數據
    .setIssuer("admin")       // 簽發者
    .setIssuedAt(now)       // 簽發時間
    .signWith(signatureAlgorithm, secretKey) // 簽名算法以及密匙
    .setExpiration(expDate);     // 設置過期時間

  return builder.compact();
 }

 /**
  * 生成加密 secretKey
  *
  * @return
  */
 public static SecretKey generalKey() {
  byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());
  SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
  return key;
 }


 /**
  * 解析令牌數據
  *
  * @param jwt
  * @return
  * @throws Exception
  */
 public static Claims parseJWT(String jwt) throws Exception {
  SecretKey secretKey = generalKey();
  return Jwts.parser()
    .setSigningKey(secretKey)
    .parseClaimsJws(jwt)
    .getBody();
 }

 public static void main(String[] args) {
  String jwt = JwtUtil.createJWT("weiyibiaoshi", "aaaaaa", null);

  System.out.println(jwt);
  try {
   Claims claims = JwtUtil.parseJWT(jwt);
   System.out.println(claims);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

3. 用戶登錄校驗

3.1 網關過濾器

/**
 * @Auther: csp1999
 * @Date: 2021/01/24/20:17
 * @Description: 授權過濾器
 */
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {

 // 令牌頭名字
 private static final String AUTHORIZE_TOKEN = "Authorization";

 /**
  * 全局過濾器
  *
  * @param exchange
  * @param chain
  * @return
  */
 @Override
 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  // 獲取Request、Response對象
  ServerHttpRequest request = exchange.getRequest();
  ServerHttpResponse response = exchange.getResponse();

  // 獲取請求的URI
  String path = request.getURI().getPath();

  // 如果是登錄、goods等開放的微服務[這裡的goods部分開放],則直接放行,這裡不做完整演示,完整演示需要設計一套權限系統
  // 未登錄下隻放行登錄和搜索
  if (path.startsWith("/api/user/login") || path.startsWith("/api/brand/search/")) {
   // 放行
   Mono<Void> filter = chain.filter(exchange);

   return filter;
  }

  // 從頭文件中獲取的令牌信息
  String token = request.getHeaders().getFirst(AUTHORIZE_TOKEN);
  // 如果為true:說明令牌在頭文件中, false:令牌不在頭文件中,將令牌封裝入頭文件,再傳遞給其他微服務
  boolean hasToken = true;

  // 如果頭文件中沒有令牌信息,則從請求參數中獲取
  if (StringUtils.isEmpty(token)) {
   token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
   hasToken = false;
  }

  // 如果為空,則輸出錯誤代碼
  if (StringUtils.isEmpty(token)) {
   // 設置方法不允許被訪問,405錯誤代碼
   response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
   return response.setComplete();
  }

  // 如果不為空,則解析令牌數據
  try {
   Claims claims = JwtUtil.parseJWT(token);
  } catch (Exception e) {
   e.printStackTrace();
   // 解析失敗,響應401錯誤
   response.setStatusCode(HttpStatus.UNAUTHORIZED);
   return response.setComplete();
  }

  // 放行之前,將令牌封裝到頭文件中(這一步是為瞭方便AUTH2校驗令牌)
  request.mutate().header(AUTHORIZE_TOKEN,token);

  // 放行
  return chain.filter(exchange);
 }


 /**
  * 過濾器執行順序
  *
  * @return
  */
 @Override
 public int getOrder() {
  // 首位
  return 0;
 }
}

3.2 網關微服務application.yml

spring:
 cloud:
 gateway:
  globalcors:
  corsConfigurations:
   '[/**]': # 匹配所有請求
   allowedOrigins: "*" # 跨域處理 允許所有的域
   allowedMethods: #支持的請求類型
    - GET
    - POST
    - PUT
    - DELETE
  routes:
  # 對接商品goods微服務路由相關配置
  - id: changgou_goods_route
   uri: lb://changgou-goods
   predicates:
   - Path=/api/brand/**,/api/category/**
   filters:
   - StripPrefix=1
   - name: RequestRateLimiter # 請求數限流 名字不能隨便寫 ,使用默認的facatory
    args:
    # 用於限流的鍵的解析器的 Bean 對象的名字。它使用 SpEL 表達式根據#{@beanName}從 Spring 容器中獲取 Bean 對象。
    key-resolver: "#{@ipKeyResolver}"
    # 令牌桶每秒填充平均速率
    redis-rate-limiter.replenishRate: 1
    # 令牌桶總容量
    redis-rate-limiter.burstCapacity: 1
    # 上面配置,表示1秒內,允許 1個請求通過,令牌桶的填充速率也是1秒鐘添加1個令牌。
  # 對接用戶user微服務路由相關配置
  - id: changgou_user_route
   uri: lb://changgou-user
   predicates:
   - Path=/api/user/**,/api/address/**,/api/areas/**,/api/cities/**,/api/provinces/**
   filters:
   # user微服務真實請求中是沒有/api的,所以這裡StripPrefix=1
   - StripPrefix=1
 # 微服務名稱
 application:
 name: changgou-gateway-web
 # Redis配置
 redis:
 # Redis數據庫索引(默認為0)
 database: 0
 # Redis服務器地址
 host: 8.131.66.136
 # Redis服務器連接端口
 port: 6379
 # Redis服務器連接密碼(默認為空)
 password: csp19990129

server:
 port: 8001
eureka:
 client:
 service-url:
  defaultZone: http://127.0.0.1:7001/eureka
 instance:
 prefer-ip-address: true
management:
 endpoint:
 gateway:
  enabled: true
 web:
  exposure:
  include: true

3.3 網關微服務主啟動類

/**
 * @Auther: csp1999
 * @Date: 2021/01/24/15:16
 * @Description: 用戶/前臺微服務網關啟動類
 */
@SpringBootApplication
@EnableEurekaClient
public class GatewayWebApplication {

 public static void main(String[] args) {
  SpringApplication.run(GatewayWebApplication.class, args);
 }

 /**
  * IP限流:由用戶請求的IP創建創建用戶唯一標識,進而根據IP進行限流操作
  *
  * @return
  */
 @Bean(name = "ipKeyResolver")
 public KeyResolver userKeyResolver() {
  return new KeyResolver() {
   @Override
   public Mono<String> resolve(ServerWebExchange exchange) {
    // 獲取遠程客戶端IP
    String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
    System.out.println("hostName:" + hostName);
    return Mono.just(hostName);
   }
  };
 }
}

3.4 用戶微服務編寫登錄代碼

/**
 * @Author: csp1999
 * @Description: User 的Controller
 * @Date 2021/1/14 0:18
 */
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {

 @Autowired
 private UserService userService;

	 /***
  * 修改User數據
  * @param user
  * @param id
  * @return
  */
 @PutMapping(value = "/{id}")
 public Result update(@RequestBody User user, @PathVariable String id) {
  	 ...
 }

 /***
  * 新增User數據
  * @param user
  * @return
  */
 @PostMapping
 public Result add(@RequestBody User user) {
  ...
 }

 /***
  * 根據ID查詢User數據
  * @param id
  * @return
  */
 @GetMapping("/{id}")
 public Result<User> findById(@PathVariable String id) {
  ...
 }

 /***
  * 查詢User全部數據
  * @return
  */
 @GetMapping
 public Result<List<User>> findAll() {
  ...
 }

 /***
  * 用戶登錄
  * @param username
  * @param password
  * @param response
  * @param request
  * @return
  */
 @RequestMapping("/login")
 public Result<User> login(String username, String password, HttpServletResponse response, HttpServletRequest request) {
  // 1.從數據庫中查詢用戶名對應的用戶的對象
  User user = userService.findById(username);
  if (user == null) {
   // 2.判斷用戶是否為空 為空返回數據
   return new Result<User>(false, StatusCode.LOGINERROR, "用戶名或密碼錯誤...");
  }

  // 3.如果不為空 判斷密碼是否正確 若正確 則登錄成功
  if (BCrypt.checkpw(password, user.getPassword())) {
   // 登錄成功,講用戶信息存入map
   Map<String, Object> info = new HashMap<String, Object>();
   info.put("role", "USER");
   info.put("success", "SUCCESS");
   info.put("username", username);

   // 3.1生成令牌
   String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), JSON.toJSONString(info), null);
   // 3.2設置jwt存入 cookie 中
   Cookie cookie = new Cookie("Authorization", jwt);
   response.addCookie(cookie);
   // 3.3設置jwt存入頭文件中
   response.setHeader("Authorization", jwt);

   return new Result<User>(true, StatusCode.OK, "登錄成功", jwt);
  } else {
   // 登錄失敗
   return new Result<User>(false, StatusCode.LOGINERROR, "用戶名或密碼錯誤");
  }
 }
}

到此這篇關於Spring Cloud Gateway 使用JWT工具類做用戶登錄校驗的示例代碼的文章就介紹到這瞭,更多相關Spring Cloud Gateway 用戶登錄校驗內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: