In version 6.x, customizing AbstractAuthenticationProcessingFilter, AuthenticationProvider, and AbstractAuthenticationToken

cannot log in successfully.

In version 5.x, you can log in successfully!

security-6.x security-demo1

security-5.x security-demo2


public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public static final AntPathRequestMatcher SMS_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/sms/login", "POST");

    private final static String MOBILE_REG_EXP = "1[3-9]\\d{9}";

    private final static String CODE_REG_EXP = "\\d{6}";

    private String mobileParameter = "mobile";

    private String codeParameter = "code";

    private boolean postOnly = true;

    public SmsCodeAuthenticationFilter() {
        super(SMS_ANT_PATH_REQUEST_MATCHER);
    }

    public SmsCodeAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(SMS_ANT_PATH_REQUEST_MATCHER, authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            var mobile = this.obtainMobile(request);
            if (StrUtil.isBlank(mobile)) {
                throw new AuthenticationServiceException("手机号码必须填写!");
            }
            if (!Pattern.matches(MOBILE_REG_EXP, mobile)) {
                throw new AuthenticationServiceException(StrUtil.format("手机号码 {} 格式错误!", mobile));
            }
            var code = this.obtainCode(request);
            if (StrUtil.isBlank(code)) {
                throw new AuthenticationServiceException("验证码必须填写!");
            }
            if (!Pattern.matches(CODE_REG_EXP, code)) {
                throw new AuthenticationServiceException("验证码必须是6位数字!");
            }
            var authRequest = SmsCodeAuthenticationToken.unauthenticated(mobile, code);
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    @Nullable
    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(this.mobileParameter);
    }

    @Nullable
    protected String obtainCode(HttpServletRequest request) {
        return request.getParameter(this.codeParameter);
    }

    public void setMobileParameter(String mobileParameter) {
        this.mobileParameter = mobileParameter;
    }

    public void setCodeParameter(String codeParameter) {
        this.codeParameter = codeParameter;
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

}


@Slf4j
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

    protected final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    private final UserDetailsService userDetailsService;

    public SmsCodeAuthenticationProvider(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(SmsCodeAuthenticationToken.class, authentication, () ->
                this.messages.getMessage("SmsCodeAuthenticationProvider.onlySupports", "Only SmsCodeAuthenticationToken is supported")
        );
        var auth = (SmsCodeAuthenticationToken) authentication;
        var mobile = (String) auth.getPrincipal();
        var code = (String) auth.getCredentials();
        var collect = Stream.of("read", "write").map(SimpleGrantedAuthority::new).collect(Collectors.toSet());

        var loginUser = userDetailsService.loadUserByUsername(mobile);
        if (!StringUtils.equals(loginUser.getPassword(), code)) {
            throw new AuthenticationServiceException("sms code err : " + code);
        }

        var authenticated = SmsCodeAuthenticationToken.authenticated(loginUser, mobile, collect);
        authenticated.setDetails(auth.getDetails());
        log.info("mobile: {}  code: {}  isAuthenticated: {}", mobile, code, authenticated.isAuthenticated());
        return authenticated;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (SmsCodeAuthenticationToken.class.isAssignableFrom(authentication));
    }


}


public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

    private final Object principal;

    private Object credentials;

    public SmsCodeAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

    public SmsCodeAuthenticationToken(Object principal, Object credentials,
                                      Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // must use super, as we override
    }

    public static SmsCodeAuthenticationToken unauthenticated(Object principal, Object credentials) {
        return new SmsCodeAuthenticationToken(principal, credentials);
    }

    public static SmsCodeAuthenticationToken authenticated(Object principal, Object credentials,
                                                           Collection<? extends GrantedAuthority> authorities) {
        return new SmsCodeAuthenticationToken(principal, credentials, authorities);
    }

    @Override
    public Object getCredentials() {
        return this.credentials;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        Assert.isTrue(!isAuthenticated,
                "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        this.credentials = null;
    }

}


@Slf4j
@Component
@AllArgsConstructor
public class SmsCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private final UserDetailsService userDetailsService;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        var filter = new SmsCodeAuthenticationFilter();
        var sharedObject = http.getSharedObject(AuthenticationManager.class);
        filter.setAuthenticationManager(sharedObject);

        var handler = new SmsCodeAuthenticationHandler();
        filter.setAuthenticationFailureHandler(handler);
        filter.setAuthenticationSuccessHandler(handler);

        var provider = new SmsCodeAuthenticationProvider(userDetailsService);
        http.authenticationProvider(provider)
                .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    }

}

Comment From: marcusdacoregio

Hi @thousmile.

I believe that the problem is because, in 6.0, Spring Security now requires explicit saving of the SecurityContext. Although the AbstractAuthenticationProcessingFilter saves the context for you in the successfulAuthentication method, it uses the RequestAttributeSecurityContextRepository to save the context, and I believe that you want to use at least the HttpSessionSecurityContextRepository.

You might want to call:

// ...
filter.setSecurityContextRepository(
    new DelegatingSecurityContextRepository(
                new RequestAttributeSecurityContextRepository(),
                new HttpSessionSecurityContextRepository());
// ...

to align with Spring Security 6 defaults.

Comment From: thousmile

Thank you so much! @marcusdacoregio

I've been searching on Google for a long time but can't find the answer!