Spring Security custom authentication login

Summary 1.

1.1. Introduction

Spring Security is a security framework based on Spring AOP and Servlet filters to manage permissions, etc.

1.2. Spring Security customizes the authentication process

1) Certification process

Generate unauthenticated AuthenticationToken ↑ (get information) (according to the AuthenticationToken allocation Provider) AuthenticationFilter -> AuthenticationManager -> AuthenticationProvider ↓ (Authentication) UserDetails (obtained by querying the database) ↓ (Passed) Generates the AuthenticationToken ↓ (Stored) SecurityContextHolderCopy the code

2) Add the AuthenticationFilter to the Security filter chain (configured in the resource server), as follows:

http.addFilterBefore(AuthenticationFilter, AbstractPreAuthenticatedProcessingFilter.class)
Copy the code

Or:

http.addFilterAfter(AuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
Copy the code

2. The following uses mobile phone SMS login as an example

2.1. Development environment

  • SpringBoot
  • Spring security
  • Redis

2.2. Core code analysis

2.2.1. Customize the login authentication process

2.2.1.1. User-defined Authentication Login Token

/** * mobile login Token ** @author:  CatalpaFlat */ public class MobileLoginAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationToken.class.getName()); private final Object principal; public MobileLoginAuthenticationToken(String mobile) { super(null); this.principal = mobile; this.setAuthenticated(false); logger.info("MobileLoginAuthenticationToken setAuthenticated ->false loading ..." ); } public MobileLoginAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; // must use super, as we override super.setAuthenticated(true); logger.info("MobileLoginAuthenticationToken setAuthenticated ->true loading ..." ); } @Override public void setAuthenticated(boolean authenticated) { if (authenticated) { throw new IllegalArgumentException( "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } super.setAuthenticated(false); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return this.principal; } @Override public void eraseCredentials() { super.eraseCredentials(); }}Copy the code

Note: setAuthenticated() : judge whether it has been authenticated

  • When filtering, an unauthenticated AuthenticationToken is generated and setAuthenticated() of the custom token is called, set to false -> Unauthenticated
  • When provider, an authenticated AuthenticationToken is generated and setAuthenticated() of the parent class is called, which is set to true -> Authenticated

2.2.1.1. Customize the authentication login filter

/** * mobile message login filter ** @author:  CatalpaFlat */ public class MobileLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private boolean postOnly = true; private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationFilter.class.getName()); @Getter @Setter private String mobileParameterName; public MobileLoginAuthenticationFilter(String mobileLoginUrl, String mobileParameterName, String httpMethod) { super(new AntPathRequestMatcher(mobileLoginUrl, httpMethod)); this.mobileParameterName = mobileParameterName; logger.info("MobileLoginAuthenticationFilter loading ..." ); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { if (postOnly && ! request.getMethod().equals(HttpMethod.POST.name())) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } //get mobile String mobile = obtainMobile(request); //assemble token MobileLoginAuthenticationToken authRequest = new MobileLoginAuthenticationToken(mobile); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } private void setDetails(HttpServletRequest Request, MobileLoginAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request));  } private String obtainMobile(HttpServletRequest Request) {return request.getParameter(mobileParameterName); } public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; }}Copy the code

AttemptAuthentication () method:

  • Filters the specified URL and httpMethod
  • Get the required request parameter data wrapper to generate an unauthenticated AuthenticationToken
  • Pass to AuthenticationManager for authentication

2.2.1.1. Customize the authentication login provider

/** * mobile SMS login authentication provider ** @author:  CatalpaFlat */ public class MobileLoginAuthenticationProvider implements AuthenticationProvider { private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationProvider.class.getName()); @Getter @Setter private UserDetailsService customUserDetailsService; public MobileLoginAuthenticationProvider() { logger.info("MobileLoginAuthenticationProvider loading ..." ); } /** * Authentication */ @override public Authentication authenticate(Authentication Authentication) throws AuthenticationException {/ / filter encapsulation token information MobileLoginAuthenticationToken authenticationToken = (MobileLoginAuthenticationToken) authentication; / / get the user information database (certification) populated UserDetails populated UserDetails. = customUserDetailsService loadUserByUsername ((String) authenticationToken.getPrincipal()); / / not through the if (populated userDetails = = null) {throw new InternalAuthenticationServiceException (" Unable to obtain user information "); } / / by MobileLoginAuthenticationToken authenticationResult = new MobileLoginAuthenticationToken (populated userDetails, userDetails.getAuthorities()); authenticationResult.setDetails(authenticationToken.getDetails()); return authenticationResult; } @override public Boolean supports(Class<? > authentication) { return MobileLoginAuthenticationToken.class.isAssignableFrom(authentication); }}Copy the code

