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