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} 实现, + * 提供了更丰富的认证功能。 + *

+ * + *

主要功能:

+ * + * + *

使用场景:

+ *

+ * 适用于需要复杂认证逻辑的系统,特别是多租户、多客户端的应用场景。 + * 可以根据不同的客户端 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()); } + /** + * 执行额外的身份验证检查。 + *

+ * 该方法在用户详情加载完成后执行,主要进行以下验证: + *

+ *

+ * + * @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"); + } + } } + /** + * 根据用户名检索用户详情。 + *

+ * 该方法实现了智能的用户详情服务选择机制: + *

+ *

+ * + *

+ * 注意:该方法包含定时攻击保护机制,确保在用户不存在时 + * 仍然执行相同的时间消耗操作。 + *

+ * + * @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 } } + /** + * 创建成功的身份验证对象。 + *

+ * 该方法在身份验证成功后被调用,主要执行以下操作: + *

+ *

+ * + *

+ * 密码升级机制:当系统升级了密码编码算法时, + * 该方法会自动将用户的旧格式密码升级为新格式,提高系统安全性。 + *

+ * + * @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 authorities = AuthorityUtils - .createAuthorityList(dbAuthsSet.toArray(new String[0])); - AppUser user = info.getAppUser(); - // 构造security用户 + } + Collection 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 authorities = AuthorityUtils - .createAuthorityList(dbAuthsSet.toArray(new String[0])); - AppUser user = info.getAppUser(); - // 构造security用户 + } + Collection 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 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()); + } }