提交 7431a35e 编写于 作者: zlt2000's avatar zlt2000

增加OIDC协议支持

上级 c1452394
package com.central.oauth2.common.constants;
/**
* id_token属性名常量
*
* @author zlt
* @version 1.0
* @date 2021/4/23
* <p>
* 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";
}
......@@ -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();
}
}
......@@ -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";
}
......@@ -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;
......
------------更新语句
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
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<String> responseTypes = authentication.getOAuth2Request().getResponseTypes();
if (responseTypes.contains(SecurityConstants.ID_TOKEN)
|| "authJwt".equals(tokenStoreType)) {
Map<String, Object> 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<String, Object> 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);
}
}
}
......@@ -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;
}
......@@ -24,4 +24,6 @@ public interface IClientService extends ISuperService<Client> {
PageResult<Client> listClient(Map<String, Object> params, boolean isPage);
void delClient(long id);
Client loadClientByClientId(String clientId);
}
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<ClientMapper, Client> im
redisRepository.del(clientRedisKey(clientId));
}
@Override
public Client loadClientByClientId(String clientId) {
QueryWrapper<Client> wrapper = Wrappers.query();
wrapper.eq("client_id", clientId);
return this.getOne(wrapper);
}
private String clientRedisKey(String clientId) {
return SecurityConstants.CACHE_CLIENT_KEY + ":" + clientId;
}
......
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
* <p>
* Blog: https://zlt2000.gitee.io
* Github: https://github.com/zlt2000
*/
@Getter
public class OidcIdTokenBuilder {
private final Map<String, Object> 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);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册