add生成JWT Token工具类

This commit is contained in:
mr 2021-06-08 00:50:27 +08:00
parent 6672bbc8cd
commit df640c4e18
8 changed files with 343 additions and 15 deletions

View File

@ -0,0 +1,29 @@
package com.youlai.auth.config;
import com.youlai.auth.jwt.JwtProperties;
import com.youlai.auth.jwt.JwtTokenGenerator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* JwtConfiguration
*/
@EnableConfigurationProperties(JwtProperties.class)
@ConditionalOnProperty(prefix = "jwt.config", name = "enabled")
@Configuration
public class JwtConfiguration {
/**
* Jwt token generator.
*
* @param jwtProperties the jwt properties
* @return the jwt token generator
*/
@Bean
public JwtTokenGenerator jwtTokenGenerator(JwtProperties jwtProperties) {
return new JwtTokenGenerator(jwtProperties);
}
}

View File

@ -4,6 +4,7 @@ import cn.hutool.json.JSONObject;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.youlai.auth.enums.OAuthClientEnum;
import com.youlai.auth.jwt.JwtTokenPair;
import com.youlai.auth.service.WeAppService;
import com.youlai.common.constant.AuthConstants;
import com.youlai.common.result.Result;
@ -15,7 +16,6 @@ import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
@ -53,7 +53,7 @@ public class OAuthController {
@ApiIgnore Principal principal,
@ApiIgnore @RequestParam Map<String, String> parameters
) throws HttpRequestMethodNotSupportedException {
OAuth2AccessToken oAuth2AccessToken;
JwtTokenPair jwtTokenPair = new JwtTokenPair();
/**
* 获取登录认证的客户端ID
@ -67,15 +67,15 @@ public class OAuthController {
switch (client) {
case WEAPP: // 微信小程序
oAuth2AccessToken = weAppService.login(principal, parameters);
jwtTokenPair = weAppService.login(parameters);
break;
case TEST: // knife4j接口测试文档使用 client_id/client_secret : client/123456
return tokenEndpoint.postAccessToken(principal, parameters).getBody();
default:
oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
// oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
break;
}
return Result.success(oAuth2AccessToken);
return Result.success(jwtTokenPair);
}
@ApiOperation(value = "注销", notes = "logout")

View File

@ -0,0 +1,102 @@
package com.youlai.auth.jwt;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 构建 jwt payload
**/
public class JwtPayloadBuilder {
private Map<String, Object> payload = new HashMap<>();
/**
* 附加的属性
*/
private Map<String, String> additional;
/**
* jwt签发者
**/
private String iss;
/**
* jwt所面向的用户
**/
private String sub;
/**
* 接收jwt的一方
**/
private String aud;
/**
* jwt的过期时间这个过期时间必须要大于签发时间
**/
private LocalDateTime exp;
/**
* jwt的签发时间
**/
private LocalDateTime iat = LocalDateTime.now();
/**
* 权限集
*/
private Set<String> authorities = new HashSet<>();
/**
* jwt的唯一身份标识主要用来作为一次性token,从而回避重放攻击
**/
private String jti = IdUtil.simpleUUID();
public JwtPayloadBuilder iss(String iss) {
this.iss = iss;
return this;
}
public JwtPayloadBuilder sub(String sub) {
this.sub = sub;
return this;
}
public JwtPayloadBuilder aud(String aud) {
this.aud = aud;
return this;
}
public JwtPayloadBuilder authorities(Set<String> authorities) {
this.authorities = authorities;
return this;
}
public JwtPayloadBuilder expDays(int days) {
Assert.isTrue(days > 0, "jwt expireDate must after now");
this.exp = this.iat.plusDays(days);
return this;
}
public JwtPayloadBuilder additional(Map<String, String> additional) {
this.additional = additional;
return this;
}
public String builder() {
payload.put("iss", this.iss);
payload.put("sub", this.sub);
payload.put("aud", this.aud);
payload.put("exp", this.exp.toEpochSecond(ZoneOffset.of("+8")));
payload.put("iat", this.iat.toEpochSecond(ZoneOffset.of("+8")));
payload.put("jti", this.jti);
if (!CollectionUtils.isEmpty(additional)) {
payload.putAll(additional);
}
payload.put("authorities", this.authorities.toArray());
return JSONUtil.toJsonStr(JSONUtil.parse(payload));
}
}

View File

@ -0,0 +1,43 @@
package com.youlai.auth.jwt;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* Jwt springboot application.yml 中的配置文件
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "jwt.config")
public class JwtProperties {
/**
* 是否可用
*/
private boolean enabled;
/**
* jks 路径
*/
private String keyLocation;
/**
* key alias
*/
private String keyAlias;
/**
* key store pass
*/
private String keyPass;
/**
* jwt签发者
**/
private String iss;
/**
* jwt所面向的用户
**/
private String sub;
/**
* access jwt token 有效天数
*/
private int accessExpDays;
}

View File