Note: Authenticate () method

  • Gets the token information encapsulated by the filter
  • Retrieving UserDetailsService Obtaining user information (database authentication) -> Check whether the user succeeds
  • A new AuthenticationToken is encapsulated and returned

2.2.1.1. Customize authentication Login authentication configuration

@Configuration(SpringBeanNameConstant.DEFAULT_CUSTOM_MOBILE_LOGIN_AUTHENTICATION_SECURITY_CONFIG_BN)
public class MobileLoginAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationSecurityConfig.class.getName());
    @Value("${login.mobile.url}")
    private String defaultMobileLoginUrl;
    @Value("${login.mobile.parameter}")
    private String defaultMobileLoginParameter;
    @Value("${login.mobile.httpMethod}")
    private String defaultMobileLoginHttpMethod;

    @Autowired
    private CustomYmlConfig customYmlConfig;
    @Autowired
    private UserDetailsService customUserDetailsService;
    @Autowired
    private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler customAuthenticationFailureHandler;


    public MobileLoginAuthenticationSecurityConfig() {
        logger.info("MobileLoginAuthenticationSecurityConfig loading ...");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        MobilePOJO mobile = customYmlConfig.getLogins().getMobile();
        String url = mobile.getUrl();
        String parameter = mobile.getParameter().getMobile();
        String httpMethod = mobile.getHttpMethod();

        MobileLoginAuthenticationFilter mobileLoginAuthenticationFilter = new MobileLoginAuthenticationFilter(StringUtils.isBlank(url) ? defaultMobileLoginUrl : url,
                StringUtils.isBlank(parameter) ? defaultMobileLoginUrl : parameter, StringUtils.isBlank(httpMethod) ? defaultMobileLoginHttpMethod : httpMethod);

        mobileLoginAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        mobileLoginAuthenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
        mobileLoginAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);

        MobileLoginAuthenticationProvider mobileLoginAuthenticationProvider = new MobileLoginAuthenticationProvider();
        mobileLoginAuthenticationProvider.setCustomUserDetailsService(customUserDetailsService);

        http.authenticationProvider(mobileLoginAuthenticationProvider)
                .addFilterAfter(mobileLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}
Copy the code

Note: Configure () method

  • Instantiate AuthenticationFilter and AuthenticationProvider
  • Add AuthenticationFilter and AuthenticationProvider to Spring Security.

2.2.2. Verification based on redis custom verification code

2.2.2.1. User-defined verification code filter based on Redis

/** * verification code filter ** @author:  CatalpaFlat */ @Component(SpringBeanNameConstant.DEFAULT_VALIDATE_CODE_FILTER_BN) public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(ValidateCodeFilter.class.getName()); @Autowired private CustomYmlConfig customYmlConfig; @Autowired private RedisTemplate<Object, Object> redisTemplate; /** * Private AntPathMatcher pathMatcher = new AntPathMatcher(); public ValidateCodeFilter() { logger.info("Loading ValidateCodeFilter..." ); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String url = customYmlConfig.getLogins().getMobile().getUrl(); if (pathMatcher.match(url, request.getRequestURI())) { String deviceId = request.getHeader("deviceId"); if (StringUtils.isBlank(deviceId)) { throw new CustomException(HttpStatus.NOT_ACCEPTABLE.value(), "Not deviceId in the head of the request"); } String codeParamName = customYmlConfig.getLogins().getMobile().getParameter().getCode(); String code = request.getParameter(codeParamName); if (StringUtils.isBlank(code)) { throw new CustomException(HttpStatus.NOT_ACCEPTABLE.value(), "Not code in the parameters of the request"); } String key = SystemConstant.DEFAULT_MOBILE_KEY_PIX + deviceId; SmsCodePO smsCodePo = (SmsCodePO) redisTemplate.opsForValue().get(key); if (smsCodePo.isExpried()){ throw new CustomException(HttpStatus.BAD_REQUEST.value(), "The verification code has expired"); } String smsCode = smsCodePo.getCode(); if (StringUtils.isBlank(smsCode)) { throw new CustomException(HttpStatus.BAD_REQUEST.value(), "Verification code does not exist"); } if (StringUtils.equals(code, smsCode)) { redisTemplate.delete(key); //let it go filterChain.doFilter(request, response); } else { throw new CustomException(HttpStatus.BAD_REQUEST.value(), "Validation code is incorrect"); } }else { //let it go filterChain.doFilter(request, response); }}}Copy the code

Note: doFilterInternal ()

  • User-defined verification code filtering verification

2.2.2.2. Add custom captcha filters to the Spring Security filter chain

http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class)
Copy the code

Note: Added before the authentication preprocessor filter

3. Test the effect




Finally attached source address: https://gitee.com/CatalpaFlat/springSecurity.git