mirror of
https://gitee.com/NexIoT/Universal-IoT-Java.git
synced 2026-10-31 16:01:28 +08:00
270 lines
11 KiB
Java
270 lines
11 KiB
Java
/*
|
|
*
|
|
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
|
*
|
|
* @Description: 本文件由 gitee.com/NexIoTXin 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
|
* @Author: gitee.com/NexIoTXin
|
|
* @Email: wo8335224@gmail.com
|
|
* @Wechat: outlookFil
|
|
*
|
|
*
|
|
*/
|
|
|
|
package cn.universal.security;
|
|
|
|
import cn.universal.persistence.entity.OAuth2ClientDetails;
|
|
import cn.universal.persistence.mapper.OauthClientDetailsMapper;
|
|
import java.time.Duration;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.stream.Collectors;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.context.annotation.Primary;
|
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
|
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
|
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
|
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.util.StringUtils;
|
|
|
|
@Service
|
|
@Primary
|
|
public class CustomClientRegistrationService implements RegisteredClientRepository {
|
|
|
|
private static final Logger logger =
|
|
LoggerFactory.getLogger(CustomClientRegistrationService.class);
|
|
private String DEGAULT_CLIENT_ID = "web";
|
|
private final OauthClientDetailsMapper oauthClientDetailsMapper;
|
|
private final InMemoryRegisteredClientRepository fallbackRepository;
|
|
|
|
public CustomClientRegistrationService(
|
|
@Autowired(required = false) OauthClientDetailsMapper oauthClientDetailsMapper) {
|
|
this.oauthClientDetailsMapper = oauthClientDetailsMapper;
|
|
|
|
logger.info(
|
|
"CustomClientRegistrationService 初始化,oauthClientDetailsMapper: {}",
|
|
oauthClientDetailsMapper != null ? "可用" : "不可用");
|
|
|
|
// 创建回退的内存客户端配置,保持与原有系统的兼容性
|
|
RegisteredClient univIotClient =
|
|
RegisteredClient.withId(UUID.randomUUID().toString())
|
|
.clientId("web")
|
|
.clientSecret("{noop}web")
|
|
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
|
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
|
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
|
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
|
.authorizationGrantType(new AuthorizationGrantType("password"))
|
|
.authorizationGrantType(new AuthorizationGrantType("captcha"))
|
|
.authorizationGrantType(new AuthorizationGrantType("sms"))
|
|
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/cn-universal-client")
|
|
.redirectUri("http://127.0.0.1:8080/authorized")
|
|
.scope("read")
|
|
.scope("write")
|
|
.scope("all")
|
|
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
|
|
.tokenSettings(
|
|
TokenSettings.builder()
|
|
.accessTokenTimeToLive(Duration.ofHours(1))
|
|
.refreshTokenTimeToLive(Duration.ofDays(30))
|
|
.build())
|
|
.build();
|
|
|
|
this.fallbackRepository = new InMemoryRegisteredClientRepository(univIotClient);
|
|
logger.info("内存回退客户端配置已创建,client_id: web");
|
|
}
|
|
|
|
@Override
|
|
public void save(RegisteredClient registeredClient) {
|
|
// 这里可以实现保存逻辑,暂时不实现
|
|
}
|
|
|
|
@Override
|
|
public RegisteredClient findById(String id) {
|
|
logger.debug("查找客户端 by ID: {}", id);
|
|
|
|
// 首先尝试从数据库查找,排除web
|
|
if (oauthClientDetailsMapper != null && !DEGAULT_CLIENT_ID.equals(id)) {
|
|
OAuth2ClientDetails clientDetails = findByClientIdFromDb(id);
|
|
if (clientDetails != null) {
|
|
logger.debug("从数据库找到客户端: {}", clientDetails.getClientId());
|
|
return convertToRegisteredClient(clientDetails);
|
|
}
|
|
}
|
|
|
|
// 如果数据库中没有找到,回退到内存配置
|
|
logger.debug("数据库中没有找到客户端,回退到内存配置");
|
|
return fallbackRepository.findById(id);
|
|
}
|
|
|
|
@Override
|
|
public RegisteredClient findByClientId(String clientId) {
|
|
logger.debug("查找客户端 by ClientId: {}", clientId);
|
|
|
|
// 首先尝试从数据库查找
|
|
if (oauthClientDetailsMapper != null && !DEGAULT_CLIENT_ID.equals(clientId)) {
|
|
OAuth2ClientDetails clientDetails = findByClientIdFromDb(clientId);
|
|
if (clientDetails != null) {
|
|
logger.debug(
|
|
"从数据库找到客户端: {}, 授权类型: {}",
|
|
clientDetails.getClientId(),
|
|
clientDetails.getAuthorizedGrantTypes());
|
|
return convertToRegisteredClient(clientDetails);
|
|
} else {
|
|
logger.debug("数据库中没有找到客户端: {}", clientId);
|
|
}
|
|
} else {
|
|
logger.debug("oauthClientDetailsMapper 不可用,跳过数据库查询");
|
|
}
|
|
|
|
// 如果数据库中没有找到,回退到内存配置
|
|
logger.debug("回退到内存配置查找客户端: {}", clientId);
|
|
RegisteredClient fallbackClient = fallbackRepository.findByClientId(clientId);
|
|
if (fallbackClient != null) {
|
|
logger.debug("从内存配置找到客户端: {}", clientId);
|
|
} else {
|
|
logger.debug("内存配置中也没有找到客户端: {}", clientId);
|
|
}
|
|
return fallbackClient;
|
|
}
|
|
|
|
private OAuth2ClientDetails findByClientIdFromDb(String clientId) {
|
|
try {
|
|
// 使用 selectOne 方法查询
|
|
OAuth2ClientDetails query = new OAuth2ClientDetails();
|
|
query.setClientId(clientId);
|
|
OAuth2ClientDetails result = oauthClientDetailsMapper.selectOne(query);
|
|
logger.debug("数据库查询结果: {}", result != null ? "找到" : "未找到");
|
|
return result;
|
|
} catch (Exception e) {
|
|
logger.error("数据库查询异常: {}", e.getMessage(), e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private RegisteredClient convertToRegisteredClient(OAuth2ClientDetails clientDetails) {
|
|
logger.debug("转换客户端配置: {}", clientDetails.getClientId());
|
|
|
|
// 解析授权类型
|
|
Set<String> authorizedGrantTypes =
|
|
parseCommaDelimitedString(clientDetails.getAuthorizedGrantTypes());
|
|
|
|
// 解析作用域
|
|
Set<String> scopes = parseCommaDelimitedString(clientDetails.getScope());
|
|
|
|
// 解析重定向URI
|
|
Set<String> redirectUris = parseCommaDelimitedString(clientDetails.getWebServerRedirectUri());
|
|
|
|
// 解析权限
|
|
Set<String> authorities = parseCommaDelimitedString(clientDetails.getAuthorities());
|
|
|
|
logger.debug(
|
|
"解析结果 - 授权类型: {}, 作用域: {}, 重定向URI: {}", authorizedGrantTypes, scopes, redirectUris);
|
|
|
|
// 检查客户端密钥格式
|
|
String clientSecret = clientDetails.getClientSecret();
|
|
logger.debug(
|
|
"客户端密钥: {}",
|
|
clientSecret != null
|
|
? clientSecret.substring(0, Math.min(10, clientSecret.length())) + "..."
|
|
: "null");
|
|
|
|
// 处理不同格式的客户端密钥
|
|
if (clientSecret != null && !DEGAULT_CLIENT_ID.equalsIgnoreCase(clientSecret)) {
|
|
if (clientSecret.startsWith("$2a$")
|
|
|| clientSecret.startsWith("$2b$")
|
|
|| clientSecret.startsWith("$2y$")) {
|
|
// BCrypt 格式,添加 {bcrypt} 前缀
|
|
clientSecret = "{bcrypt}" + clientSecret;
|
|
logger.debug(
|
|
"添加 {bcrypt} 前缀后的客户端密钥: {}",
|
|
clientSecret.substring(0, Math.min(15, clientSecret.length())) + "...");
|
|
} else if (!clientSecret.startsWith("{noop}")
|
|
&& !clientSecret.startsWith("{bcrypt}")
|
|
&& !clientSecret.startsWith("{pbkdf2}")
|
|
&& !clientSecret.startsWith("{scrypt}")) {
|
|
// 明文密码,添加 {noop} 前缀
|
|
clientSecret = "{noop}" + clientSecret;
|
|
logger.debug(
|
|
"添加 {noop} 前缀后的客户端密钥: {}",
|
|
clientSecret.substring(0, Math.min(15, clientSecret.length())) + "...");
|
|
}
|
|
}
|
|
|
|
// 构建客户端设置
|
|
ClientSettings.Builder clientSettingsBuilder =
|
|
ClientSettings.builder()
|
|
.requireAuthorizationConsent("1".equals(clientDetails.getAutoapprove()));
|
|
|
|
// 构建令牌设置
|
|
TokenSettings.Builder tokenSettingsBuilder = TokenSettings.builder();
|
|
if (clientDetails.getAccessTokenValidity() != null) {
|
|
tokenSettingsBuilder.accessTokenTimeToLive(
|
|
Duration.ofSeconds(clientDetails.getAccessTokenValidity()));
|
|
}
|
|
if (clientDetails.getRefreshTokenValidity() != null) {
|
|
tokenSettingsBuilder.refreshTokenTimeToLive(
|
|
Duration.ofSeconds(clientDetails.getRefreshTokenValidity()));
|
|
}
|
|
|
|
RegisteredClient registeredClient =
|
|
RegisteredClient.withId(clientDetails.getClientId())
|
|
.clientId(clientDetails.getClientId())
|
|
.clientSecret(clientSecret)
|
|
.clientAuthenticationMethods(
|
|
authenticationMethods -> {
|
|
if (authorizedGrantTypes.contains("client_credentials")) {
|
|
authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
|
authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
|
|
}
|
|
})
|
|
.authorizationGrantTypes(
|
|
authorizationGrantTypes -> {
|
|
if (authorizedGrantTypes.contains("password")) {
|
|
authorizationGrantTypes.add(AuthorizationGrantType.PASSWORD);
|
|
}
|
|
if (authorizedGrantTypes.contains("authorization_code")) {
|
|
authorizationGrantTypes.add(AuthorizationGrantType.AUTHORIZATION_CODE);
|
|
}
|
|
if (authorizedGrantTypes.contains("refresh_token")) {
|
|
authorizationGrantTypes.add(AuthorizationGrantType.REFRESH_TOKEN);
|
|
}
|
|
if (authorizedGrantTypes.contains("client_credentials")) {
|
|
authorizationGrantTypes.add(AuthorizationGrantType.CLIENT_CREDENTIALS);
|
|
}
|
|
})
|
|
.redirectUris(redirectUris::addAll)
|
|
.scopes(scopes::addAll)
|
|
.clientSettings(clientSettingsBuilder.build())
|
|
.tokenSettings(tokenSettingsBuilder.build())
|
|
.build();
|
|
|
|
logger.debug(
|
|
"创建的 RegisteredClient: clientId={}, hasClientSecret={}, authMethods={}, grantTypes={}",
|
|
registeredClient.getClientId(),
|
|
registeredClient.getClientSecret() != null,
|
|
registeredClient.getClientAuthenticationMethods(),
|
|
registeredClient.getAuthorizationGrantTypes());
|
|
|
|
return registeredClient;
|
|
}
|
|
|
|
private Set<String> parseCommaDelimitedString(String value) {
|
|
if (!StringUtils.hasText(value)) {
|
|
return Collections.emptySet();
|
|
}
|
|
return Arrays.stream(value.split(","))
|
|
.map(String::trim)
|
|
.filter(StringUtils::hasText)
|
|
.collect(Collectors.toSet());
|
|
}
|
|
}
|