@ -0,0 +1,116 @@
package com.youlai.auth.jwt;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
import org.springframework.util.Assert;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Map;
import java.util.Set;
/**
* JwtTokenGenerator
*/
@Slf4j
public class JwtTokenGenerator {
private static final String JWT_EXP_KEY = "exp";
private KeyPair keyPair;
private JwtPayloadBuilder jwtPayloadBuilder = new JwtPayloadBuilder();
private JwtProperties jwtProperties;
/**
* Instantiates a new Jwt token generator.
*
* @param jwtProperties the jwt properties
*/
public JwtTokenGenerator(JwtProperties jwtProperties) {
this.jwtProperties = jwtProperties;
KeyPairFactory keyPairFactory = new KeyPairFactory();
this.keyPair = keyPairFactory.getKeyPair(jwtProperties);
}
/**
* Jwt token pair jwt token pair.
*
* @param aud the aud
* @param authorities the authorities
* @param additional the additional
* @return the jwt token pair
*/
public JwtTokenPair jwtTokenPair(String aud, Set<String> authorities, Map<String, String> additional) {
String accessToken = jwtToken(aud, jwtProperties.getAccessExpDays(), authorities, additional);
JwtTokenPair jwtTokenPair = new JwtTokenPair();
jwtTokenPair.setToken_type("bearer");
jwtTokenPair.setAccess_token(accessToken);
return jwtTokenPair;
}
/**
* Jwt token string.
*
* @param aud the aud
* @param exp the exp
* @param authorities the authorities
* @param additional the additional
* @return the string
*/
private String jwtToken(String aud, int exp, Set<String> authorities, Map<String, String> additional) {
String payload = jwtPayloadBuilder
.iss(jwtProperties.getIss())
.sub(jwtProperties.getSub())
.aud(aud)
.additional(additional)
.authorities(authorities)
.expDays(exp)
.builder();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RsaSigner signer = new RsaSigner(privateKey);
return JwtHelper.encode(payload, signer).getEncoded();
}
/**
* 解码 并校验签名 过期不予解析
*
* @param jwtToken the jwt token
* @return the jwt claims
*/
public JSONObject decodeAndVerify(String jwtToken) {
Assert.hasText(jwtToken, "jwt token must not be bank");
RSAPublicKey rsaPublicKey = (RSAPublicKey) this.keyPair.getPublic();
SignatureVerifier rsaVerifier = new RsaVerifier(rsaPublicKey);
Jwt jwt = JwtHelper.decodeAndVerify(jwtToken, rsaVerifier);
String claims = jwt.getClaims();
JSONObject jsonObject = JSONUtil.parseObj(claims);
String exp = jsonObject.getStr(JWT_EXP_KEY);
if (isExpired(exp)) {
throw new IllegalStateException("jwt token is expired");
}
return jsonObject;
}
/**
* 判断jwt token是否过期.
*
* @param exp the jwt token exp
* @return the boolean
*/
private boolean isExpired(String exp) {
return LocalDateTime.now().isAfter(LocalDateTime.ofEpochSecond(Long.parseLong(exp), 0, ZoneOffset.ofHours(8)));
}
}

View File

@ -0,0 +1,16 @@
package com.youlai.auth.jwt;
import lombok.Data;
import java.io.Serializable;
/**
* JwtTokenPair
*
**/
@Data
public class JwtTokenPair implements Serializable {
private static final long serialVersionUID = -8518897818107784049L;
private String access_token;
private String token_type;
}

View File

@ -0,0 +1,19 @@
package com.youlai.auth.jwt;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import java.security.KeyPair;
/**
* KeyPairFactory
**/
public class KeyPairFactory {
public KeyPair getKeyPair(JwtProperties jwtProperties) {
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource(jwtProperties.getKeyLocation()),
jwtProperties.getKeyPass().toCharArray());
KeyPair keyPair = factory.getKeyPair(jwtProperties.getKeyAlias(), jwtProperties.getKeyPass().toCharArray());
return keyPair;
}
}

View File

@ -4,8 +4,9 @@ import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.hutool.core.util.StrUtil;
import com.youlai.auth.jwt.JwtTokenGenerator;
import com.youlai.auth.jwt.JwtTokenPair;
import com.youlai.auth.enums.PasswordEncoderTypeEnum;
import com.youlai.common.constant.AuthConstants;
import com.youlai.common.constant.GlobalConstants;
import com.youlai.common.result.Result;
import com.youlai.common.result.ResultCode;
@ -17,11 +18,13 @@ import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.apache.logging.log4j.util.Strings;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.security.Principal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
@ -37,16 +40,17 @@ public class WeAppService {
private PasswordEncoder passwordEncoder;
private TokenEndpoint tokenEndpoint;
@Resource
private JwtTokenGenerator jwtTokenGenerator;
/**
* @param principal
* @param parameters code=小程序授权code
* encryptedData=包括敏感数据在内的完整用户信息的加密数据
* iv=加密算法的初始向量
* @return
*/
@SneakyThrows
public OAuth2AccessToken login(Principal principal, Map<String, String> parameters) {
public JwtTokenPair login(Map<String, String> parameters) {
String code = parameters.get("code");
@ -61,6 +65,7 @@ public class WeAppService {
String sessionKey = session.getSessionKey();
Result<AuthMemberDTO> result = memberFeignClient.getUserByOpenid(openid);
Long userId = result.getData().getId();
if (ResultCode.USER_NOT_EXIST.getCode().equals(result.getCode())) { // 微信授权登录 会员信息不存在时 注册会员
String encryptedData = parameters.get("encryptedData");
@ -86,11 +91,9 @@ public class WeAppService {
}
}
// oauth2认证参数对应授权登录时注册会员的usernamepassword信息模拟通过oauth2的密码模式认证生成JWT
parameters.put("username", openid);
parameters.put("password", openid);
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
return oAuth2AccessToken;
HashSet<String> roles = new HashSet<>();
HashMap<String, String> additional = new HashMap<>();
additional.put("userId", String.valueOf(userId));
return jwtTokenGenerator.jwtTokenPair(openid, roles, additional);
}
}