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