diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java index e7f454003..6a6a9ca31 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java @@ -21,6 +21,7 @@ import com.pig4cloud.pigx.auth.support.core.CustomeOAuth2TokenCustomizer; import com.pig4cloud.pigx.auth.support.core.FormIdentityLoginConfigurer; import com.pig4cloud.pigx.auth.support.core.PigxDaoAuthenticationProvider; import com.pig4cloud.pigx.auth.support.core.PigxOAuth2RefreshTokenAuthenticationConverter; +import com.pig4cloud.pigx.auth.support.filter.FormLoginValidateCodeFilter; import com.pig4cloud.pigx.auth.support.filter.PasswordDecoderFilter; import com.pig4cloud.pigx.auth.support.filter.ValidateCodeFilter; import com.pig4cloud.pigx.auth.support.handler.PigxAuthenticationFailureEventHandler; @@ -41,14 +42,13 @@ import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.SecurityFilterChain; @@ -78,6 +78,8 @@ public class AuthorizationServerConfiguration { private final ValidateCodeFilter validateCodeFilter; + private final FormLoginValidateCodeFilter formLoginValidateCodeFilter; + private final PigxBootCorsProperties corsProperties; @Bean @@ -91,6 +93,8 @@ public class AuthorizationServerConfiguration { // 增加验证码过滤器 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); + // 增加授权码登录表单验证码过滤器 + http.addFilterBefore(formLoginValidateCodeFilter, UsernamePasswordAuthenticationFilter.class); // 增加密码解密过滤器 http.addFilterBefore(passwordDecoderFilter, UsernamePasswordAuthenticationFilter.class); @@ -163,15 +167,13 @@ public class AuthorizationServerConfiguration { * * @return DelegatingAuthenticationConverter */ - @Bean - public AuthenticationConverter accessTokenRequestConverter() { + private AuthenticationConverter accessTokenRequestConverter() { return new DelegatingAuthenticationConverter( Arrays.asList(new OAuth2ResourceOwnerPasswordAuthenticationConverter(), new OAuth2ResourceOwnerSmsAuthenticationConverter(), new PigxOAuth2RefreshTokenAuthenticationConverter(), new OAuth2ClientCredentialsAuthenticationConverter(), - new OAuth2AuthorizationCodeAuthenticationConverter(), - new OAuth2AuthorizationCodeRequestAuthenticationConverter())); + new OAuth2AuthorizationCodeAuthenticationConverter())); } /** diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/CaptchaCacheServiceProvider.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/CaptchaCacheServiceProvider.java deleted file mode 100644 index 1a9945e34..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/CaptchaCacheServiceProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.pig4cloud.pigx.auth.endpoint; - -import com.anji.captcha.service.CaptchaCacheService; -import com.pig4cloud.pigx.common.data.cache.RedisUtils; - -/** - * @author lengleng - * @date 2020/8/27 - *

- * 验证码 缓存提供支持集群,需要通过SPI - */ -public class CaptchaCacheServiceProvider implements CaptchaCacheService { - - private static final String REDIS = "redis"; - - @Override - public void set(String key, String value, long expiresInSeconds) { - RedisUtils.set(key, value, expiresInSeconds); - } - - @Override - public boolean exists(String key) { - return RedisUtils.hasKey(key); - } - - @Override - public void delete(String key) { - RedisUtils.delete(key); - } - - @Override - public String get(String key) { - return RedisUtils.get(key); - } - - @Override - public String type() { - return REDIS; - } - - @Override - public Long increment(String key, long val) { - return RedisUtils.increment(key, val); - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/ImageCodeEndpoint.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/ImageCodeEndpoint.java index c38778b9a..cf5a53e55 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/ImageCodeEndpoint.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/ImageCodeEndpoint.java @@ -78,6 +78,6 @@ public class ImageCodeEndpoint { CaptchaService captchaService = SpringContextHolder.getBean(CaptchaService.class); return R.ok(captchaService.check(vo)); - } + } } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigxTokenEndpoint.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigxTokenEndpoint.java index dc7adfdcf..27f6c3e79 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigxTokenEndpoint.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigxTokenEndpoint.java @@ -19,6 +19,7 @@ package com.pig4cloud.pigx.auth.endpoint; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.TemporalAccessorUtil; import cn.hutool.core.map.MapUtil; +import cn.hutool.json.JSONUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pigx.admin.api.entity.SysOauthClientDetails; @@ -26,6 +27,7 @@ import com.pig4cloud.pigx.admin.api.entity.SysTenant; import com.pig4cloud.pigx.admin.api.feign.RemoteClientDetailsService; import com.pig4cloud.pigx.admin.api.feign.RemoteTenantService; import com.pig4cloud.pigx.admin.api.vo.TokenVO; +import com.pig4cloud.pigx.auth.support.core.AuthCaptchaSupport; import com.pig4cloud.pigx.auth.support.core.AuthErrorCodes; import com.pig4cloud.pigx.common.core.constant.CacheConstants; import com.pig4cloud.pigx.common.core.constant.CommonConstants; @@ -61,6 +63,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import java.security.Principal; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -76,6 +79,8 @@ import java.util.Set; @RequiredArgsConstructor public class PigxTokenEndpoint { + private static final String DEFAULT_FORM_CLIENT_ID = "pig"; + private final OAuth2AuthorizationService authorizationService; private final RemoteClientDetailsService clientDetailsService; @@ -84,6 +89,8 @@ public class PigxTokenEndpoint { private final CacheManager cacheManager; + private final AuthCaptchaSupport authCaptchaSupport; + /** * 认证页面 * @@ -92,13 +99,24 @@ public class PigxTokenEndpoint { * @return ModelAndView */ @GetMapping("/token/login") - public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) { + public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error, + HttpServletRequest request, HttpServletResponse response) { modelAndView.setViewName("ftl/login"); // Note: XSS prevention is handled by FreeMarker template using ?html directive modelAndView.addObject("error", error); - R> tenantList = tenantService.list(); - modelAndView.addObject("tenantList", tenantList.getData()); + List tenantList = Objects.requireNonNullElse(tenantService.list().getData(), List.of()); + String authClientId = StrUtil.blankToDefault( + authCaptchaSupport.resolveAuthorizationClientId(request, response, true), DEFAULT_FORM_CLIENT_ID); + Long selectedTenantId = resolveSelectedTenantId(request, response, tenantList); + Map tenantCaptchaEnabledMap = buildTenantCaptchaEnabledMap(tenantList, authClientId); + + modelAndView.addObject("tenantList", tenantList); + modelAndView.addObject("authClientId", authClientId); + modelAndView.addObject("selectedTenantId", selectedTenantId); + modelAndView.addObject("showCaptcha", + Boolean.TRUE.equals(tenantCaptchaEnabledMap.get(String.valueOf(selectedTenantId)))); + modelAndView.addObject("tenantCaptchaEnabledJson", JSONUtil.toJsonStr(tenantCaptchaEnabledMap)); return modelAndView; } @@ -256,7 +274,33 @@ public class PigxTokenEndpoint { @GetMapping("/token/query-token") public R queryToken(String token) { OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); - return R.ok(authorization); - } + return R.ok(authorization); + } + + private Long resolveSelectedTenantId(HttpServletRequest request, HttpServletResponse response, List tenantList) { + String tenantId = authCaptchaSupport.resolveAuthorizationTenantId(request, response, true); + if (StrUtil.isNotBlank(tenantId)) { + try { + return Long.parseLong(tenantId); + } catch (NumberFormatException ignored) { + // ignore invalid tenant id and fall back to the default selection below + } + } + + if (!tenantList.isEmpty()) { + return tenantList.get(0).getId(); + } + + return CommonConstants.TENANT_ID_1; + } + + private Map buildTenantCaptchaEnabledMap(List tenantList, String authClientId) { + Map result = new LinkedHashMap<>(tenantList.size()); + for (SysTenant tenant : tenantList) { + String tenantId = String.valueOf(tenant.getId()); + result.put(tenantId, authCaptchaSupport.isCaptchaEnabled(tenantId, authClientId)); + } + return result; + } } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java index 4d888c20e..68c650012 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java @@ -183,7 +183,7 @@ public abstract class OAuth2ResourceOwnerBaseAuthenticationProvider authorizedScopes, Authentication usernamePasswordAuthentication) { - // @formatter:off + // @formatter:off DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() .registeredClient(registeredClient) .principal(usernamePasswordAuthentication) @@ -319,10 +319,10 @@ public abstract class OAuth2ResourceOwnerBaseAuthenticationProvider + * 统一处理验证码开关判断、授权客户端解析和验证码校验,避免在多个过滤器和控制器中重复实现。 + *

+ * + * @author pig + * @date 2026-03-20 + */ +@Component +@RequiredArgsConstructor +public class AuthCaptchaSupport { + + private final RequestCache requestCache = new HttpSessionRequestCache(); + + private final CaptchaValidator captchaValidator; + + /** + * 解析当前授权流程中的真实客户端ID + * + * @param request 当前请求 + * @param response 当前响应 + * @param includeCurrentRequestId 是否优先读取当前请求参数中的 client_id + * @return 授权客户端ID + */ + public String resolveAuthorizationClientId(HttpServletRequest request, HttpServletResponse response, + boolean includeCurrentRequestId) { + return resolveAuthorizationParameter(request, response, OAuth2ParameterNames.CLIENT_ID, includeCurrentRequestId); + } + + /** + * 解析当前授权流程中的租户ID + * + * @param request 当前请求 + * @param response 当前响应 + * @param includeCurrentRequestId 是否优先读取当前请求参数中的租户ID + * @return 租户ID + */ + public String resolveAuthorizationTenantId(HttpServletRequest request, HttpServletResponse response, + boolean includeCurrentRequestId) { + return resolveAuthorizationParameter(request, response, CommonConstants.TENANT_ID, includeCurrentRequestId); + } + + /** + * 判断客户端是否开启验证码 + * + * @param tenantId 租户ID + * @param clientId 客户端ID + * @return true 开启验证码 + */ + public boolean isCaptchaEnabled(String tenantId, String clientId) { + if (StrUtil.isBlank(clientId)) { + return false; + } + + String finalTenantId = StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1.toString() : tenantId; + String key = String.format("%s:%s:%s", finalTenantId, CacheConstants.CLIENT_FLAG, clientId); + String val = RedisUtils.get(key); + + if (val == null) { + return true; + } + + JSONObject information = JSONUtil.parseObj(val); + return !StrUtil.equals(CaptchaFlagTypeEnum.OFF.getType(), information.getStr(CommonConstants.CAPTCHA_FLAG)); + } + + /** + * 校验请求中的验证码 + * + * @param request 当前请求 + * @throws ValidateCodeException 验证码校验失败 + */ + public void validateCode(HttpServletRequest request) throws ValidateCodeException { + String code = request.getParameter("code"); + if (StrUtil.isBlank(code)) { + throw new ValidateCodeException(MsgUtils.getMessage(AuthErrorCodes.AUTH_CAPTCHA_EMPTY)); + } + + String randomStr = request.getParameter("randomStr"); + // https://gitee.com/log4j/pig/issues/IWA0D 手机号场景以 mobile 作为缓存键 + String mobile = request.getParameter("mobile"); + if (StrUtil.isNotBlank(mobile)) { + randomStr = mobile; + } + + boolean isBehavior = StrUtil.equalsAnyIgnoreCase(randomStr, CommonConstants.IMAGE_CODE_BLOCK_PUZZLE, + CommonConstants.IMAGE_CODE_CLICK_WORD); + String captchaType = isBehavior ? randomStr : CommonConstants.IMAGE_CODE_MATH; + String captchaVerification = isBehavior ? code + : randomStr + CommonConstants.CAPTCHA_VERIFICATION_SEPARATOR + code; + + CaptchaResult result = captchaValidator.validate(captchaType, captchaVerification); + if (!result.isOk()) { + String errorCode = isBehavior ? AuthErrorCodes.AUTH_CAPTCHA_EMPTY : AuthErrorCodes.AUTH_CAPTCHA_INVALID; + throw new ValidateCodeException(MsgUtils.getMessage(errorCode)); + } + } + + private String resolveAuthorizationParameter(HttpServletRequest request, HttpServletResponse response, + String parameterName, boolean includeCurrentRequestId) { + if (includeCurrentRequestId) { + String parameterValue = request.getParameter(parameterName); + if (StrUtil.isNotBlank(parameterValue)) { + return parameterValue; + } + } + + SavedRequest savedRequest = requestCache.getRequest(request, response); + if (Objects.isNull(savedRequest)) { + return null; + } + + String[] parameterValues = savedRequest.getParameterValues(parameterName); + if (Objects.isNull(parameterValues) || parameterValues.length == 0) { + return null; + } + + return parameterValues[0]; + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/FormIdentityLoginConfigurer.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/FormIdentityLoginConfigurer.java index b526f630b..b4837a58c 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/FormIdentityLoginConfigurer.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/FormIdentityLoginConfigurer.java @@ -16,7 +16,7 @@ public final class FormIdentityLoginConfigurer extends AbstractHttpConfigurer { @Override - public void init(HttpSecurity http) throws Exception { + public void init(HttpSecurity http) { http.formLogin(formLogin -> { formLogin.loginPage("/token/login"); formLogin.loginProcessingUrl("/oauth2/form"); 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 319482b9f..18a6752b6 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 @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component; * 安全认证配置属性类 * * @author lengleng - * @date 2025/08/06 + * @date 2026/05/03 */ @Data @Component @@ -17,17 +17,39 @@ import org.springframework.stereotype.Component; @ConfigurationProperties("security") public class AuthSecurityConfigProperties { - /** - * 是否是微服务架构 - */ - private boolean isMicro; + /** + * 是否是微服务架构 + */ + private boolean isMicro; + + /** + * 认证中心解密登录前端密码的密钥。AES 使用 16 字节字符串,SM4 使用 32 位 HEX 字符串。 + */ + private String encodeKey; /** - * 网关解密登录前端密码 秘钥 + * 认证中心解密登录前端密码的算法,默认使用 AES 以兼容历史配置。 */ - private String encodeKey; + private EncodeType encodeType = EncodeType.AES; - /** 是否开启密码超期强制修改密码 */ + /** + * 是否开启密码超期强制修改密码,默认关闭。 + */ private boolean expirePassword = false; + /** + * 登录密码加密算法枚举 + *

+ * AES:使用 16 字节 UTF-8 字符串作为密钥,CFB / NoPadding 模式,IV 等于密钥。
+ * SM4:国密 SM4 算法,使用 32 位 HEX 字符串作为密钥(解码后 16 字节)。 + */ + public enum EncodeType { + + /** AES 算法(默认) */ + AES, + /** 国密 SM4 算法 */ + SM4 + + } + } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/FormLoginValidateCodeFilter.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/FormLoginValidateCodeFilter.java new file mode 100644 index 000000000..2f236b15b --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/FormLoginValidateCodeFilter.java @@ -0,0 +1,65 @@ +package com.pig4cloud.pigx.auth.support.filter; + +import com.pig4cloud.pigx.auth.support.core.AuthCaptchaSupport; +import com.pig4cloud.pigx.auth.support.handler.FormAuthenticationFailureHandler; +import com.pig4cloud.pigx.common.core.constant.CommonConstants; +import com.pig4cloud.pigx.common.core.exception.ValidateCodeException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * 授权码登录表单验证码过滤器 + * + *

+ * 仅处理 /oauth2/form 请求,复用现有图形验证码能力,对授权码登录页补充验证码校验。 + *

+ * + * @author pig + * @date 2026-03-20 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class FormLoginValidateCodeFilter extends OncePerRequestFilter { + + private static final String FORM_LOGIN_URL = "/oauth2/form"; + + private final AuthCaptchaSupport authCaptchaSupport; + + private final FormAuthenticationFailureHandler failureHandler = new FormAuthenticationFailureHandler(); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + if (!FORM_LOGIN_URL.equals(request.getServletPath())) { + filterChain.doFilter(request, response); + return; + } + + String authClientId = authCaptchaSupport.resolveAuthorizationClientId(request, response, true); + String tenantId = authCaptchaSupport.resolveAuthorizationTenantId(request, response, true); + + if (!authCaptchaSupport.isCaptchaEnabled(tenantId, authClientId)) { + filterChain.doFilter(request, response); + return; + } + + try { + authCaptchaSupport.validateCode(request); + filterChain.doFilter(request, response); + } catch (ValidateCodeException e) { + log.debug("授权码登录验证码校验失败: {}", e.getMessage()); + failureHandler.onAuthenticationFailure(request, response, new AuthenticationServiceException(e.getMessage())); + } + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/PasswordDecoderFilter.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/PasswordDecoderFilter.java index f6ebeece7..2704c6829 100755 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/PasswordDecoderFilter.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/PasswordDecoderFilter.java @@ -17,10 +17,12 @@ package com.pig4cloud.pigx.auth.support.filter; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.symmetric.AES; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; @@ -39,6 +41,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; +import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; import javax.crypto.spec.IvParameterSpec; @@ -59,6 +62,10 @@ public class PasswordDecoderFilter extends OncePerRequestFilter { private static final String KEY_ALGORITHM = "AES"; + private static final int AES_KEY_LENGTH = 16; + + private static final int SM4_HEX_KEY_LENGTH = 32; + static { // 关闭hutool 强制关闭Bouncy Castle库的依赖 SecureUtil.disableBouncyCastle(); @@ -92,28 +99,72 @@ public class PasswordDecoderFilter extends OncePerRequestFilter { RepeatBodyRequestWrapper requestWrapper = new RepeatBodyRequestWrapper(request); Map parameterMap = requestWrapper.getParameterMap(); - // 构建前端对应解密AES 因子 - AES aes = new AES(Mode.CFB, Padding.NoPadding, - new SecretKeySpec(authSecurityConfigProperties.getEncodeKey().getBytes(), KEY_ALGORITHM), - new IvParameterSpec(authSecurityConfigProperties.getEncodeKey().getBytes())); - - parameterMap.forEach((k, v) -> { - String[] values = parameterMap.get(k); - if (!PASSWORD.equals(k) || ArrayUtil.isEmpty(values)) { - return; - } - + String[] values = parameterMap.get(PASSWORD); + if (ArrayUtil.isNotEmpty(values)) { // 解密密码 - String decryptPassword = aes.decryptStr(values[0]); - parameterMap.put(k, new String[]{decryptPassword}); - }); + String decryptPassword = decryptPassword(values[0]); + parameterMap.put(PASSWORD, new String[]{decryptPassword}); + } chain.doFilter(requestWrapper, response); } - /** - * 根据请求的clientId 查询客户端配置是否是加密传输 + /** + * 根据 {@code security.encode-type} 配置选择 AES 或 SM4 解密前端传入的密文。 + *

+ * 包级可见以便单元测试直接覆盖;当配置为空时按 AES 兜底,避免老配置升级后无法登录。 + * + * @param password 前端加密后的密码 + * @return 解密后的明文密码 + * @throws IllegalArgumentException 当配置的密钥长度不符合所选算法要求 + */ + String decryptPassword(String password) { + AuthSecurityConfigProperties.EncodeType encodeType = authSecurityConfigProperties.getEncodeType(); + if (encodeType == null) { + encodeType = AuthSecurityConfigProperties.EncodeType.AES; + } + return switch (encodeType) { + case AES -> decryptByAes(password); + case SM4 -> decryptBySm4(password); + }; + } + + /** + * 使用 AES(CFB / NoPadding,IV 等于密钥)解密密码。 + * + * @param password 前端 AES 加密后的密码 + * @return 明文密码 + */ + private String decryptByAes(String password) { + String encodeKey = authSecurityConfigProperties.getEncodeKey(); + Assert.hasText(encodeKey, "AES encode-key must not be empty (AES登录密码解密密钥不能为空)"); + byte[] keyBytes = encodeKey.getBytes(); + Assert.isTrue(keyBytes.length == AES_KEY_LENGTH, + "AES encode-key must be 16 bytes (AES登录密码解密密钥必须为16字节)"); + + // 构建前端对应解密 AES 因子 + AES aes = new AES(Mode.CFB, Padding.NoPadding, new SecretKeySpec(keyBytes, KEY_ALGORITHM), + new IvParameterSpec(keyBytes)); + return aes.decryptStr(password); + } + + /** + * 使用国密 SM4 解密密码,密钥需为 32 位 HEX 字符串。 + * + * @param password 前端 SM4 加密后的密码 + * @return 明文密码 + */ + private String decryptBySm4(String password) { + String encodeKey = authSecurityConfigProperties.getEncodeKey(); + Assert.hasText(encodeKey, "SM4 encode-key must not be empty (SM4登录密码解密密钥不能为空)"); + Assert.isTrue(StrUtil.length(encodeKey) == SM4_HEX_KEY_LENGTH && HexUtil.isHexNumber(encodeKey), + "SM4 encode-key must be 32 hex characters (SM4登录密码解密密钥必须为32位HEX字符串)"); + return SmUtil.sm4(HexUtil.decodeHex(encodeKey)).decryptStr(password); + } + + /** + * 根据请求的clientId 查询客户端配置是否是加密传输 * @param request 请求上下文 - * @return true 加密传输 、 false 原文传输 + * @return true 加密传输,false 明文传输 */ private boolean isEncClient(HttpServletRequest request) { String header = request.getHeader(HttpHeaders.AUTHORIZATION); diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/ValidateCodeFilter.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/ValidateCodeFilter.java index 626a5fba3..ed46d6753 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/ValidateCodeFilter.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/filter/ValidateCodeFilter.java @@ -8,20 +8,11 @@ package com.pig4cloud.pigx.auth.support.filter; */ import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import com.anji.captcha.model.vo.CaptchaVO; -import com.anji.captcha.service.CaptchaService; -import com.pig4cloud.pigx.auth.support.core.AuthErrorCodes; -import com.pig4cloud.pigx.common.core.constant.CacheConstants; +import com.pig4cloud.pigx.auth.support.core.AuthCaptchaSupport; import com.pig4cloud.pigx.common.core.constant.CommonConstants; import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.core.constant.enums.CaptchaFlagTypeEnum; import com.pig4cloud.pigx.common.core.exception.ValidateCodeException; -import com.pig4cloud.pigx.common.core.util.MsgUtils; -import com.pig4cloud.pigx.common.core.util.SpringContextHolder; import com.pig4cloud.pigx.common.core.util.WebUtils; -import com.pig4cloud.pigx.common.data.cache.RedisUtils; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -47,6 +38,8 @@ import java.io.IOException; @RequiredArgsConstructor public class ValidateCodeFilter extends OncePerRequestFilter { + private final AuthCaptchaSupport authCaptchaSupport; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { @@ -97,55 +90,7 @@ public class ValidateCodeFilter extends OncePerRequestFilter { * 校验验证码 */ private void checkCode() throws ValidateCodeException { - HttpServletRequest request = WebUtils.getRequest(); - String code = request.getParameter("code"); - - if (StrUtil.isBlank(code)) { - throw new ValidateCodeException(MsgUtils.getMessage(AuthErrorCodes.AUTH_CAPTCHA_EMPTY)); - } - - String randomStr = request.getParameter("randomStr"); - - // https://gitee.com/log4j/pig/issues/IWA0D - String mobile = request.getParameter("mobile"); - if (StrUtil.isNotBlank(mobile)) { - randomStr = mobile; - } - - // 若是滑块登录 - if (StrUtil.equalsAnyIgnoreCase(randomStr, CommonConstants.IMAGE_CODE_BLOCK_PUZZLE, CommonConstants.IMAGE_CODE_CLICK_WORD)) { - CaptchaService captchaService = SpringContextHolder.getBean(CaptchaService.class); - CaptchaVO vo = new CaptchaVO(); - vo.setCaptchaVerification(code); - vo.setCaptchaType(randomStr); - if (!captchaService.verification(vo).isSuccess()) { - throw new ValidateCodeException(MsgUtils.getMessage(AuthErrorCodes.AUTH_CAPTCHA_EMPTY)); - } - return; - } - - String key = CacheConstants.DEFAULT_CODE_KEY + randomStr; - if (!RedisUtils.hasKey(key)) { - throw new ValidateCodeException(MsgUtils.getMessage(AuthErrorCodes.AUTH_CAPTCHA_INVALID)); - } - - String codeObj = RedisUtils.get(key); - - if (codeObj == null) { - throw new ValidateCodeException(MsgUtils.getMessage(AuthErrorCodes.AUTH_CAPTCHA_INVALID)); - } - - if (StrUtil.isBlank(codeObj)) { - RedisUtils.delete(key); - throw new ValidateCodeException(MsgUtils.getMessage(AuthErrorCodes.AUTH_CAPTCHA_INVALID)); - } - - if (!StrUtil.equals(codeObj, code)) { - RedisUtils.delete(key); - throw new ValidateCodeException(MsgUtils.getMessage(AuthErrorCodes.AUTH_CAPTCHA_INVALID)); - } - - RedisUtils.delete(key); + authCaptchaSupport.validateCode(WebUtils.getRequest()); } /** @@ -159,18 +104,7 @@ public class ValidateCodeFilter extends OncePerRequestFilter { String clientId = WebUtils.extractClientId(header).orElse(null); // 获取租户拼接区分租户的key String tenantId = request.getHeader(CommonConstants.TENANT_ID); - String key = String.format("%s:%s:%s", StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1 : tenantId, - CacheConstants.CLIENT_FLAG, clientId); - - String val = RedisUtils.get(key); - - // 当配置不存在时,不用校验 - if (val == null) { - return false; - } - - JSONObject information = JSONUtil.parseObj(val); - return !StrUtil.equals(CaptchaFlagTypeEnum.OFF.getType(), information.getStr(CommonConstants.CAPTCHA_FLAG)); + return authCaptchaSupport.isCaptchaEnabled(tenantId, clientId); } } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/FormAuthenticationFailureHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/FormAuthenticationFailureHandler.java index 3f7676e1f..917e8e78a 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/FormAuthenticationFailureHandler.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/FormAuthenticationFailureHandler.java @@ -16,8 +16,11 @@ package com.pig4cloud.pigx.auth.support.handler; +import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.http.HttpUtil; +import com.pig4cloud.pigx.common.core.constant.CommonConstants; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; import com.pig4cloud.pigx.common.core.util.WebUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -51,15 +54,22 @@ public class FormAuthenticationFailureHandler implements AuthenticationFailureHa // 获取当前请求的context-path String contextPath = request.getContextPath(); + String tenantId = request.getParameter(CommonConstants.TENANT_ID); + String clientId = request.getParameter(SecurityConstants.CLIENT_ID); + String redirectUrl = String.format("%s/token/login?error=%s", contextPath, exception.getMessage()); + if (StrUtil.isNotBlank(tenantId)) { + redirectUrl = String.format("%s&%s=%s", redirectUrl, CommonConstants.TENANT_ID, tenantId); + } + if (StrUtil.isNotBlank(clientId)) { + redirectUrl = String.format("%s&%s=%s", redirectUrl, SecurityConstants.CLIENT_ID, clientId); + } // 构建重定向URL,加入context-path - String url = HttpUtil.encodeParams( - String.format("%s/token/login?error=%s", contextPath, exception.getMessage()), - CharsetUtil.CHARSET_UTF_8); + String url = HttpUtil.encodeParams(redirectUrl, CharsetUtil.CHARSET_UTF_8); try { WebUtils.getResponse().sendRedirect(url); - } catch (IOException e) { + } catch (IOException e) { log.error("重定向失败", e); } } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxAuthenticationFailureEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxAuthenticationFailureEventHandler.java index fbc22a940..0deefc769 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxAuthenticationFailureEventHandler.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxAuthenticationFailureEventHandler.java @@ -39,7 +39,6 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -50,15 +49,19 @@ import org.springframework.stereotype.Component; import java.io.IOException; /** + * 认证失败处理器:处理用户认证失败事件 + * * @author lengleng - * @date 2022-06-02 + * @date 2025/08/19 */ @Slf4j @Component @RequiredArgsConstructor +@SuppressWarnings("removal") public class PigxAuthenticationFailureEventHandler implements AuthenticationFailureHandler { - private static final MappingJackson2HttpMessageConverter errorHttpResponseConverter = new MappingJackson2HttpMessageConverter(); + private static final org.springframework.http.converter.json.MappingJackson2HttpMessageConverter errorHttpResponseConverter = + new org.springframework.http.converter.json.MappingJackson2HttpMessageConverter(); private final ApplicationEventPublisher publisher; @@ -81,13 +84,11 @@ public class PigxAuthenticationFailureEventHandler implements AuthenticationFail log.info("登录失败,异常:{}", exception.getLocalizedMessage()); // 密码模式记录错误信息 - if (OAuth2ParameterNames.PASSWORD.equals(grantType)) { - String username = request.getParameter(OAuth2ParameterNames.USERNAME); + if (SecurityConstants.PASSWORD.equals(grantType)) { + String username = request.getParameter(SecurityConstants.DETAILS_USERNAME); - // 密码错误记录错误次数 (TOC用户不记录) - String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC); - if (exception instanceof OAuth2AuthenticationException - && !SecurityConstants.HEADER_TOC_YES.equals(header)) { + // 密码错误记录错误次数 + if (exception instanceof OAuth2AuthenticationException) { recordLoginFailureTimes(username); } @@ -126,12 +127,17 @@ public class PigxAuthenticationFailureEventHandler implements AuthenticationFail } /** - * 记录登录失败此处,如果操作阈值 调用接口锁定用户 - * @param username username + * 记录登录失败次数,超过阈值时调用接口锁定用户。 + *

若系统参数 LOGIN_ERROR_TIMES {@literal <=} 0,则禁用锁定功能并清除已有失败计数 key。

+ * @param username 用户名 */ private void recordLoginFailureTimes(String username) { String key = String.format("%s%s:%s", CacheConstants.GLOBALLY, CacheConstants.LOGIN_ERROR_TIMES, username); Long deltaTimes = ParamResolver.getLong("LOGIN_ERROR_TIMES", 5L); + if (!isLoginFailureLockEnabled(deltaTimes)) { + RedisUtils.delete(key); + return; + } // 使用 RedisUtils 执行原生 Redis 命令进行递增操作 Long times = RedisUtils.increment(key, 1L);// 增加登录失败次数 @@ -145,6 +151,24 @@ public class PigxAuthenticationFailureEventHandler implements AuthenticationFail } } + /** + * 判断登录失败锁定功能是否启用(参数值大于 0 表示启用) + * @param deltaTimes 系统参数 LOGIN_ERROR_TIMES,0 或 null 表示禁用 + * @return 启用返回 true,禁用返回 false + */ + static boolean isLoginFailureLockEnabled(Long deltaTimes) { + return deltaTimes != null && deltaTimes > 0; + } + + /** + * 写出认证失败响应。 + *

+ * OAuth2 认证异常携带标准错误码和描述,需要保留错误码给前端识别;其他认证异常只返回本地化错误信息。 + * @param request 当前认证请求 + * @param response 当前认证响应 + * @param exception 认证失败异常 + * @throws IOException 响应写出失败时抛出 + */ private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); @@ -153,6 +177,7 @@ public class PigxAuthenticationFailureEventHandler implements AuthenticationFail if (exception instanceof OAuth2AuthenticationException) { OAuth2AuthenticationException authorizationException = (OAuth2AuthenticationException) exception; + // 优先使用 OAuth2 错误描述;没有描述时回退到标准错误码。 errorMessage = StrUtil.isBlank(authorizationException.getError().getDescription()) ? authorizationException.getError().getErrorCode() : authorizationException.getError().getDescription(); @@ -163,6 +188,7 @@ public class PigxAuthenticationFailureEventHandler implements AuthenticationFail } else { + // 非 OAuth2 异常没有标准错误码,直接返回 Spring Security 的本地化异常消息。 errorMessage = exception.getLocalizedMessage(); this.errorHttpResponseConverter.write(R.failed(errorMessage), MediaType.APPLICATION_JSON, httpResponse); } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxAuthenticationSuccessEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxAuthenticationSuccessEventHandler.java index 918edcce4..12a825855 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxAuthenticationSuccessEventHandler.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxAuthenticationSuccessEventHandler.java @@ -52,8 +52,10 @@ import java.time.temporal.ChronoUnit; import java.util.Map; /** + * 认证成功事件处理器:处理用户认证成功后的相关操作 + * * @author lengleng - * @date 2022-06-02 + * @date 2025/08/19 */ @Slf4j @Component diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxLogoutSuccessEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxLogoutSuccessEventHandler.java index 152d960ac..c2e6f504b 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxLogoutSuccessEventHandler.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigxLogoutSuccessEventHandler.java @@ -17,16 +17,26 @@ package com.pig4cloud.pigx.auth.support.handler; import com.pig4cloud.pigx.admin.api.dto.SysLogDTO; +import com.pig4cloud.pigx.common.core.constant.CacheConstants; import com.pig4cloud.pigx.common.core.util.WebUtils; +import com.pig4cloud.pigx.common.data.cache.RedisUtils; import com.pig4cloud.pigx.common.log.util.SysLogUtils; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; import org.springframework.context.ApplicationListener; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.event.LogoutSuccessEvent; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.stereotype.Component; +import java.util.Set; + /** * @author zhangran * @date 2022-06-02 @@ -35,12 +45,22 @@ import org.springframework.stereotype.Component; */ @Slf4j @Component +@RequiredArgsConstructor public class PigxLogoutSuccessEventHandler implements ApplicationListener { + private final OAuth2AuthorizationService authorizationService; + + private final CacheManager cacheManager; + @Override public void onApplicationEvent(LogoutSuccessEvent event) { Authentication authentication = (Authentication) event.getSource(); if (authentication instanceof PreAuthenticatedAuthenticationToken) { + // 来自 removeToken(),token 已删,仅记日志 + handle(authentication); + } else { + // 来自 Spring Security 标准登出,token 未删,需批量清理 + removeTokensByUsername(authentication.getName()); handle(authentication); } } @@ -48,7 +68,6 @@ public class PigxLogoutSuccessEventHandler implements ApplicationListener - * 获取到登录的authentication 对象 * @param authentication 登录对象 */ public void handle(Authentication authentication) { @@ -70,4 +89,33 @@ public class PigxLogoutSuccessEventHandler implements ApplicationListener + * Redis key 格式:token::username::{username}::{clientId}::{tenantId}::{tokenId} + * + * @param username 用户名 + */ + private void removeTokensByUsername(String username) { + Set keys = RedisUtils.keys("token::username::" + username + "*"); + for (String keyName : keys) { + String[] parts = keyName.split("::"); + if (parts.length < 6) { + continue; + } + String tokenValue = parts[5]; + OAuth2Authorization authorization = authorizationService.findByToken(tokenValue, + OAuth2TokenType.ACCESS_TOKEN); + if (authorization == null) { + continue; + } + Cache userCache = cacheManager.getCache(CacheConstants.USER_DETAILS); + if (userCache != null) { + userCache.evict(authorization.getPrincipalName()); + } + authorizationService.remove(authorization); + log.debug("已清理用户 {} 的 token: {}", username, tokenValue); + } + } + } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java index 7c34efb13..97e2cdfdb 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java @@ -25,7 +25,7 @@ import java.io.IOException; @Slf4j public class TenantSavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - private RequestCache requestCache = new HttpSessionRequestCache(); + private final RequestCache requestCache = new HttpSessionRequestCache(); public TenantSavedRequestAwareAuthenticationSuccessHandler() { } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java index 811c1f1ed..f8a50250c 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java @@ -1,15 +1,15 @@ package com.pig4cloud.pigx.auth.support.password; import com.pig4cloud.pigx.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; import com.pig4cloud.pigx.common.security.util.OAuth2EndpointUtils; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; -import jakarta.servlet.http.HttpServletRequest; import java.util.Map; import java.util.Set; @@ -28,14 +28,15 @@ public class OAuth2ResourceOwnerPasswordAuthenticationConverter */ @Override public boolean support(String grantType) { - return AuthorizationGrantType.PASSWORD.getValue().equals(grantType); + return SecurityConstants.PASSWORD.equals(grantType); } @Override public OAuth2ResourceOwnerPasswordAuthenticationToken buildToken(Authentication clientPrincipal, Set requestedScopes, Map additionalParameters) { - return new OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType.PASSWORD, clientPrincipal, - requestedScopes, additionalParameters); + return new OAuth2ResourceOwnerPasswordAuthenticationToken( + new AuthorizationGrantType(SecurityConstants.PASSWORD), clientPrincipal, requestedScopes, + additionalParameters); } /** @@ -46,16 +47,16 @@ public class OAuth2ResourceOwnerPasswordAuthenticationConverter public void checkParams(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request); // username (REQUIRED) - String username = parameters.getFirst(OAuth2ParameterNames.USERNAME); - if (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USERNAME, + String username = parameters.getFirst(SecurityConstants.DETAILS_USERNAME); + if (!StringUtils.hasText(username) || parameters.get(SecurityConstants.DETAILS_USERNAME).size() != 1) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, SecurityConstants.DETAILS_USERNAME, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } // password (REQUIRED) - String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD); - if (!StringUtils.hasText(password) || parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.PASSWORD, + String password = parameters.getFirst(SecurityConstants.PASSWORD); + if (!StringUtils.hasText(password) || parameters.get(SecurityConstants.PASSWORD).size() != 1) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, SecurityConstants.PASSWORD, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java index 3eace7f8d..cef4975c0 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java @@ -1,6 +1,7 @@ package com.pig4cloud.pigx.auth.support.password; import com.pig4cloud.pigx.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.security.authentication.AuthenticationManager; @@ -9,7 +10,6 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; @@ -41,8 +41,8 @@ public class OAuth2ResourceOwnerPasswordAuthenticationProvider @Override public UsernamePasswordAuthenticationToken buildToken(Map reqParameters) { - String username = (String) reqParameters.get(OAuth2ParameterNames.USERNAME); - String password = (String) reqParameters.get(OAuth2ParameterNames.PASSWORD); + String username = (String) reqParameters.get(SecurityConstants.DETAILS_USERNAME); + String password = (String) reqParameters.get(SecurityConstants.PASSWORD); return new UsernamePasswordAuthenticationToken(username, password); } @@ -56,7 +56,8 @@ public class OAuth2ResourceOwnerPasswordAuthenticationProvider @Override public void checkClient(RegisteredClient registeredClient) { assert registeredClient != null; - if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) { + if (!registeredClient.getAuthorizationGrantTypes() + .contains(new AuthorizationGrantType(SecurityConstants.PASSWORD))) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); } } diff --git a/pigx-auth/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService b/pigx-auth/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService deleted file mode 100644 index 39e4ea0c5..000000000 --- a/pigx-auth/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService +++ /dev/null @@ -1 +0,0 @@ -com.pig4cloud.pigx.auth.endpoint.CaptchaCacheServiceProvider diff --git a/pigx-auth/src/main/resources/logback-spring.xml b/pigx-auth/src/main/resources/logback-spring.xml index 90c072baa..53c3fc1e3 100644 --- a/pigx-auth/src/main/resources/logback-spring.xml +++ b/pigx-auth/src/main/resources/logback-spring.xml @@ -1,6 +1,6 @@ - + + class="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/> + class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/> diff --git a/pigx-auth/src/main/resources/templates/ftl/login.ftl b/pigx-auth/src/main/resources/templates/ftl/login.ftl index 809217fda..9b9e4632a 100755 --- a/pigx-auth/src/main/resources/templates/ftl/login.ftl +++ b/pigx-auth/src/main/resources/templates/ftl/login.ftl @@ -13,16 +13,17 @@

+ + <#include "layout/base.ftl">