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