From 3b15ea4235a9243141000d9f10cb5b7b44d1af8c Mon Sep 17 00:00:00 2001 From: PIGCLOUD Date: Mon, 2 Nov 2026 12:23:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=E9=99=90=E5=88=B6=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E7=99=BB=E5=BD=95=E4=BB=85=E4=BD=BF=E7=94=A8=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit d3c470a848b5369459cd2acc9662276ed52b0e15) --- .../filter/FormLoginValidateCodeFilter.java | 12 ++- .../FormLoginValidateCodeFilterTests.java | 75 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 pigx-auth/src/test/java/com/pig4cloud/pigx/auth/support/filter/FormLoginValidateCodeFilterTests.java 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 index 2f236b15b..3c2205c2e 100644 --- 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 @@ -1,8 +1,9 @@ package com.pig4cloud.pigx.auth.support.filter; +import cn.hutool.core.util.ArrayUtil; 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.constant.SecurityConstants; import com.pig4cloud.pigx.common.core.exception.ValidateCodeException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -11,6 +12,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -45,6 +47,14 @@ public class FormLoginValidateCodeFilter extends OncePerRequestFilter { return; } + // 表单登录只允许密码认证,防止切换到 APP 模式的免密码校验分支。 + String[] grantTypes = request.getParameterValues(OAuth2ParameterNames.GRANT_TYPE); + if (ArrayUtil.length(grantTypes) != 1 || !ArrayUtil.contains(grantTypes, SecurityConstants.PASSWORD)) { + failureHandler.onAuthenticationFailure(request, response, + new AuthenticationServiceException("Form login only supports password grant")); + return; + } + String authClientId = authCaptchaSupport.resolveAuthorizationClientId(request, response, true); String tenantId = authCaptchaSupport.resolveAuthorizationTenantId(request, response, true); diff --git a/pigx-auth/src/test/java/com/pig4cloud/pigx/auth/support/filter/FormLoginValidateCodeFilterTests.java b/pigx-auth/src/test/java/com/pig4cloud/pigx/auth/support/filter/FormLoginValidateCodeFilterTests.java new file mode 100644 index 000000000..a9cb9c135 --- /dev/null +++ b/pigx-auth/src/test/java/com/pig4cloud/pigx/auth/support/filter/FormLoginValidateCodeFilterTests.java @@ -0,0 +1,75 @@ +package com.pig4cloud.pigx.auth.support.filter; + +import com.pig4cloud.pigx.auth.support.core.AuthCaptchaSupport; +import jakarta.servlet.FilterChain; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +class FormLoginValidateCodeFilterTests { + + private final AuthCaptchaSupport captchaSupport = mock(AuthCaptchaSupport.class); + + private final FormLoginValidateCodeFilter filter = new FormLoginValidateCodeFilter(captchaSupport); + + private final FilterChain chain = mock(FilterChain.class); + + private final MockHttpServletRequest request = new MockHttpServletRequest("POST", "/oauth2/form"); + + private final MockHttpServletResponse response = new MockHttpServletResponse(); + + @AfterEach + void cleanup() { + RequestContextHolder.resetRequestAttributes(); + } + + static Stream invalidGrants() { + return Stream.of(null, new String[] { "" }, new String[] { "app" }, new String[] { "mobile" }, + new String[] { "client_credentials" }, new String[] { "authorization_code" }, + new String[] { "refresh_token" }, new String[] { "password", "app" }, + new String[] { "app", "password" }, new String[] { "password", "password" }).map(values -> Arguments.of((Object) values)); + } + + @ParameterizedTest + @MethodSource("invalidGrants") + void rejectsInvalidGrantsBeforeCaptchaOrAuthentication(String[] grantTypes) throws Exception { + if (grantTypes != null) { + request.setParameter("grant_type", grantTypes); + } + applyFilter(); + assertThat(response.getStatus()).isEqualTo(302); + assertThat(response.getRedirectedUrl()).contains("/token/login?error="); + verifyNoInteractions(captchaSupport, chain); + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + void allowsPasswordWithExistingTenantCaptchaPolicy(boolean captchaEnabled) throws Exception { + request.setParameter("grant_type", "password"); + when(captchaSupport.resolveAuthorizationClientId(request, response, true)).thenReturn("client"); + when(captchaSupport.resolveAuthorizationTenantId(request, response, true)).thenReturn("2"); + when(captchaSupport.isCaptchaEnabled("2", "client")).thenReturn(captchaEnabled); + applyFilter(); + verify(captchaSupport).isCaptchaEnabled("2", "client"); + verify(captchaSupport, times(captchaEnabled ? 1 : 0)).validateCode(request); + verify(chain).doFilter(request, response); + } + + private void applyFilter() throws Exception { + request.setServletPath("/oauth2/form"); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request, response)); + filter.doFilter(request, response, chain); + } + +}