top.dcenter.ums.security.jwt.config.JwtAutoConfiguration Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ums-jwt Show documentation
Show all versions of ums-jwt Show documentation
ums-jwt feature: JWT 创建(通过接口自定义 Claims, 通过配置设置算法等), 校验(通过接口自定义校验规则),
刷新(自动刷新, 直接拒绝, 通过 refreshToken 刷新), 刷新的 JWT 使旧 JWT 失效引发的并发访问问题及黑名单.
/*
* MIT License
* Copyright (c) 2020-2029 YongWu zheng (dcenter.top and gitee.com/pcore and github.com/ZeroOrInfinity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package top.dcenter.ums.security.jwt.config;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.RSAKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.lang.NonNull;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter;
import top.dcenter.ums.security.core.api.service.UmsUserDetailsService;
import top.dcenter.ums.security.jwt.JwtContext;
import top.dcenter.ums.security.jwt.api.cache.service.JwtCacheTransformService;
import top.dcenter.ums.security.jwt.api.endpoind.service.JwkEndpointPermissionService;
import top.dcenter.ums.security.jwt.api.endpoind.service.JwkSetUriConfig;
import top.dcenter.ums.security.jwt.api.id.service.JwtIdService;
import top.dcenter.ums.security.jwt.api.validator.service.ReAuthService;
import top.dcenter.ums.security.jwt.claims.service.GenerateClaimsSetService;
import top.dcenter.ums.security.jwt.controller.JwtRefreshTokenController;
import top.dcenter.ums.security.jwt.decoder.UmsNimbusJwtDecoder;
import top.dcenter.ums.security.jwt.endpoint.JwkEndpoint;
import top.dcenter.ums.security.jwt.enums.JwtRefreshHandlerPolicy;
import top.dcenter.ums.security.jwt.factory.KeyStoreKeyFactory;
import top.dcenter.ums.security.jwt.properties.BearerTokenProperties;
import top.dcenter.ums.security.jwt.properties.JwtBlacklistProperties;
import top.dcenter.ums.security.jwt.properties.JwtProperties;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;
import static org.springframework.util.StringUtils.hasText;
import static top.dcenter.ums.security.common.utils.ReflectionUtil.setFieldValue;
import static top.dcenter.ums.security.jwt.properties.JwtProperties.MACS_SECRET_LENGTH;
/**
* jwt 自动配置
* Jwt token 的 keyPair 签名 key:
* 1. 生成密钥键值对命令:keytool -genkeypair -alias zyw -keyalg RSA -keypass 123456 -keystore zyw.jks -storepass 123456
* 2. 生成公钥命令:keytool -list -rfc --keystore zyw.jks | openssl x509 -inform pem -pubkey
* @author YongWu zheng
* @version V2.0 Created by 2020.12.2 9:55
*/
@SuppressWarnings("jol")
@Configuration
@Order(99)
@AutoConfigureAfter({JwtPropertiesAutoConfiguration.class, JwtServiceAutoConfiguration.class})
@ConditionalOnProperty(prefix = "ums.jwt", name = "enable", havingValue = "true")
@Slf4j
public class JwtAutoConfiguration implements InitializingBean {
/**
* {@link JwtContext} 的 signer 字段名称
*/
public static final String SIGNER_PARAM_NAME = "signer";
/**
* {@link JwtContext} 的 jwsAlgorithm 字段名称
*/
public static final String JWS_ALGORITHM_PARAM_NAME = "jwsAlgorithm";
/**
* {@link JwtContext} 的 kid 字段名称
*/
public static final String KID_PARAM_NAME = "kid";
/**
* {@link JwtContext} 的 timeout 字段名称
*/
public static final String JWT_TIMEOUT = "timeout";
/**
* {@link JwtContext} 的 clockSkew 字段名称
*/
public static final String CLOCK_SKEW = "clockSkew";
/**
* {@link JwtContext} 的 bearerToken 字段名称
*/
public static final String BEARER_TOKEN = "bearerToken";
/**
* {@link JwtContext} 的 redisConnectionFactory 字段名称
*/
public static final String REDIS_CONNECTION_FACTORY = "redisConnectionFactory";
/**
* {@link JwtContext} 的 blacklistProperties 字段名称
*/
public static final String BLACKLIST_PROPERTIES = "blacklistProperties";
/**
* {@link JwtContext} 的 refreshHandlerPolicy 字段名称
*/
public static final String REFRESH_HANDLER_POLICY = "refreshHandlerPolicy";
/**
* {@link JwtContext} 的 jwtIdService 字段名称
*/
public static final String JWT_ID_SERVICE = "jwtIdService";
/**
* {@link JwtContext} 的 jwtCacheTransformService 字段名称
*/
public static final String JWT_CACHE_TRANSFORM_SERVICE = "jwtCacheTransformService";
/**
* {@link JwtContext} 的 principalClaimName 字段名称
*/
public static final String PRINCIPAL_CLAIM_NAME = "principalClaimName";
private final RSAPublicKey publicKey;
private final JWSSigner signer;
private final JwsAlgorithm jwsAlgorithm;
private final String kid;
private final BearerTokenProperties bearerTokenProperties;
private final RedisConnectionFactory redisConnectionFactory;
private final JwtBlacklistProperties jwtBlacklistProperties;
private final JwtRefreshHandlerPolicy refreshHandlerPolicy;
private final JwtIdService jwtIdService;
private final JwtCacheTransformService> jwtCacheTransformService;
private final String principalClaimName;
/**
* JWT 的有效期
*/
private final Duration timeout;
/**
* JWT 不同服务器间的时钟偏差, 时钟可能存在偏差, 设置时钟偏移量以消除不同服务器间的时钟偏差的影响, 默认: 0 秒.
* 注意: 此默认值适合 "单服务器" 应用, "多服务器" 应用请更改此值
*/
private final Duration clockSkew;
private final OAuth2TokenValidator oAuth2TokenValidator;
private final MappedJwtClaimSetConverter mappedJwtClaimSetConverter;
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public JwtAutoConfiguration(JwtProperties jwtProperties,
@Autowired(required = false)
RedisConnectionFactory redisConnectionFactory,
@Autowired(required = false)
@Qualifier("jwtRedisConnectionFactory")
RedisConnectionFactory jwtRedisConnectionFactory,
@Autowired(required = false) OAuth2ResourceServerProperties auth2ResourceServerProperties,
JwtIdService jwtIdService,
JwtCacheTransformService> jwtCacheTransformService,
OAuth2TokenValidator oAuth2TokenValidator,
MappedJwtClaimSetConverter mappedJwtClaimSetConverter) throws Exception {
if (isNull(redisConnectionFactory) && isNull(jwtRedisConnectionFactory)) {
throw new RuntimeException("redisConnectionFactory 或 jwtRedisConnectionFactory 必须实现一个");
}
if (nonNull(jwtRedisConnectionFactory)) {
this.redisConnectionFactory = jwtRedisConnectionFactory;
}
else {
this.redisConnectionFactory = redisConnectionFactory;
}
this.timeout = jwtProperties.getTimeout();
this.bearerTokenProperties = jwtProperties.getBearer();
this.jwtBlacklistProperties = jwtProperties.getBlacklist();
this.refreshHandlerPolicy = jwtProperties.getRefreshHandlerPolicy();
this.clockSkew = jwtProperties.getClockSkew();
this.jwtIdService = jwtIdService;
this.jwtCacheTransformService = jwtCacheTransformService;
this.oAuth2TokenValidator = oAuth2TokenValidator;
this.mappedJwtClaimSetConverter = mappedJwtClaimSetConverter;
this.principalClaimName = jwtProperties.getPrincipalClaimName();
Resource resource = jwtProperties.getJksKeyPairLocation();
if (nonNull(resource)) {
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, jwtProperties.getJksPassword().toCharArray());
KeyPair keyPair = keyStoreKeyFactory.getKeyPair(jwtProperties.getJksAlias());
this.publicKey = (RSAPublicKey) keyPair.getPublic();
this.kid = jwtProperties.getKid();
RSAKey rsaJwk = (RSAKey) JwtContext.generateJwk(keyPair, this.kid, KeyUse.SIGNATURE);
// Create RSA-signer with the private key
this.signer = new RSASSASigner(rsaJwk);
this.jwsAlgorithm = SignatureAlgorithm.from(jwtProperties.getJwsAlgorithms());
}
else if (nonNull(jwtProperties.getMacsSecret())) {
String macsSecret = jwtProperties.getMacsSecret();
if (macsSecret.length() < MACS_SECRET_LENGTH) {
throw new RuntimeException("用于 JWT 的 HMAC protection 的 secret, 字符长度必须大于等于 " + MACS_SECRET_LENGTH);
}
// Create HMAC signer
this.signer = new MACSigner(macsSecret.getBytes(StandardCharsets.UTF_8));
this.jwsAlgorithm = MacAlgorithm.from(jwtProperties.getJwsAlgorithms());
this.publicKey = null;
this.kid = jwtProperties.getKid();
}
else {
if (nonNull(auth2ResourceServerProperties)) {
OAuth2ResourceServerProperties.Jwt jwt = auth2ResourceServerProperties.getJwt();
String jwsAlgorithm = jwt.getJwsAlgorithm();
this.jwsAlgorithm = SignatureAlgorithm.from(jwsAlgorithm);
}
else {
this.jwsAlgorithm = null;
}
this.publicKey = null;
this.signer = null;
this.kid = null;
}
}
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Bean
@ConditionalOnProperty(prefix = "ums.jwt", name = "expose-refresh-token-uri", havingValue = "true")
public JwtRefreshTokenController jwtRefreshTokenController(GenerateClaimsSetService generateClaimsSetService,
UmsUserDetailsService umsUserDetailsService,
UmsNimbusJwtDecoder jwtDecoder,
JwtProperties jwtProperties) {
return new JwtRefreshTokenController(generateClaimsSetService, umsUserDetailsService,
jwtDecoder, jwtProperties);
}
@Bean
@ConditionalOnProperty(prefix = "ums.jwt", name = "expose-jwk-set-uri", havingValue = "true")
public JwkEndpoint jwkEndpoint(JwtProperties jwtProperties, JwkEndpointPermissionService jwkEndpointPermissionService)
throws InvocationTargetException, IllegalAccessException {
requireNonNull(this.publicKey, "jks-key-pair-location cannot bu null");
requireNonNull(jwkEndpointPermissionService, "jwkEndpointPermissionService cannot bu null");
return new JwkEndpoint(this.publicKey, jwtProperties.getJwsAlgorithms(),
jwkEndpointPermissionService, jwtProperties.getKid());
}
@Bean
@Primary
public JwtDecoder jwtDecoder(@Autowired(required = false) OAuth2ResourceServerProperties auth2ResourceServerProperties,
@Autowired(required = false) JwkSetUriConfig jwkSetUriConfig,
@Autowired(required = false) ReAuthService reAuthService,
JwtProperties jwtProperties) {
Resource jksKeyPairResource = jwtProperties.getJksKeyPairLocation();
String macsSecret = jwtProperties.getMacsSecret();
UmsNimbusJwtDecoder jwtDecoder = null;
if (nonNull(jksKeyPairResource)) {
jwtDecoder = UmsNimbusJwtDecoder.withPublicKey(this.publicKey,
jwtProperties.getRefreshHandlerPolicy(),
jwtProperties.getRemainingRefreshInterval(),
Boolean.TRUE)
.signatureAlgorithm((SignatureAlgorithm) this.jwsAlgorithm)
.build();
}
else if (hasText(macsSecret)) {
jwtDecoder =
UmsNimbusJwtDecoder.withSecretKey(new SecretKeySpec(macsSecret.getBytes(StandardCharsets.UTF_8),
"MAC"),
jwtProperties.getRefreshHandlerPolicy(),
jwtProperties.getRemainingRefreshInterval(),
Boolean.TRUE)
.macAlgorithm((MacAlgorithm) this.jwsAlgorithm)
.build();
}
else if (nonNull(auth2ResourceServerProperties)) {
jwtDecoder =
UmsNimbusJwtDecoder.withJwkSetUri(auth2ResourceServerProperties.getJwt().getJwkSetUri(),
jwtProperties.getRefreshHandlerPolicy(),
jwtProperties.getRemainingRefreshInterval(),
jwkSetUriConfig,
Boolean.FALSE)
.jwsAlgorithm((SignatureAlgorithm) this.jwsAlgorithm)
.build();
}
if (isNull(jwtDecoder)) {
throw new RuntimeException("未成功创建 org.springframework.security.oauth2.jwt.JwtDecoder; \n" +
"当需要拥有创建 JWT 功能时需要配置 \"ums.jwt.jksKeyPairLocation\" 或 " +
"\"ums.jwt.macsSecret\" 的属性, \n" +
"当仅仅需要解析 JWT 时请配置 \"spring.security.oauth2.resourceserver.jwt.jwk-set-uri\" 属性");
}
jwtDecoder.setReAuthService(reAuthService);
setJwtValidatorAndClaimSetConverter(oAuth2TokenValidator, mappedJwtClaimSetConverter, jwtDecoder);
return jwtDecoder;
}
@Override
public void afterPropertiesSet() throws Exception {
// 注册 JWSSigner signer 到 JwtContext.signer
Class jwtUtilClass = JwtContext.class;
Class.forName(jwtUtilClass.getName());
if (nonNull(this.signer))
{
setFieldValue(SIGNER_PARAM_NAME, this.signer, null, jwtUtilClass);
}
if (nonNull(this.jwsAlgorithm)) {
setFieldValue(JWS_ALGORITHM_PARAM_NAME, this.jwsAlgorithm.getName(), null, jwtUtilClass);
}
if (nonNull(this.kid)) {
setFieldValue(KID_PARAM_NAME, this.kid, null, jwtUtilClass);
}
if (nonNull(this.timeout)) {
setFieldValue(JWT_TIMEOUT, this.timeout, null, jwtUtilClass);
}
if (nonNull(this.clockSkew)) {
setFieldValue(CLOCK_SKEW, this.clockSkew, null, jwtUtilClass);
}
if (nonNull(this.bearerTokenProperties)) {
BearerTokenProperties bearerTokenProperties = new BearerTokenProperties();
BeanUtils.copyProperties(this.bearerTokenProperties, bearerTokenProperties);
setFieldValue(BEARER_TOKEN, bearerTokenProperties, null, jwtUtilClass);
}
if (nonNull(this.jwtBlacklistProperties)) {
JwtBlacklistProperties jwtBlacklistProperties = new JwtBlacklistProperties();
BeanUtils.copyProperties(this.jwtBlacklistProperties, jwtBlacklistProperties);
setFieldValue(BLACKLIST_PROPERTIES, jwtBlacklistProperties, null, jwtUtilClass);
}
if (nonNull(this.redisConnectionFactory)) {
setFieldValue(REDIS_CONNECTION_FACTORY, this.redisConnectionFactory, null, jwtUtilClass);
}
if (nonNull(this.refreshHandlerPolicy)) {
setFieldValue(REFRESH_HANDLER_POLICY, this.refreshHandlerPolicy, null, jwtUtilClass);
}
if (nonNull(this.jwtIdService)) {
setFieldValue(JWT_ID_SERVICE, this.jwtIdService, null, jwtUtilClass);
}
if (nonNull(this.jwtCacheTransformService)) {
setFieldValue(JWT_CACHE_TRANSFORM_SERVICE, this.jwtCacheTransformService, null, jwtUtilClass);
}
if (nonNull(this.principalClaimName)) {
setFieldValue(PRINCIPAL_CLAIM_NAME, this.principalClaimName, null, jwtUtilClass);
}
}
private void setJwtValidatorAndClaimSetConverter(@NonNull OAuth2TokenValidator oAuth2TokenValidator,
@NonNull MappedJwtClaimSetConverter mappedJwtClaimSetConverter,
@NonNull UmsNimbusJwtDecoder jwtDecoder) {
// 设置自定义的 JWT Validator
jwtDecoder.setJwtValidator(oAuth2TokenValidator);
// 设置自定义的 claim set converter
jwtDecoder.setClaimSetConverter(mappedJwtClaimSetConverter);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy