Java Backend Security Development Spring Security Development REST Service Chapter 4-4 - SMS Authentication Code

Keywords: Mobile Spring JSON Java




1. Configuration related

1.1 AbstractChannelSecurityConfig

Abstract class encapsulating authentication logic for Http pages
ApplyPasswordAuthentication Config sets the default form login authorization authentication route for HttpSecurity, and imoocAuthentication SuccessHandler, imoocAuthentication FailureHandler

extends WebSecurityConfigurerAdapter
implements WebSecurityConfigurer
extends SecurityConfigurer<Filter, T>
applyPasswordAuthenticationConfig

/**
 * 
 */
package com.wxm.spring.security.core.authentication;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import com.wxm.spring.security.core.properties.SecurityConstants;

/**
 * @author zhailiang
 *
 */
public class AbstractChannelSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	protected AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
	
	@Autowired
	protected AuthenticationFailureHandler imoocAuthenticationFailureHandler;
	
	protected void applyPasswordAuthenticationConfig(HttpSecurity http) throws Exception {
		http.formLogin()
			.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
			.loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
			.successHandler(imoocAuthenticationSuccessHandler)
			.failureHandler(imoocAuthenticationFailureHandler);
	}
	
}

1.2 BrowserSecurityConfig

  1. @Autowired loads SecurityProperties and reads the configuration items in application.properties
  2. Inherit AbstractChannelSecurityConfig and call AbstractChannelSecurityConfig.applyPasswordAuthenticationConfig in configure to implement browser-specific settings:
    Authentication code configuration:.apply(validateCodeSecurityConfig)
    SMS Configuration:.Apply (smsCodeAuthentication SecurityConfig)
    Remember my configuration:
    .rememberMe()
    .tokenRepository(persistentTokenRepository())
    .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
    .userDetailsService(userDetailsService)
    Whitelist Configuration:
    .authorizeRequests()
    .antMatchers(
    SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
    SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
    securityProperties.getBrowser().getLoginPage(),
    SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
    //securityProperties.getBrowser().getSignUpUrl(),
    //securityProperties.getBrowser().getSession().getSessionInvalidUrl()+".json",
    //securityProperties.getBrowser().getSession().getSessionInvalidUrl()+".html",
    "/user/regist")
    .permitAll()
    .anyRequest()
    .authenticated()
    Turn off CSFR:
    .csrf().disable();

extends AbstractChannelSecurityConfig
configure

