田源
2025-04-03 9b4433fddf5b401edb0aace8a404ac733b122702
Source/BladeX-Tool/blade-core-secure/src/main/java/org/springblade/core/secure/utils/SecureUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,227 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill åº„骞 (smallchill@163.com)
 */
package org.springblade.core.secure.utils;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.SneakyThrows;
import org.springblade.core.jwt.JwtUtil;
import org.springblade.core.jwt.props.JwtProperties;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.secure.TokenInfo;
import org.springblade.core.secure.constant.SecureConstant;
import org.springblade.core.secure.exception.SecureException;
import org.springblade.core.secure.provider.IClientDetails;
import org.springblade.core.secure.provider.IClientDetailsService;
import org.springblade.core.tool.utils.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.*;
/**
 * Secure工具类
 *
 * @author Chill
 */
public class SecureUtil extends AuthUtil {
   private final static String CLIENT_ID = TokenConstant.CLIENT_ID;
   private static IClientDetailsService clientDetailsService;
   private static JwtProperties jwtProperties;
   /**
    * èŽ·å–å®¢æˆ·ç«¯æœåŠ¡ç±»
    *
    * @return clientDetailsService
    */
   private static IClientDetailsService getClientDetailsService() {
      if (clientDetailsService == null) {
         clientDetailsService = SpringUtil.getBean(IClientDetailsService.class);
      }
      return clientDetailsService;
   }
   /**
    * èŽ·å–é…ç½®ç±»
    *
    * @return jwtProperties
    */
   private static JwtProperties getJwtProperties() {
      if (jwtProperties == null) {
         jwtProperties = SpringUtil.getBean(JwtProperties.class);
      }
      return jwtProperties;
   }
   /**
    * åˆ›å»ºä»¤ç‰Œ
    *
    * @param user      user
    * @param audience  audience
    * @param issuer    issuer
    * @param tokenType tokenType
    * @return jwt
    */
   public static TokenInfo createJWT(Map<String, Object> user, String audience, String issuer, String tokenType) {
      String[] tokens = extractAndDecodeHeader();
      String clientId = tokens[0];
      String clientSecret = tokens[1];
      // èŽ·å–å®¢æˆ·ç«¯ä¿¡æ¯
      IClientDetails clientDetails = clientDetails(clientId);
      // æ ¡éªŒå®¢æˆ·ç«¯ä¿¡æ¯
      if (!validateClient(clientDetails, clientId, clientSecret)) {
         throw new SecureException("客户端认证失败, è¯·æ£€æŸ¥è¯·æ±‚头 [Authorization] ä¿¡æ¯");
      }
      SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
      long nowMillis = System.currentTimeMillis();
      Date now = new Date(nowMillis);
      //生成签名密钥
      byte[] apiKeySecretBytes = Base64.getDecoder().decode(JwtUtil.getBase64Security());
      Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
      //添加构成JWT的类
      JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
         .setIssuer(issuer)
         .setAudience(audience)
         .signWith(signingKey);
      //设置JWT参数
      user.forEach(builder::claim);
      //设置应用id
      builder.claim(CLIENT_ID, clientId);
      //添加Token过期时间
      long expireMillis;
      if (tokenType.equals(TokenConstant.ACCESS_TOKEN)) {
         expireMillis = clientDetails.getAccessTokenValidity() * 1000L;
      } else if (tokenType.equals(TokenConstant.REFRESH_TOKEN)) {
         expireMillis = clientDetails.getRefreshTokenValidity() * 1000L;
      } else {
         expireMillis = getExpire();
      }
      long expMillis = nowMillis + expireMillis;
      Date exp = new Date(expMillis);
      builder.setExpiration(exp).setNotBefore(now);
      //组装Token信息
      TokenInfo tokenInfo = new TokenInfo();
      tokenInfo.setToken(builder.compact());
      tokenInfo.setExpire((int) (expireMillis / 1000L));
      //Token状态配置, ä»…在生成AccessToken时候执行
      if (getJwtProperties().getState() && TokenConstant.ACCESS_TOKEN.equals(tokenType)) {
         String tenantId = String.valueOf(user.get(TokenConstant.TENANT_ID));
         String userId = String.valueOf(user.get(TokenConstant.USER_ID));
         JwtUtil.addAccessToken(tenantId, userId, tokenInfo.getToken(), tokenInfo.getExpire());
      }
      //Token状态配置, ä»…在生成RefreshToken时候执行
      if (getJwtProperties().getState() && getJwtProperties().getSingle() && TokenConstant.REFRESH_TOKEN.equals(tokenType)) {
         String tenantId = String.valueOf(user.get(TokenConstant.TENANT_ID));
         String userId = String.valueOf(user.get(TokenConstant.USER_ID));
         JwtUtil.addRefreshToken(tenantId, userId, tokenInfo.getToken(), tokenInfo.getExpire());
      }
      return tokenInfo;
   }
   /**
    * èŽ·å–è¿‡æœŸæ—¶é—´(次日凌晨3点)
    *
    * @return expire
    */
   public static long getExpire() {
      Calendar cal = Calendar.getInstance();
      cal.add(Calendar.DAY_OF_YEAR, 1);
      cal.set(Calendar.HOUR_OF_DAY, 3);
      cal.set(Calendar.SECOND, 0);
      cal.set(Calendar.MINUTE, 0);
      cal.set(Calendar.MILLISECOND, 0);
      return cal.getTimeInMillis() - System.currentTimeMillis();
   }
   /**
    * å®¢æˆ·ç«¯ä¿¡æ¯è§£ç 
    */
   @SneakyThrows
   public static String[] extractAndDecodeHeader() {
      // èŽ·å–è¯·æ±‚å¤´å®¢æˆ·ç«¯ä¿¡æ¯
      String header = Objects.requireNonNull(WebUtil.getRequest()).getHeader(SecureConstant.BASIC_HEADER_KEY);
      header = Func.toStr(header).replace(SecureConstant.BASIC_HEADER_PREFIX_EXT, SecureConstant.BASIC_HEADER_PREFIX);
      if (!header.startsWith(SecureConstant.BASIC_HEADER_PREFIX)) {
         throw new SecureException("未获取到请求头[Authorization]的信息");
      }
      byte[] base64Token = header.substring(6).getBytes(Charsets.UTF_8_NAME);
      byte[] decoded;
      try {
         decoded = Base64.getDecoder().decode(base64Token);
      } catch (IllegalArgumentException var7) {
         throw new RuntimeException("客户端令牌解析失败");
      }
      String token = new String(decoded, Charsets.UTF_8_NAME);
      int index = token.indexOf(StringPool.COLON);
      if (index == -1) {
         throw new RuntimeException("客户端令牌不合法");
      } else {
         return new String[]{token.substring(0, index), token.substring(index + 1)};
      }
   }
   /**
    * èŽ·å–è¯·æ±‚å¤´ä¸­çš„å®¢æˆ·ç«¯id
    */
   public static String getClientIdFromHeader() {
      String[] tokens = extractAndDecodeHeader();
      assert tokens.length == 2;
      return tokens[0];
   }
   /**
    * èŽ·å–å®¢æˆ·ç«¯ä¿¡æ¯
    *
    * @param clientId å®¢æˆ·ç«¯id
    * @return clientDetails
    */
   private static IClientDetails clientDetails(String clientId) {
      return getClientDetailsService().loadClientByClientId(clientId);
   }
   /**
    * æ ¡éªŒClient
    *
    * @param clientId     å®¢æˆ·ç«¯id
    * @param clientSecret å®¢æˆ·ç«¯å¯†é’¥
    * @return boolean
    */
   private static boolean validateClient(IClientDetails clientDetails, String clientId, String clientSecret) {
      if (clientDetails != null) {
         return StringUtil.equals(clientId, clientDetails.getClientId()) && StringUtil.equals(clientSecret, clientDetails.getClientSecret());
      }
      return false;
   }
}