I hope to add a feature for registering filters. When I want to develop a class similar to FormLogiConfigurer, I need to manually add Filter to register my implementation of a filter similar to UsernamePasswordAuthenticationFilter into the interceptor chain. However, if I use the configuration class in HttpSecurity. apply, I will add this interceptor again to the interceptor chain. If the configuration class is directly applied, an exception will be thrown, 'The Filter class xxx does not have a registered order and cannot be added without a specified order. Advisor using addFilterBefore or addFilterAfter instance.' At this point, I have to use reflection to manually add to the interceptor chain registrar instead of adding to the interceptor chain instance
My code is as follows
package com.example.demo.security;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class SmsAuthorizationConfigurer<H extends HttpSecurity> extends AbstractAuthenticationFilterConfigurer<HttpSecurity, SmsAuthorizationConfigurer<H>, SmsAuthorizationFilter> {
private static final String DEFAULT_LOGIN_PROCESSING_URL = "/sms/login";
public SmsAuthorizationConfigurer() {
super(new SmsAuthorizationFilter(), DEFAULT_LOGIN_PROCESSING_URL);
}
@Override
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl, "POST");
}
@Override
public SmsAuthorizationConfigurer<H> loginPage(String loginPage) {
return super.loginPage(loginPage);
}
public SmsAuthorizationConfigurer<H> mobileParameter(String mobileParameter) {
getAuthenticationFilter().setMobileParameter(mobileParameter);
return this;
}
public SmsAuthorizationConfigurer<H> smsCodeParameter(String smsCodeParameter) {
getAuthenticationFilter().setSmsCodeParameter(smsCodeParameter);
return this;
}
@Override
public void init(HttpSecurity http) throws Exception {
super.init(http);
Class<?> aClass = Class.forName("org.springframework.security.config.annotation.web.builders.FilterOrderRegistration");
Method put = aClass.getDeclaredMethod("put", Class.class, int.class);
put.setAccessible(true);
Method getOrder = aClass.getDeclaredMethod("getOrder", Class.class);
getOrder.setAccessible(true);
Field filterOrders = http.getClass().getDeclaredField("filterOrders");
filterOrders.setAccessible(true);
Object filterOrderRegistration = filterOrders.get(http);
Integer order = (Integer) getOrder.invoke(filterOrderRegistration, UsernamePasswordAuthenticationFilter.class);
put.invoke(filterOrderRegistration, getAuthenticationFilter().getClass(), order + 1);
}
}
package com.example.demo.config;
import com.example.demo.security.SmsAuthorizationConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.apply(new SmsAuthorizationConfigurer<>()).mobileParameter("phone").smsCodeParameter("code");
httpSecurity.csrf().disable();
return httpSecurity.build();
}
@Bean
UserDetailsService userDetailsService() {
UserDetails userDetails = User.builder()
.username("user")
.password("{noop}123456")
.authorities("test1").build();
return new InMemoryUserDetailsManager(userDetails);
}
}
package com.example.demo.security;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
public class SmsAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
public static final String SPRING_SECURITY_FORM_SMS_CODE_KEY = "smsCode";
public static final String DEFAULT_REQUEST_MATCHER= "/sms/login";
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(DEFAULT_REQUEST_MATCHER,
"POST");
private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
private String smsCodeParameter = SPRING_SECURITY_FORM_SMS_CODE_KEY;
private boolean postOnly = true;
public SmsAuthorizationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
public SmsAuthorizationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException{
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String mobile = obtainMobile(request);
mobile = (mobile != null) ? mobile.trim() : "";
String smsCode = obtainSmsCode(request);
smsCode = (smsCode != null) ? smsCode : "";
SmsAuthenticationToken authRequest = SmsAuthenticationToken.unauthenticated(mobile,
smsCode);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected void setDetails(HttpServletRequest request, SmsAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
@Nullable
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(this.mobileParameter);
}
@Nullable
protected String obtainSmsCode(HttpServletRequest request) {
return request.getParameter(this.smsCodeParameter);
}
public void setMobileParameter(String mobileParameter) {
Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
this.mobileParameter = mobileParameter;
}
public void setSmsCodeParameter(String mobileParameter) {
Assert.hasText(mobileParameter, "smsCode parameter must not be empty or null");
this.smsCodeParameter = mobileParameter;
}
public final String getMobileParameter() {
return this.mobileParameter;
}
public final String getSmsCodeParameter() {
return this.smsCodeParameter;
}
}
package com.example.demo.security;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
public class SmsAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private Object credentials;
public SmsAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public SmsAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public static SmsAuthenticationToken unauthenticated(Object principal, Object credentials) {
return new SmsAuthenticationToken(principal, credentials);
}
@Override
public Object getCredentials() {
return credentials;
}
@Override
public Object getPrincipal() {
return principal;
}
}
Comment From: jzheaux
Thanks for getting in touch! It feels like this is a question that would be better suited to Stack Overflow. We prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it) or add more detail if you feel this is a genuine bug.