```java
/**
 * 
 */
package com.wxm.spring.security.browser;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import com.wxm.spring.security.core.authentication.AbstractChannelSecurityConfig;
import com.wxm.spring.security.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.wxm.spring.security.core.properties.SecurityConstants;
import com.wxm.spring.security.core.properties.SecurityProperties;
import com.wxm.spring.security.core.validate.code.ValidateCodeSecurityConfig;

/**
 * @author zhailiang
 *
 */
@Configuration
public class BrowserSecurityConfig extends AbstractChannelSecurityConfig {

	@Autowired
	private SecurityProperties securityProperties;
	
	@Autowired
	private DataSource dataSource;
	
	@Autowired
	private UserDetailsService userDetailsService;
	
	@Autowired
	private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
	
	@Autowired
	private ValidateCodeSecurityConfig validateCodeSecurityConfig;
	
	//@Autowired
	//private SpringSocialConfigurer imoocSocialSecurityConfig;
	
	//@Autowired
	//private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
	
	//@Autowired
	//private InvalidSessionStrategy invalidSessionStrategy;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		
		applyPasswordAuthenticationConfig(http);
		
		http
			.apply(validateCodeSecurityConfig)
				.and()
			.apply(smsCodeAuthenticationSecurityConfig)
				.and()
			//.apply(imoocSocialSecurityConfig)
				//.and()
			.rememberMe()
				.tokenRepository(persistentTokenRepository())
				.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
				.userDetailsService(userDetailsService)
				.and()
			//.sessionManagement()
				//.invalidSessionStrategy(invalidSessionStrategy)
				//.maximumSessions(securityProperties.getBrowser().getSession().getMaximumSessions())
				//.maxSessionsPreventsLogin(securityProperties.getBrowser().getSession().isMaxSessionsPreventsLogin())
				//.expiredSessionStrategy(sessionInformationExpiredStrategy)
				//.and()
				//.and()
			.authorizeRequests()
				.antMatchers(
					SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
					SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
					securityProperties.getBrowser().getLoginPage(),
					SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
					//securityProperties.getBrowser().getSignUpUrl(),
					//securityProperties.getBrowser().getSession().getSessionInvalidUrl()+".json",
					//securityProperties.getBrowser().getSession().getSessionInvalidUrl()+".html",
					"/user/regist")
					.permitAll()
				.anyRequest()
				.authenticated()
				.and()
			.csrf().disable();
		
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	@Bean
	public PersistentTokenRepository persistentTokenRepository() {
		JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
		tokenRepository.setDataSource(dataSource);
//		tokenRepository.setCreateTableOnStartup(true);
		return tokenRepository;
	}
}

1.3 ValidateCodeSecurityConfig

Insert validateCodeFilter before AbstractPreAuthenticatedProcessingFilter

extends SecurityConfigurerAdapter
implements SecurityConfigurer
configure

/**
 * 
 */
package com.wxm.spring.security.core.validate.code;

import javax.servlet.Filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.stereotype.Component;

/**
 * @author zhailiang
 *
 */
@Component("validateCodeSecurityConfig")
public class ValidateCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

	@Autowired
	private Filter validateCodeFilter;
	
	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class);
	}
}

1.4 SmsCodeAuthenticationSecurityConfig

Configure SmsCodeAuthenticationFilter to insert SmsCodeAuthenticationFilter before UsernamePasswordAuthenticationFilter
Configure SmsCodeAuthentication Provider

extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityConfigurer
configure

/**
 * 
 */
package com.wxm.spring.security.core.authentication.mobile;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

/**
 * @author zhailiang
 *
 */
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
	
	@Autowired
	private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
	
	@Autowired
	private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
	
	@Autowired
	private UserDetailsService userDetailsService;
	
	@Override
	public void configure(HttpSecurity http) throws Exception {
		
		SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
		smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
		smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
		smsCodeAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
		
		SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
		smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
		
		http.authenticationProvider(smsCodeAuthenticationProvider)
			.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
		
	}

}

AbstractChannelSecurityConfig
WebSecurityConfigurerAdapter
apply
Password
Authentication
Config
WebSecurityConfigurer
SecurityConfigurer
BrowserSecurityConfig
configure
ValidateCodeSecurityConfig
SecurityConfigurerAdapter
configure
SmsCodeAuthenticationSecurityConfig
configure
SmsCodeAuthenticationSecurityConfig
configure

2. Authentication code refactoring - base class

2.1 ValidateCode

Contains validation code information: code, expireTime

2.2 ValidateCodeBeanConfig

Introduce SecurityProperties, ImageCodeGenerator, SmsCodeSender

2.3 ValidateCodeController

Introducing ValidateCodeProcessorHolder
Call createCode in createCode to create a verification code, returned by HttpServletResponse

2.4 ValidateCodeType

Enumeration classes, SMS, IMAGE

2.5 ValidateCodeSecurityConfig

Same 1.4

2.6 ValidateCodeProcessorHolder

Implement ValidateCodeProcessor's dependency lookup

2.7 ValidateCodeProcessor

Interface class, create and validate methods, ImageValidateCodeProcessor and SmCodeProcessor implement this interface

2.8 ValidateCodeGenerator

Interface class, containing generate methods, implemented by ImageValidateCodeGenerator and SmsValidateCodeGenerator

2.9 AbstractValidateCodeProcessor

  1. implements ValidateCodeProcessor
  2. Implement the basic flow of verification code: validate, create (save, send), generate
  3. ValidateCodeGenerator Injection Lookup
    ImageValidateCodeGenerator and SmsValidateCodeGenerator
  4. Genee finds the corresponding ImageValidateCodeGenerator or SmsValidateCodeGenerator in the validateCodeGenerators according to the request type.
	/**
	 * Collects the implementations of all {@link ValidateCodeGenerator} interfaces in the system.
	 */
	@Autowired
	private Map<String, ValidateCodeGenerator> validateCodeGenerators;
  1. create
    Call generate to generate authentication code (graphics or text messages)
    Call save to save in session
    Call send to send a verification code (return ImageCode or send a text message)
  2. validate
    Remove the subclass of ValidateCode from session (ImageValidateCode or SmsValidateCode) and verify it with the validation code passed in from ServletWebRequest, which returns normally and captures him without throwing an exception (ValidateCodeException).
  3. ImageCodeProcessor and SMCodeProcessor extend this class

2.10 ValidateCodeException

extends AuthenticationException
ImoocAuthentication FailureHandler will capture him

3. Graphic Authentication Code

3.1 ImageCode

extends ValidateCode, adds graphic validation code information: BufferedImage

3.2 ImageCodeGenerator

implements ValidateCodeGenerator
Genee Generates ImageCode

3.3 DemoImageCodeGenerator

implements ValidateCodeGenerator
Another generation generate s ImageCode

3.3 ImageCodeProcessor

extends AbstractValidateCodeProcessor
Overloaded send

	@Override
	protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception {
		ImageIO.write(imageCode.getImage(), "JPEG", request.getResponse().getOutputStream());
	}

4. Text Message Authentication Code

4.1 ValidateCode

Save SMS Authentication Codes directly using the underlying class ValidateCode, since there is no additional information, there is no need to extend subclasses

4.2 SmsCodeGenerator

  1. implements ValidateCodeGenerator
  2. Inject SecurityProperties and read the configuration information in application.properties, such as:
    imooc.security.code.sms.length = 6
  3. Genee generate s ValidateCode

4.3 SmsCodeProcessor

extends AbstractValidateCodeProcessor
Overload send, call SmsCodeSender interface class, send SMS Authentication Code

4.4 SmsCodeSender

Interface class, containing send method

4.5 DefaultSmsCodeSender

implements SmsCodeSender, implements send method

4.6 SmsCodeAuthenticationToken

  1. Modified with UsernamePasswordAuthenticationToken
  2. extends AbstractAuthenticationToken
  3. private final Object principal;
    Store mobile number

4.7 SmsCodeAuthenticationFilter

  1. Modified with UsernamePasswordAuthenticationFilter
  2. extends AbstractAuthenticationProcessingFilter
  3. attemptAuthentication is validation logic
    Create SmsCodeAuthenticationToken with mobile number from HttpServletRequest
    return this.getAuthenticationManager().authenticate(authRequest): Pass SmsCodeAuthenticationToken to AuthenticationManager, AuthenticationManager calls the chain of AuthenticationProvider, and finally this SmsCodeAuthenticationToken is processed by SmsCodeAuthenticationProvider and returns Authentication
/**
 * 
 */
package com.wxm.spring.security.core.authentication.mobile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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;

import com.wxm.spring.security.core.properties.SecurityConstants;



/**
 * @author zhailiang
 *
 */
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
	// ~ Static fields/initializers
	// =====================================================================================

	private String mobileParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE;
	private boolean postOnly = true;

	// ~ Constructors
	// ===================================================================================================

	public SmsCodeAuthenticationFilter() {
		//"/authentication/mobile"
		super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, "POST"));
	}

	// ~ Methods
	// ========================================================================================================
	//Authentication logic
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}

		String mobile = obtainMobile(request);

		if (mobile == null) {
			mobile = "";
		}

		mobile = mobile.trim();

		SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		//Pass the SmsCodeAuthenticationToken to AuthenticationManager, which calls the chain of AuthenticationProvider s.
		//Finally, this SmsCodeAuthentication Token is processed by the SmsCodeAuthentication Provider and returned to Authentication
		return this.getAuthenticationManager().authenticate(authRequest);
	}


	/**
	 * Get your mobile number
	 */
	protected String obtainMobile(HttpServletRequest request) {
		return request.getParameter(mobileParameter);
	}

	/**
	 * Provided so that subclasses may configure what is put into the
	 * authentication request's details property.
	 *
	 * @param request
	 *            that an authentication request is being created for
	 * @param authRequest
	 *            the authentication request object that should have its details
	 *            set
	 */
	protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	/**
	 * Sets the parameter name which will be used to obtain the username from
	 * the login request.
	 *
	 * @param usernameParameter
	 *            the parameter name. Defaults to "username".
	 */
	public void setMobileParameter(String usernameParameter) {
		Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
		this.mobileParameter = usernameParameter;
	}


	/**
	 * Defines whether only HTTP POST requests will be allowed by this filter.
	 * If set to true, and an authentication request is received which is not a
	 * POST request, an exception will be raised immediately and authentication
	 * will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
	 * will be called as if handling a failed authentication.
	 * <p>
	 * Defaults to <tt>true</tt> but may be overridden by subclasses.
	 */
	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}

	public final String getMobileParameter() {
		return mobileParameter;
	}

}

4.8 SmsCodeAuthenticationProvider

  1. implements AuthenticationProvider
  2. supports: Returns whether a certain type of validation is supported
  3. authenticate: Verify logic, return Authentication
/**
 * 
 */
package com.wxm.spring.security.core.authentication.mobile;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * @author zhailiang
 *
 */
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

	private UserDetailsService userDetailsService;

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.security.authentication.AuthenticationProvider#
	 * authenticate(org.springframework.security.core.Authentication)
	 */
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
		
		UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());

		if (user == null) {
			throw new InternalAuthenticationServiceException("Unable to get user information");
		}
		
		SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());
		
		authenticationResult.setDetails(authenticationToken.getDetails());

		return authenticationResult;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.security.authentication.AuthenticationProvider#
	 * supports(java.lang.Class)
	 */
	@Override
	public boolean supports(Class<?> authentication) {
		return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
	}

	public UserDetailsService getUserDetailsService() {
		return userDetailsService;
	}

	public void setUserDetailsService(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

}

4.9 SmsCodeAuthenticationSecurityConfig

  1. extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>
  2. Contains AuthenticationSuccessHandler, AuthenticationFailureHandler, userDetailsService
  3. Overloaded configure
    Configure SmsCodeAuthenticationFilter
    Configure SmsCodeAuthentication Provider
    Insert smsCodeAuthenticationFilter before UsernamePasswordAuthenticationFilter
	@Override
	public void configure(HttpSecurity http) throws Exception {
		
		SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
		smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
		smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
		smsCodeAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
		
		SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
		smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
		
		http.authenticationProvider(smsCodeAuthenticationProvider)
			.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
		
	}

4. Miscellaneous

4.1 Dependent Queries

@Autowired
private Map<String, ValidateCodeProcessor> validateCodeProcessors;

4.2 Code Refactoring

BrowserSecurityConfig
SecurityConstants

Five original articles were published. 0% praised. 53% visited
Private letter follow

Posted by TheSeeker on Thu, 06 Feb 2020 21:11:52 -0800