diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/PigxDaoAuthenticationProvider.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/PigxDaoAuthenticationProvider.java
index 628c30630..e9cb18079 100644
--- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/PigxDaoAuthenticationProvider.java
+++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/PigxDaoAuthenticationProvider.java
@@ -2,14 +2,17 @@ package com.pig4cloud.pigx.auth.support.core;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
+import com.pig4cloud.pigx.auth.support.filter.AuthSecurityConfigProperties;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.WebUtils;
+import com.pig4cloud.pigx.common.data.resolver.ParamResolver;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService;
import lombok.SneakyThrows;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
@@ -25,44 +28,97 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
import org.springframework.util.Assert;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
+ * Pigx 数据访问对象认证提供者
+ *
+ * 扩展自 Spring Security 的 {@link AbstractUserDetailsAuthenticationProvider},
+ * 仿照 {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider} 实现,
+ * 提供了更丰富的认证功能。
+ *
+ *
+ * 主要功能:
+ *
+ * - 1. 增加用户过期、锁定、凭证过期、禁用校验
+ * - 2. 增加密码过期校验,支持配置密码有效期
+ * - 3. 增加多种登录方式,灵活使用 UserDetailsService
+ * - 4. 支持 APP 模式免密码验证
+ * - 5. 提供定时攻击保护机制
+ * - 6. 支持多租户上下文设置
+ *
+ *
+ * 使用场景:
+ *
+ * 适用于需要复杂认证逻辑的系统,特别是多租户、多客户端的应用场景。
+ * 可以根据不同的客户端 ID 和授权类型选择合适的用户详情服务。
+ *
+ *
* @author lengleng
* @date 2022-06-04
+ * @version 1.0
+ * @since 1.0.0
*/
public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
- /**
- * The plaintext password used to perform PasswordEncoder#matches(CharSequence,
- * String)} on when the user is not found to avoid SEC-2056.
- */
+ /** 用户未找到时使用的默认密码字符串 */
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
+ /** 基础认证转换器,用于从请求中提取客户端凭证 */
private final static BasicAuthenticationConverter basicConvert = new BasicAuthenticationConverter();
+ /** 密码编码器,用于密码的编码和验证 */
private PasswordEncoder passwordEncoder;
- /**
- * The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
- * on when the user is not found to avoid SEC-2056. This is necessary, because some
- * {@link PasswordEncoder} implementations will short circuit if the password is not
- * in a valid format.
- */
+ /** 用户未找到时的编码密码,用于防止定时攻击 */
private volatile String userNotFoundEncodedPassword;
+ /** 用户详情服务,用于加载用户信息 */
private UserDetailsService userDetailsService;
+ /** 用户详情密码服务,用于密码升级 */
private UserDetailsPasswordService userDetailsPasswordService;
+ /**
+ * 构造函数,初始化认证提供者。
+ *
+ * 执行以下初始化操作:
+ *
+ * - 设置国际化消息源,用于错误信息的本地化
+ * - 配置委托密码编码器,支持多种密码编码格式
+ *
+ *
+ *
+ * @see org.springframework.security.crypto.factory.PasswordEncoderFactories#createDelegatingPasswordEncoder()
+ */
public PigxDaoAuthenticationProvider() {
setMessageSource(SpringUtil.getBean("securityMessageSource"));
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
+ /**
+ * 执行额外的身份验证检查。
+ *
+ * 该方法在用户详情加载完成后执行,主要进行以下验证:
+ *
+ * - 1. 检查授权类型,APP 模式下跳过密码验证
+ * - 2. 验证密码凭证是否为空
+ * - 3. 验证提供的密码是否与存储的密码匹配
+ * - 4. 检查密码是否过期(如果启用了密码过期策略)
+ *
+ *
+ *
+ * @param userDetails 表示用户的 UserDetails 对象,其凭证应该被检查
+ * @param authentication 认证请求,此时仍然不受信任
+ * @throws AuthenticationException 如果身份验证失败
+ * @throws BadCredentialsException 如果密码不匹配或为空
+ * @throws CredentialsExpiredException 如果密码已过期
+ */
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
@@ -84,8 +140,46 @@ public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthentica
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
+
+ // 密码过期判断,如果开启的
+ AuthSecurityConfigProperties configProperties = SpringUtil.getBean(AuthSecurityConfigProperties.class);
+ if (configProperties.isExpirePassword() && userDetails instanceof PigxUser pigxUser
+ && Objects.nonNull(pigxUser.getPasswordModifyTime())) {
+ Long expireDays = ParamResolver.getLong("PASSWORD_EXPIRE_DAYS", 90L);
+ long daysBetween = ChronoUnit.DAYS.between(pigxUser.getPasswordModifyTime(), LocalDateTime.now());
+ if (daysBetween > expireDays) {
+ this.logger.debug("Failed to authenticate since password expired");
+ throw new CredentialsExpiredException("User credentials have expired");
+ }
+ }
}
+ /**
+ * 根据用户名检索用户详情。
+ *
+ * 该方法实现了智能的用户详情服务选择机制:
+ *
+ * - 1. 从请求中提取授权类型和客户端 ID
+ * - 2. 如果客户端 ID 为空,则从 Basic 认证头中提取
+ * - 3. 根据客户端 ID 和授权类型选择合适的 UserDetailsService
+ * - 4. 按照优先级(Order)选择最合适的服务
+ * - 5. 加载用户详情并设置租户上下文(如果是多租户用户)
+ *
+ *
+ *
+ *
+ * 注意:该方法包含定时攻击保护机制,确保在用户不存在时
+ * 仍然执行相同的时间消耗操作。
+ *
+ *
+ * @param username 要检索的用户名
+ * @param authentication 认证请求,此时仍然不受信任
+ * @return 加载的用户详情对象
+ * @throws UsernameNotFoundException 如果用户名不存在
+ * @throws InternalAuthenticationServiceException 如果没有注册合适的 UserDetailsService 或服务返回 null
+ * @see PigxUserDetailsService#support(String, String)
+ * @see TenantContextHolder#setTenantId(Long)
+ */
@SneakyThrows
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
@@ -130,6 +224,30 @@ public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthentica
}
}
+ /**
+ * 创建成功的身份验证对象。
+ *
+ * 该方法在身份验证成功后被调用,主要执行以下操作:
+ *
+ * - 1. 检查是否需要升级密码编码格式
+ * - 2. 如果需要升级,使用新的编码格式重新编码密码
+ * - 3. 通过 UserDetailsPasswordService 更新用户密码
+ * - 4. 调用父类方法创建最终的认证对象
+ *
+ *
+ *
+ *
+ * 密码升级机制:当系统升级了密码编码算法时,
+ * 该方法会自动将用户的旧格式密码升级为新格式,提高系统安全性。
+ *
+ *
+ * @param principal 由认证提供者返回的主体对象
+ * @param authentication 原始的认证请求
+ * @param user 由认证提供者返回的 UserDetails 实例
+ * @return 成功的认证对象
+ * @see UserDetailsPasswordService#updatePassword(UserDetails, String)
+ * @see PasswordEncoder#upgradeEncoding(String)
+ */
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
UserDetails user) {
@@ -143,12 +261,51 @@ public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthentica
return super.createSuccessAuthentication(principal, authentication, user);
}
+ /**
+ * 准备定时攻击保护机制。
+ *
+ * 该方法为防止定时攻击做准备工作。定时攻击是一种安全攻击方式,
+ * 攻击者通过测量系统响应时间的差异来推断用户是否存在。
+ *
+ *
+ *
+ * 工作原理:
+ *
+ * - 预先编码一个固定的假密码
+ * - 当用户不存在时,使用这个假密码进行密码验证操作
+ * - 确保无论用户是否存在,密码验证的时间消耗基本一致
+ *
+ *
+ *
+ * @see #mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken)
+ */
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
+ /**
+ * 执行定时攻击缓解措施。
+ *
+ * 当用户不存在时,该方法会执行一次假的密码验证操作,
+ * 确保系统响应时间与真实用户验证时间保持一致,
+ * 防止攻击者通过响应时间差异判断用户是否存在。
+ *
+ *
+ *
+ * 安全意义:
+ * 没有这种保护机制时,攻击者可能会发现:
+ *
+ * - 用户存在时:查询数据库 + 密码验证 = 较长时间
+ * - 用户不存在时:仅返回错误 = 较短时间
+ *
+ * 通过时间差异,攻击者可以枚举出系统中存在的用户名。
+ *
+ *
+ * @param authentication 包含用户凭证的认证对象
+ * @see #prepareTimingAttackProtection()
+ */
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
@@ -156,16 +313,30 @@ public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthentica
}
}
- protected PasswordEncoder getPasswordEncoder() {
- return this.passwordEncoder;
- }
-
/**
- * Sets the PasswordEncoder instance to be used to encode and validate passwords. If
- * not set, the password will be compared using
- * {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
- * @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
- * types.
+ * 设置密码编码器实例,用于密码的编码和验证。
+ *
+ * 密码编码器负责以下功能:
+ *
+ * - 对明文密码进行安全编码(加密/哈希)
+ * - 验证用户输入的密码是否与存储的编码密码匹配
+ * - 支持密码编码格式的升级迁移
+ *
+ *
+ *
+ *
+ * 注意事项:
+ *
+ * - 如果不设置编码器,密码将以明文形式进行比较(不推荐)
+ * - 对于已经使用旧版本编码器的系统,建议使用兼容的编码器类型
+ * - 设置新编码器后,会重置用户未找到时的编码密码缓存
+ *
+ *
+ *
+ * @param passwordEncoder 密码编码器实例,必须是 {@code PasswordEncoder} 类型之一
+ * @throws IllegalArgumentException 如果 passwordEncoder 为 null
+ * @see org.springframework.security.crypto.password.PasswordEncoder
+ * @see org.springframework.security.crypto.factory.PasswordEncoderFactories
*/
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
@@ -173,16 +344,4 @@ public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthentica
this.userNotFoundEncodedPassword = null;
}
- protected UserDetailsService getUserDetailsService() {
- return this.userDetailsService;
- }
-
- public void setUserDetailsService(UserDetailsService userDetailsService) {
- this.userDetailsService = userDetailsService;
- }
-
- public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
- this.userDetailsPasswordService = userDetailsPasswordService;
- }
-
}
diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/AuthSecurityConfigProperties.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/AuthSecurityConfigProperties.java
index 4c4b547c4..319482b9f 100644
--- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/AuthSecurityConfigProperties.java
+++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/AuthSecurityConfigProperties.java
@@ -6,10 +6,10 @@ import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
/**
+ * 安全认证配置属性类
+ *
* @author lengleng
- * @date 2020/10/4
- *
- * 网关配置文件
+ * @date 2025/08/06
*/
@Data
@Component
@@ -27,4 +27,7 @@ public class AuthSecurityConfigProperties {
*/
private String encodeKey;
+ /** 是否开启密码超期强制修改密码 */
+ private boolean expirePassword = false;
+
}
diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocDefaultUserDetailsServiceImpl.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocDefaultUserDetailsServiceImpl.java
index 3ead93058..39ffeea7a 100755
--- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocDefaultUserDetailsServiceImpl.java
+++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocDefaultUserDetailsServiceImpl.java
@@ -52,92 +52,88 @@ import java.util.*;
@RequiredArgsConstructor
public class PigxTocDefaultUserDetailsServiceImpl implements PigxUserDetailsService {
+ private final CacheManager cacheManager;
- private final CacheManager cacheManager;
+ private final Optional remoteAppUserServiceOptional;
- private final Optional remoteAppUserServiceOptional;
+ /**
+ * 用户密码登录
+ * @param username 用户密码登录
+ * @return
+ */
+ @Override
+ @SneakyThrows
+ public UserDetails loadUserByUsername(String username) {
+ Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS_MINI);
+ if (Objects.nonNull(cache) && Objects.nonNull(cache.get(username))) {
+ AppUserInfo appUserInfo = cache.get(username, AppUserInfo.class);
+ return getUserDetailsAppUser(R.ok(appUserInfo));
+ }
- /**
- * 用户密码登录
- *
- * @param username 用户密码登录
- * @return
- */
- @Override
- @SneakyThrows
- public UserDetails loadUserByUsername(String username) {
- Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS_MINI);
- if (Objects.nonNull(cache) && Objects.nonNull(cache.get(username))) {
- AppUserInfo appUserInfo = cache.get(username, AppUserInfo.class);
- return getUserDetailsAppUser(R.ok(appUserInfo));
- }
+ R result = remoteAppUserServiceOptional.get().info(username);
+ Optional userInfoOptional = RetOps.of(result).getData();
+ userInfoOptional.ifPresent(userInfo -> Objects.requireNonNull(cache).put(username, userInfo));
+ return getUserDetailsAppUser(result);
+ }
- R result = remoteAppUserServiceOptional.get().info(username);
- Optional userInfoOptional = RetOps.of(result).getData();
- userInfoOptional.ifPresent(userInfo -> Objects.requireNonNull(cache).put(username, userInfo));
- return getUserDetailsAppUser(result);
- }
+ @Override
+ public UserDetails loadUserByUser(PigxUser pigxUser) {
+ return pigxUser;
+ }
- @Override
- public UserDetails loadUserByUser(PigxUser pigxUser) {
- return pigxUser;
- }
-
- UserDetails getUserDetailsAppUser(R result) {
- // @formatter:off
+ UserDetails getUserDetailsAppUser(R result) {
+ // @formatter:off
return RetOps.of(result)
.assertSuccess(r -> new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE),null)))
.getData()
.map(this::convertUserDetailsAppUser)
.orElseThrow(() -> new UsernameNotFoundException(MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE)));
// @formatter:on
- }
+ }
- /**
- * UserInfo 转 UserDetails
- *
- * @param info
- * @return 返回UserDetails对象
- */
- UserDetails convertUserDetailsAppUser(AppUserInfo info) {
- Set dbAuthsSet = new HashSet<>();
- if (ArrayUtil.isNotEmpty(info.getRoles())) {
- // 获取角色
- Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
- // 获取资源
- dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
+ /**
+ * UserInfo 转 UserDetails
+ * @param info
+ * @return 返回UserDetails对象
+ */
+ UserDetails convertUserDetailsAppUser(AppUserInfo info) {
+ Set dbAuthsSet = new HashSet<>();
+ if (ArrayUtil.isNotEmpty(info.getRoles())) {
+ // 获取角色
+ Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
+ // 获取资源
+ dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
- }
- Collection extends GrantedAuthority> authorities = AuthorityUtils
- .createAuthorityList(dbAuthsSet.toArray(new String[0]));
- AppUser user = info.getAppUser();
- // 构造security用户
+ }
+ Collection extends GrantedAuthority> authorities = AuthorityUtils
+ .createAuthorityList(dbAuthsSet.toArray(new String[0]));
+ AppUser user = info.getAppUser();
+ // 构造security用户
- return new PigxUser(user.getUserId(), user.getUsername(), null, user.getPhone(), user.getAvatar(),
- user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
- SecurityConstants.BCRYPT + user.getPassword(), true, true, UserTypeEnum.TOC.getStatus(), true,
- !CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
- }
+ return new PigxUser(user.getUserId(), user.getUsername(), null, user.getPhone(), user.getAvatar(),
+ user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
+ SecurityConstants.BCRYPT + user.getPassword(), true, true, UserTypeEnum.TOC.getStatus(), true, null,
+ !CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
+ }
- @Override
- public int getOrder() {
- return 10;
- }
+ @Override
+ public int getOrder() {
+ return 10;
+ }
- /**
- * 支持所有的 mobile 类型
- *
- * @param clientId 目标客户端
- * @param grantType 授权类型
- * @return true/false
- */
- @Override
- public boolean support(String clientId, String grantType) {
- if (Objects.isNull(WebUtils.getRequest())) {
- return false;
- }
- String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC);
- return SecurityConstants.HEADER_TOC_YES.equals(header);
- }
+ /**
+ * 支持所有的 mobile 类型
+ * @param clientId 目标客户端
+ * @param grantType 授权类型
+ * @return true/false
+ */
+ @Override
+ public boolean support(String clientId, String grantType) {
+ if (Objects.isNull(WebUtils.getRequest())) {
+ return false;
+ }
+ String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC);
+ return SecurityConstants.HEADER_TOC_YES.equals(header);
+ }
}
diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocMobileUserDetailsServiceImpl.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocMobileUserDetailsServiceImpl.java
index 01327a8b0..b0b6f5b48 100644
--- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocMobileUserDetailsServiceImpl.java
+++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocMobileUserDetailsServiceImpl.java
@@ -40,8 +40,6 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import java.util.*;
-import static com.pig4cloud.pigx.common.security.service.PigxTocDefaultUserDetailsServiceImpl.NOTFOUND_USER_ERROR_CODE;
-
/**
* 用户详细信息
*
@@ -51,85 +49,82 @@ import static com.pig4cloud.pigx.common.security.service.PigxTocDefaultUserDetai
@RequiredArgsConstructor
public class PigxTocMobileUserDetailsServiceImpl implements PigxUserDetailsService {
- private final Optional remoteAppUserServiceOptional;
+ private final Optional remoteAppUserServiceOptional;
- /**
- * 用户密码登录
- *
- * @param phone 用户密码登录
- * @return
- */
- @Override
- @SneakyThrows
- public UserDetails loadUserByUsername(String phone) {
- if (!remoteAppUserServiceOptional.isPresent()) {
- throw new UnsupportedOperationException();
- }
- R info = remoteAppUserServiceOptional.get().social(phone);
- return this.getUserDetailsAppUser(info);
- }
+ /**
+ * 用户密码登录
+ * @param phone 用户密码登录
+ * @return
+ */
+ @Override
+ @SneakyThrows
+ public UserDetails loadUserByUsername(String phone) {
+ if (!remoteAppUserServiceOptional.isPresent()) {
+ throw new UnsupportedOperationException();
+ }
+ R info = remoteAppUserServiceOptional.get().social(phone);
+ return this.getUserDetailsAppUser(info);
+ }
- @Override
- public UserDetails loadUserByUser(PigxUser pigxUser) {
- return pigxUser;
- }
+ @Override
+ public UserDetails loadUserByUser(PigxUser pigxUser) {
+ return pigxUser;
+ }
- UserDetails getUserDetailsAppUser(R result) {
- // @formatter:off
+ UserDetails getUserDetailsAppUser(R result) {
+ // @formatter:off
return RetOps.of(result)
.assertSuccess(r -> new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE),null)))
.getData()
.map(this::convertUserDetailsAppUser)
.orElseThrow(() -> new UsernameNotFoundException(MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE)));
// @formatter:on
- }
+ }
- /**
- * UserInfo 转 UserDetails
- *
- * @param info
- * @return 返回UserDetails对象
- */
- UserDetails convertUserDetailsAppUser(AppUserInfo info) {
- Set dbAuthsSet = new HashSet<>();
- if (ArrayUtil.isNotEmpty(info.getRoles())) {
- // 获取角色
- Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
- // 获取资源
- dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
+ /**
+ * UserInfo 转 UserDetails
+ * @param info
+ * @return 返回UserDetails对象
+ */
+ UserDetails convertUserDetailsAppUser(AppUserInfo info) {
+ Set dbAuthsSet = new HashSet<>();
+ if (ArrayUtil.isNotEmpty(info.getRoles())) {
+ // 获取角色
+ Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
+ // 获取资源
+ dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
- }
- Collection extends GrantedAuthority> authorities = AuthorityUtils
- .createAuthorityList(dbAuthsSet.toArray(new String[0]));
- AppUser user = info.getAppUser();
- // 构造security用户
+ }
+ Collection extends GrantedAuthority> authorities = AuthorityUtils
+ .createAuthorityList(dbAuthsSet.toArray(new String[0]));
+ AppUser user = info.getAppUser();
+ // 构造security用户
- return new PigxUser(user.getUserId(), user.getUsername(), null, user.getPhone(), user.getAvatar(),
- user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
- SecurityConstants.BCRYPT + user.getPassword(), true, true, UserTypeEnum.TOC.getStatus(), true,
- !CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
- }
+ return new PigxUser(user.getUserId(), user.getUsername(), null, user.getPhone(), user.getAvatar(),
+ user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
+ SecurityConstants.BCRYPT + user.getPassword(), true, true, UserTypeEnum.TOC.getStatus(), true, null,
+ !CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
+ }
- @Override
- public int getOrder() {
- return 15;
- }
+ @Override
+ public int getOrder() {
+ return 15;
+ }
- /**
- * 支持所有的 mobile 类型
- *
- * @param clientId 目标客户端
- * @param grantType 授权类型
- * @return true/false
- */
- @Override
- public boolean support(String clientId, String grantType) {
- if (Objects.isNull(WebUtils.getRequest())) {
- return false;
- }
+ /**
+ * 支持所有的 mobile 类型
+ * @param clientId 目标客户端
+ * @param grantType 授权类型
+ * @return true/false
+ */
+ @Override
+ public boolean support(String clientId, String grantType) {
+ if (Objects.isNull(WebUtils.getRequest())) {
+ return false;
+ }
- String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC);
- return SecurityConstants.HEADER_TOC_YES.equals(header) && SecurityConstants.GRANT_MOBILE.equals(grantType);
- }
+ String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC);
+ return SecurityConstants.HEADER_TOC_YES.equals(header) && SecurityConstants.GRANT_MOBILE.equals(grantType);
+ }
}
diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUser.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUser.java
index 429d0c7ab..8d7a5b48d 100644
--- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUser.java
+++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUser.java
@@ -26,6 +26,8 @@ import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
+import java.io.Serial;
+import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -36,6 +38,7 @@ import java.util.Map;
*/
public class PigxUser extends User implements OAuth2AuthenticatedPrincipal {
+ @Serial
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
/**
@@ -91,9 +94,14 @@ public class PigxUser extends User implements OAuth2AuthenticatedPrincipal {
@Getter
private String email;
+ /** 用户类型 */
@Getter
private String userType;
+ /** 密码修改时间 */
+ @Getter
+ private LocalDateTime passwordModifyTime;
+
/**
* Construct the User with the details required by
* {@link DaoAuthenticationProvider}.
@@ -125,6 +133,7 @@ public class PigxUser extends User implements OAuth2AuthenticatedPrincipal {
@JsonProperty("enabled") boolean enabled, @JsonProperty("accountNonExpired") boolean accountNonExpired,
@JsonProperty("userType") String userType,
@JsonProperty("credentialsNonExpired") boolean credentialsNonExpired,
+ @JsonProperty("passwordModifyTime") LocalDateTime passwordModifyTime,
@JsonProperty("accountNonLocked") boolean accountNonLocked,
@JsonProperty("authorities") Collection extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
@@ -137,6 +146,7 @@ public class PigxUser extends User implements OAuth2AuthenticatedPrincipal {
this.name = name;
this.email = email;
this.userType = userType;
+ this.passwordModifyTime = passwordModifyTime;
}
@Override
diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUserDetailsService.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUserDetailsService.java
index 887f98d1c..34e71b0d4 100644
--- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUserDetailsService.java
+++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUserDetailsService.java
@@ -40,85 +40,77 @@ import java.util.Set;
*/
public interface PigxUserDetailsService extends UserDetailsService, Ordered {
- /**
- * Notfound 用户错误代码
- */
- String NOTFOUND_USER_ERROR_CODE = "UserDetailsService.notFound";
+ /**
+ * Notfound 用户错误代码
+ */
+ String NOTFOUND_USER_ERROR_CODE = "UserDetailsService.notFound";
+ /**
+ * 是否支持此客户端校验
+ * @param clientId 请求客户端
+ * @param grantType 授权类型
+ * @return true/false
+ */
+ default boolean support(String clientId, String grantType) {
+ return true;
+ }
- /**
- * 是否支持此客户端校验
- *
- * @param clientId 请求客户端
- * @param grantType 授权类型
- * @return true/false
- */
- default boolean support(String clientId, String grantType) {
- return true;
- }
+ /**
+ * 排序值 默认取最大的
+ * @return 排序值
+ */
+ default int getOrder() {
+ return 0;
+ }
- /**
- * 排序值 默认取最大的
- *
- * @return 排序值
- */
- default int getOrder() {
- return 0;
- }
-
- /**
- * 获取用户详细信息
- *
- * @param userInfoOptional 用户信息:可选
- * @return {@link UserDetails }
- */
- default UserDetails getUserDetails(Optional userInfoOptional) {
- // @formatter:off
+ /**
+ * 获取用户详细信息
+ * @param userInfoOptional 用户信息:可选
+ * @return {@link UserDetails }
+ */
+ default UserDetails getUserDetails(Optional userInfoOptional) {
+ // @formatter:off
return userInfoOptional
.map(this::convertUserDetails)
.orElseThrow(() -> new UsernameNotFoundException(MsgUtils.getSecurityMessage(NOTFOUND_USER_ERROR_CODE)));
// @formatter:on
- }
+ }
- /**
- * UserInfo 转 UserDetails
- *
- * @param info
- * @return 返回UserDetails对象
- */
- default UserDetails convertUserDetails(UserInfo info) {
- Set dbAuthsSet = new HashSet<>();
+ /**
+ * UserInfo 转 UserDetails
+ * @param info
+ * @return 返回UserDetails对象
+ */
+ default UserDetails convertUserDetails(UserInfo info) {
+ Set dbAuthsSet = new HashSet<>();
- // 维护角色列表
- info.getRoleList().forEach(role -> dbAuthsSet.add(SecurityConstants.ROLE + role.getRoleId()));
+ // 维护角色列表
+ info.getRoleList().forEach(role -> dbAuthsSet.add(SecurityConstants.ROLE + role.getRoleId()));
- // 维护权限列表
- dbAuthsSet.addAll(info.getPermissions());
- Collection authorities = AuthorityUtils
- .createAuthorityList(dbAuthsSet.toArray(new String[0]));
+ // 维护权限列表
+ dbAuthsSet.addAll(info.getPermissions());
+ Collection authorities = AuthorityUtils
+ .createAuthorityList(dbAuthsSet.toArray(new String[0]));
+ // 构造security用户
+ Long deptId = null;
+ if (CollUtil.isNotEmpty(info.getDeptList())) {
+ deptId = CollUtil.getFirst(info.getDeptList()).getDeptId();
+ }
+ return new PigxUser(info.getUserId(), info.getUsername(), deptId, info.getPhone(), info.getAvatar(),
+ info.getNickname(), info.getName(), info.getEmail(), info.getTenantId(),
+ SecurityConstants.BCRYPT + info.getPassword(), true, true, UserTypeEnum.TOB.getStatus(),
+ !CommonConstants.STATUS_LOCK.equals(info.getPasswordExpireFlag()) // 密码过期判断
+ , info.getPasswordModifyTime(), !CommonConstants.STATUS_LOCK.equals(info.getLockFlag()), authorities);
+ }
- // 构造security用户
- Long deptId = null;
- if (CollUtil.isNotEmpty(info.getDeptList())) {
- deptId = CollUtil.getFirst(info.getDeptList()).getDeptId();
- }
- return new PigxUser(info.getUserId(), info.getUsername(), deptId, info.getPhone(), info.getAvatar(),
- info.getNickname(), info.getName(), info.getEmail(), info.getTenantId(),
- SecurityConstants.BCRYPT + info.getPassword()
- , true, true, UserTypeEnum.TOB.getStatus()
- , !CommonConstants.STATUS_LOCK.equals(info.getPasswordExpireFlag()) // 密码过期判断
- , !CommonConstants.STATUS_LOCK.equals(info.getLockFlag()), authorities);
- }
-
- /**
- * 通过用户实体查询
- *
- * @param pigxUser user
- * @return
- */
- default UserDetails loadUserByUser(PigxUser pigxUser) {
- return this.loadUserByUsername(pigxUser.getUsername());
- }
+ /**
+ * 通过用户实体查询
+ * @param pigxUser user
+ * @return
+ */
+ default UserDetails loadUserByUser(PigxUser pigxUser) {
+ return this.loadUserByUsername(pigxUser.getUsername());
+ }
}