溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何在Spring Cloud Gateway中利用JWT工具實現(xiàn)一個用戶登錄校驗功能

發(fā)布時間:2021-01-27 15:30:54 來源:億速云 閱讀:783 作者:Leah 欄目:開發(fā)技術(shù)

這期內(nèi)容當中小編將會給大家?guī)碛嘘P(guān)如何在Spring Cloud Gateway中利用JWT工具實現(xiàn)一個用戶登錄校驗功能,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

1. JWT測試

public class JwtTest {

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

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

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

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

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

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

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

2. 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;

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

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

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

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

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

  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;
 }


 /**
  * 解析令牌數(shù)據(jù)
  *
  * @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 網(wǎng)關(guān)過濾器

@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等開放的微服務(wù)[這里的goods部分開放],則直接放行,這里不做完整演示,完整演示需要設(shè)計一套權(quán)限系統(tǒng)
  // 未登錄下只放行登錄和搜索
  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:令牌不在頭文件中,將令牌封裝入頭文件,再傳遞給其他微服務(wù)
  boolean hasToken = true;

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

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

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

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

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


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

3.2 網(wǎng)關(guān)微服務(wù)application.yml

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

/**
 * @Auther: csp1999
 * @Date: 2021/01/24/15:16
 * @Description: 用戶/前臺微服務(wù)網(wǎng)關(guān)啟動類
 */
@SpringBootApplication
@EnableEurekaClient
public class GatewayWebApplication {

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

 /**
  * IP限流:由用戶請求的IP創(chuàng)建創(chuàng)建用戶唯一標識,進而根據(jù)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 用戶微服務(wù)編寫登錄代碼

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

 @Autowired
 private UserService userService;

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

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

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

 /***
  * 查詢User全部數(shù)據(jù)
  * @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.從數(shù)據(jù)庫中查詢用戶名對應(yīng)的用戶的對象
  User user = userService.findById(username);
  if (user == null) {
   // 2.判斷用戶是否為空 為空返回數(shù)據(jù)
   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設(shè)置jwt存入 cookie 中
   Cookie cookie = new Cookie("Authorization", jwt);
   response.addCookie(cookie);
   // 3.3設(shè)置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工具實現(xiàn)一個用戶登錄校驗功能了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI