From 7431a35e9c032656b17674049d985490931a8e8a Mon Sep 17 00:00:00 2001 From: zlt2000 Date: Mon, 17 May 2021 23:16:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0OIDC=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/constants/IdTokenClaimNames.java | 74 ++++++++++++ .../central/oauth2/common/util/JwtUtils.java | 21 ++++ .../common/constant/SecurityConstants.java | 9 ++ zlt-doc/sql/oauth-center.sql | 2 + ...234\254\345\215\207\347\272\247v4.5.0.sql" | 4 + .../config/AuthorizationServerConfig.java | 74 ++++++++++++ .../java/com/central/oauth/model/Client.java | 9 ++ .../central/oauth/service/IClientService.java | 2 + .../oauth/service/impl/ClientServiceImpl.java | 9 +- .../oauth/utils/OidcIdTokenBuilder.java | 111 ++++++++++++++++++ 10 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/constants/IdTokenClaimNames.java create mode 100644 "zlt-doc/\347\211\210\346\234\254\345\215\207\347\272\247_sql/\346\227\247\347\211\210\346\234\254\345\215\207\347\272\247v4.5.0.sql" create mode 100644 zlt-uaa/src/main/java/com/central/oauth/utils/OidcIdTokenBuilder.java diff --git a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/constants/IdTokenClaimNames.java b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/constants/IdTokenClaimNames.java new file mode 100644 index 0000000..ff4fd46 --- /dev/null +++ b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/constants/IdTokenClaimNames.java @@ -0,0 +1,74 @@ +package com.central.oauth2.common.constants; + +/** + * id_token属性名常量 + * + * @author zlt + * @version 1.0 + * @date 2021/4/23 + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +public class IdTokenClaimNames { + /** + * {@code iss} - the Issuer identifier + */ + public final static String ISS = "iss"; + + /** + * {@code sub} - the Subject identifier + */ + public final static String SUB = "sub"; + + /** + * {@code aud} - the Audience(s) that the ID Token is intended for + */ + public final static String AUD = "aud"; + + /** + * {@code exp} - the Expiration time on or after which the ID Token MUST NOT be accepted + */ + public final static String EXP = "exp"; + + /** + * {@code iat} - the time at which the ID Token was issued + */ + public final static String IAT = "iat"; + + /** + * {@code auth_time} - the time when the End-User authentication occurred + */ + public final static String AUTH_TIME = "auth_time"; + + /** + * {@code nonce} - a {@code String} value used to associate a Client session with an ID Token, + * and to mitigate replay attacks. + */ + public final static String NONCE = "nonce"; + + /** + * {@code acr} - the Authentication Context Class Reference + */ + public final static String ACR = "acr"; + + /** + * {@code amr} - the Authentication Methods References + */ + public final static String AMR = "amr"; + + /** + * {@code azp} - the Authorized party to which the ID Token was issued + */ + public final static String AZP = "azp"; + + /** + * {@code at_hash} - the Access Token hash value + */ + public final static String AT_HASH = "at_hash"; + + /** + * {@code c_hash} - the Authorization Code hash value + */ + public final static String C_HASH = "c_hash"; +} diff --git a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/util/JwtUtils.java b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/util/JwtUtils.java index 01d3110..e562740 100644 --- a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/util/JwtUtils.java +++ b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/util/JwtUtils.java @@ -4,15 +4,22 @@ import com.central.common.constant.SecurityConstants; import com.central.common.utils.JsonUtil; import com.central.common.utils.RsaUtils; import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.cloud.bootstrap.encrypt.KeyProperties; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; 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.security.jwt.crypto.sign.Signer; +import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; import java.io.BufferedReader; import java.io.InputStreamReader; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.stream.Collectors; @@ -84,4 +91,18 @@ public class JwtUtils { public static boolean checkExp(JsonNode claims) { return checkExp(claims, System.currentTimeMillis()); } + + public static Jwt encode(CharSequence content, KeyProperties keyProperties) { + KeyPair keyPair = new KeyStoreKeyFactory( + keyProperties.getKeyStore().getLocation(), + keyProperties.getKeyStore().getSecret().toCharArray()) + .getKeyPair(keyProperties.getKeyStore().getAlias()); + PrivateKey privateKey = keyPair.getPrivate(); + Signer rsaSigner = new RsaSigner((RSAPrivateKey) privateKey); + return JwtHelper.encode(content, rsaSigner); + } + + public static String encodeStr(CharSequence content, KeyProperties keyProperties) { + return encode(content, keyProperties).getEncoded(); + } } diff --git a/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java b/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java index 6900d2a..0152632 100644 --- a/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java +++ b/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java @@ -158,4 +158,13 @@ public interface SecurityConstants { * rsa公钥 */ String RSA_PUBLIC_KEY = "pubkey.txt"; + /** + * 获取id_token的response_type + */ + String ID_TOKEN = "id_token"; + + /** + * 令牌颁发者 + */ + String ISS = "http://zlt2000.cn"; } diff --git a/zlt-doc/sql/oauth-center.sql b/zlt-doc/sql/oauth-center.sql index a7dcb74..4a94760 100644 --- a/zlt-doc/sql/oauth-center.sql +++ b/zlt-doc/sql/oauth-center.sql @@ -22,6 +22,8 @@ CREATE TABLE `oauth_client_details` ( `create_time` datetime(0) NULL DEFAULT NULL, `update_time` datetime(0) NULL DEFAULT NULL, `client_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '应用名称', + `support_id_token` tinyint(1) DEFAULT 1 COMMENT '是否支持id_token', + `id_token_validity` int(11) COMMENT 'id_token有效期', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; diff --git "a/zlt-doc/\347\211\210\346\234\254\345\215\207\347\272\247_sql/\346\227\247\347\211\210\346\234\254\345\215\207\347\272\247v4.5.0.sql" "b/zlt-doc/\347\211\210\346\234\254\345\215\207\347\272\247_sql/\346\227\247\347\211\210\346\234\254\345\215\207\347\272\247v4.5.0.sql" new file mode 100644 index 0000000..9deec52 --- /dev/null +++ "b/zlt-doc/\347\211\210\346\234\254\345\215\207\347\272\247_sql/\346\227\247\347\211\210\346\234\254\345\215\207\347\272\247v4.5.0.sql" @@ -0,0 +1,4 @@ +------------更新语句 +Use `oauth-center`; +alter table oauth_client_details add support_id_token tinyint(1) DEFAULT 1 COMMENT '是否支持id_token'; +alter table oauth_client_details add id_token_validity int(11) DEFAULT 60 COMMENT 'id_token有效期'; \ No newline at end of file diff --git a/zlt-uaa/src/main/java/com/central/oauth/config/AuthorizationServerConfig.java b/zlt-uaa/src/main/java/com/central/oauth/config/AuthorizationServerConfig.java index a32c0af..4130612 100644 --- a/zlt-uaa/src/main/java/com/central/oauth/config/AuthorizationServerConfig.java +++ b/zlt-uaa/src/main/java/com/central/oauth/config/AuthorizationServerConfig.java @@ -1,22 +1,38 @@ package com.central.oauth.config; +import com.central.common.constant.SecurityConstants; +import com.central.common.model.SysUser; +import com.central.oauth.model.Client; +import com.central.oauth.service.IClientService; import com.central.oauth.service.impl.RedisClientDetailsService; +import com.central.oauth.utils.OidcIdTokenBuilder; +import com.central.oauth2.common.constants.IdTokenClaimNames; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.cloud.bootstrap.encrypt.KeyProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.TokenGranter; import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.security.oauth2.provider.token.TokenStore; import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; /** * OAuth2 授权服务器配置 @@ -55,6 +71,9 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap @Autowired private TokenGranter tokenGranter; + @Value("${zlt.oauth2.token.store.type:'redis'}") + private String tokenStoreType; + /** * 配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory * @param endpoints @@ -93,4 +112,59 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap //让/oauth/token支持client_id以及client_secret作登录认证 .allowFormAuthenticationForClients(); } + + @Bean + @Order(1) + public TokenEnhancer tokenEnhancer(@Autowired(required = false) KeyProperties keyProperties + , IClientService clientService) { + return (accessToken, authentication) -> { + Set responseTypes = authentication.getOAuth2Request().getResponseTypes(); + if (responseTypes.contains(SecurityConstants.ID_TOKEN) + || "authJwt".equals(tokenStoreType)) { + Map additionalInfo = new HashMap<>(2); + Object principal = authentication.getPrincipal(); + //增加id参数 + if (principal instanceof SysUser) { + SysUser user = (SysUser)principal; + if (responseTypes.contains(SecurityConstants.ID_TOKEN)) { + //生成id_token + setIdToken(additionalInfo, authentication, keyProperties, clientService, user); + } + if ("authJwt".equals(tokenStoreType)) { + additionalInfo.put("id", user.getId()); + } + } + ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); + } + return accessToken; + }; + } + + /** + * 生成id_token + * @param additionalInfo 存储token附加信息对象 + * @param authentication 授权对象 + * @param keyProperties 密钥 + * @param clientService 应用service + */ + private void setIdToken(Map additionalInfo, OAuth2Authentication authentication + , KeyProperties keyProperties, IClientService clientService, SysUser user) { + String clientId = authentication.getOAuth2Request().getClientId(); + Client client = clientService.loadClientByClientId(clientId); + if (client.getSupportIdToken()) { + String nonce = authentication.getOAuth2Request().getRequestParameters().get(IdTokenClaimNames.NONCE); + long now = System.currentTimeMillis(); + long expiresAt = System.currentTimeMillis() + client.getIdTokenValiditySeconds() * 1000; + String idToken = OidcIdTokenBuilder.builder(keyProperties) + .issuer(SecurityConstants.ISS) + .issuedAt(now) + .expiresAt(expiresAt) + .subject(String.valueOf(user.getId())) + .audience(clientId) + .nonce(nonce) + .build(); + + additionalInfo.put(SecurityConstants.ID_TOKEN, idToken); + } + } } diff --git a/zlt-uaa/src/main/java/com/central/oauth/model/Client.java b/zlt-uaa/src/main/java/com/central/oauth/model/Client.java index 996aea4..dfe3e9e 100644 --- a/zlt-uaa/src/main/java/com/central/oauth/model/Client.java +++ b/zlt-uaa/src/main/java/com/central/oauth/model/Client.java @@ -32,4 +32,13 @@ public class Client extends SuperEntity { private Integer refreshTokenValiditySeconds = 28800; private String additionalInformation = "{}"; private String autoapprove = "true"; + /** + * 是否支持id_token + */ + private Boolean supportIdToken = true; + /** + * id_token有效时间(s) + */ + @TableField(value = "id_token_validity") + private Integer idTokenValiditySeconds = 60; } diff --git a/zlt-uaa/src/main/java/com/central/oauth/service/IClientService.java b/zlt-uaa/src/main/java/com/central/oauth/service/IClientService.java index d8d4f27..eed136a 100644 --- a/zlt-uaa/src/main/java/com/central/oauth/service/IClientService.java +++ b/zlt-uaa/src/main/java/com/central/oauth/service/IClientService.java @@ -24,4 +24,6 @@ public interface IClientService extends ISuperService { PageResult listClient(Map params, boolean isPage); void delClient(long id); + + Client loadClientByClientId(String clientId); } diff --git a/zlt-uaa/src/main/java/com/central/oauth/service/impl/ClientServiceImpl.java b/zlt-uaa/src/main/java/com/central/oauth/service/impl/ClientServiceImpl.java index 8e04cfd..5e01419 100644 --- a/zlt-uaa/src/main/java/com/central/oauth/service/impl/ClientServiceImpl.java +++ b/zlt-uaa/src/main/java/com/central/oauth/service/impl/ClientServiceImpl.java @@ -1,8 +1,8 @@ package com.central.oauth.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.central.common.constant.CommonConstant; import com.central.common.lock.DistributedLock; import com.central.common.redis.template.RedisRepository; import com.central.common.constant.SecurityConstants; @@ -72,6 +72,13 @@ public class ClientServiceImpl extends SuperServiceImpl im redisRepository.del(clientRedisKey(clientId)); } + @Override + public Client loadClientByClientId(String clientId) { + QueryWrapper wrapper = Wrappers.query(); + wrapper.eq("client_id", clientId); + return this.getOne(wrapper); + } + private String clientRedisKey(String clientId) { return SecurityConstants.CACHE_CLIENT_KEY + ":" + clientId; } diff --git a/zlt-uaa/src/main/java/com/central/oauth/utils/OidcIdTokenBuilder.java b/zlt-uaa/src/main/java/com/central/oauth/utils/OidcIdTokenBuilder.java new file mode 100644 index 0000000..1f732c5 --- /dev/null +++ b/zlt-uaa/src/main/java/com/central/oauth/utils/OidcIdTokenBuilder.java @@ -0,0 +1,111 @@ +package com.central.oauth.utils; + +import com.central.common.utils.JsonUtil; +import com.central.oauth2.common.util.JwtUtils; +import lombok.Getter; +import org.springframework.cloud.bootstrap.encrypt.KeyProperties; +import org.springframework.util.Assert; + +import java.util.*; + +import static com.central.oauth2.common.constants.IdTokenClaimNames.*; + +/** + * Oidc协议的IdToken + * + * @author zlt + * @version 1.0 + * @date 2021/4/21 + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +@Getter +public class OidcIdTokenBuilder { + private final Map claims; + private final KeyProperties keyProperties; + + private OidcIdTokenBuilder(KeyProperties keyProperties) { + this.claims = new LinkedHashMap<>(); + this.keyProperties = keyProperties; + } + + public static OidcIdTokenBuilder builder(KeyProperties keyProperties) { + Assert.notNull(keyProperties, "keyProperties required"); + return new OidcIdTokenBuilder(keyProperties); + } + + /** + * Use this claim in the resulting {@link OidcIdTokenBuilder} + * + * @param name The claim name + * @param value The claim value + */ + public OidcIdTokenBuilder claim(String name, Object value) { + this.claims.put(name, value); + return this; + } + + /** + * Use this audience in the resulting {@link OidcIdTokenBuilder} + * + * @param audience The audience to use + */ + public OidcIdTokenBuilder audience(String audience) { + return claim(AUD, audience); + } + + /** + * Use this expiration in the resulting {@link OidcIdTokenBuilder} + * + * @param expiresAt The expiration to use + */ + public OidcIdTokenBuilder expiresAt(long expiresAt) { + return this.claim(EXP, expiresAt); + } + + /** + * Use this issued-at timestamp in the resulting {@link OidcIdTokenBuilder} + * + * @param issuedAt The issued-at timestamp to use + */ + public OidcIdTokenBuilder issuedAt(long issuedAt) { + return this.claim(IAT, issuedAt); + } + + /** + * Use this issuer in the resulting {@link OidcIdTokenBuilder} + * + * @param issuer The issuer to use + */ + public OidcIdTokenBuilder issuer(String issuer) { + return this.claim(ISS, issuer); + } + + /** + * Use this nonce in the resulting {@link OidcIdTokenBuilder} + * + * @param nonce The nonce to use + */ + public OidcIdTokenBuilder nonce(String nonce) { + return this.claim(NONCE, nonce); + } + + /** + * Use this subject in the resulting {@link OidcIdTokenBuilder} + * + * @param subject The subject to use + */ + public OidcIdTokenBuilder subject(String subject) { + return this.claim(SUB, subject); + } + + /** + * Build the {@link OidcIdTokenBuilder} + * + * @return The constructed {@link OidcIdTokenBuilder} + */ + public String build() { + return JwtUtils.encodeStr(JsonUtil.toJSONString(claims), keyProperties); + } +} -- GitLab