fix(auth): 限制表单登录仅使用密码认证

(cherry picked from commit d3c470a848b5369459cd2acc9662276ed52b0e15)
This commit is contained in:
PIGCLOUD committed 2026-11-03 14:49:32 +08:00
1 parent 3fd14644cd
commit 3b15ea4235
2 files changed
+86 -1

No files matched your search

@@ -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);
@@ -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<Arguments> 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);
}
}