短信验证码

短信验证码的发送

需要实现对手机的发送验证码,这里只是简单的处理,打印出来

  • 定义接口//发送短信的接口public interface SmsCodeSender {

      void send(String mobile, String code);
      }
      //生成短信接口
      public interface ValidateCodeGenerator {
    
      ValidateCode generate(ServletWebRequest  request);
    
      }
    
  • 实现接口

      //发送短信的接口的实现
      public class DefaultSmsCodeSender implements SmsCodeSender {
    
      /* (non-Javadoc)
       * [@see](https://my.oschina.net/weimingwei) com.imooc.security.core.validate.code.sms.SmsCodeSender#send(java.lang.String, java.lang.String)
    
      //生成短信接口的实现
      [@Override](https://my.oschina.net/u/1162528)
      public void send(String mobile, String code) {
      	System.out.println("向手机"+mobile+"发送短信验证码"+code);
      }
      }
    
  • 生成手机验证码@Component("smsValidateCodeGenerator")public class SmsCodeGenerator implements ValidateCodeGenerator {

      	@Autowired
      	private SecurityProperties securityProperties;
    
      	/*
      	 * (non-Javadoc)
      	 *
      	 * [@see](https://my.oschina.net/weimingwei)
      	 * com.imooc.security.core.validate.code.ValidateCodeGenerator#generate(org.
      	 * springframework.web.context.request.ServletWebRequest)
      	 */
      	public ValidateCode generate(ServletWebRequest request) {
      		String code = RandomStringUtils.randomNumeric(securityProperties.getCode().getSms().getLegth());
      		return new ValidateCode(code, securityProperties.getCode().getSms().getExpireIn());
      	}
    
      	public SecurityProperties getSecurityProperties() {
      		return securityProperties;
      	}
    
      	public void setSecurityProperties(SecurityProperties securityProperties) {
      		this.securityProperties = securityProperties;
      	}
      }
    
  • 发送手机验证码

      @GetMapping("/code/sms")
      public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletRequestBindingException {
         ImageCode imageCode = generate(request);
      	ValidateCode smsCode = imageCodeGenerator.generate(new ServletWebRequest(request));
      	sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, smsCode);
      	String mobile = ServletRequestUtils.getRequiredStringParameter(request,"mobile");
      	smsCodeSender.send(mobile,smsCode.getCode());
      }
    

上面的部分实现了手机验证码的生成,接下来要做的就是手机验证码的验证。

手机验证码的验证

手机验证码的验证因为不像用户名密码那样可以直接登录,为了实现手机的验证,需要模仿用户名和密码的登录方式,对用户名登录的那一套进行改造,需要创建一个短信验证过滤器SmsAuthenticationFilter,短信验证码的token,SmsAuthenticaitonToken,然后需要一个验证手机号的Provider,SmsAuthenticationProvider,然后将手机号传递给UserDetailService进行相关的处理.

SpringSecurity之短信验证码-LMLPHP

SmsAuthenticationToken的代码如下:

/**
 * 封装登录信息
 */
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
	private static final long serialVersionUID = 420L;
	//放认证信息(登录之前放手机号,登录成功之后存放用户的信息)
	private final Object principal;

	public SmsCodeAuthenticationToken(String mobile) {
		super((Collection)null);
		this.principal = mobile;
		this.setAuthenticated(false);
	}

	public SmsCodeAuthenticationToken(Object principal,  Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		super.setAuthenticated(true);
	}

	public Object getCredentials() {
		return null;
	}

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

	public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
		if (isAuthenticated) {
			throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
		} else {
			super.setAuthenticated(false);
		}
	}

	public void eraseCredentials() {
		super.eraseCredentials();
	}
}

SmsCodeAuthenticationFilter.java

	public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
		// ~ Static fields/initializers
		// =====================================================================================
		//拦截的参数的名称
		private String mobileParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE;
		//表示是否只允许post请求
		private boolean postOnly = true;

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

		/**
		 * 指定过滤器拦截的请求
		 */
		public SmsCodeAuthenticationFilter() {
			super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, "POST"));
		}

		// ~ Methods
		// ========================================================================================================

		public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
				throws AuthenticationException {
			//判断是不是post请求
			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);
			//认证前将请求的信息放入SmsCodeAuthenticationToken
			// Allow subclasses to set the "details" property
			setDetails(request, authRequest);

			return this.getAuthenticationManager().authenticate(authRequest);
		}


		/**
		 * 获取手机号
		 */
		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;
		}

	}

SmsCodeAuthenticationProvider.java

	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("无法获取用户信息");
			}
			//将认证的用户信息重新构建成一个对象
			SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());
			/*
			SmsCodeAuthenticationFilter的
			  将之前没验证的SmsCodeAuthenticationToken内的请求信息重新放入到新创建的token里面
			 */
			authenticationResult.setDetails(authenticationToken.getDetails());
			return authenticationResult;
		}

		/*
		 *判断传递进来的东西是不是SmsCodeAuthenticationToken
		 * (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;
		}
	}

SmsCodeAuthenticationSecurityConfig.java(短息验证的配置)

	/**
	 * 该模块是要在app,也要在browser里面用
	 */
	@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 builder) throws Exception {
		SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
		//设置manager
		smsCodeAuthenticationFilter.setAuthenticationManager(builder.getSharedObject(AuthenticationManager.class));
		smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
		smsCodeAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
		//设置Provider
		SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
		smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
		//将我们自定义的Provider加入到所有provider的集合里面
		builder.authenticationProvider(smsCodeAuthenticationProvider)
				.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
		}
	}


	/**
	 * 专门用来做web安全应用的适配器WebSecurityConfigurerAdapter
	 */
	@Configuration
	public class BrowswerSecurityConfig extends AbstractChannelSecurityConfig {
		@Autowired
		private SecurityProperties securityProperties;

		@Autowired
		private DataSource dataSource;

		@Autowired
		private UserDetailsService userDetailsService;

		@Autowired
		private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

		@Autowired
		private ValidateCodeSecurityConfig validateCodeSecurityConfig;

		/**
		 * 配置密码编码器
		 *
		 * @return
		 */
		@Bean
		public PasswordEncoder passwordEncoder() {
			return new BCryptPasswordEncoder();
		}
		@Primary
		@Bean(name = "dataSource")
		@ConfigurationProperties(prefix = "spring.datasource")
		public DataSource dataSource(){
			return DataSourceBuilder.create().build();
		}
		/**
		 * @param
		 * @throws Exception
		 */
		@Bean
		public PersistentTokenRepository persistentTokenRepository() {
			JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
			//配置数据源
			tokenRepository.setDataSource(dataSource);
			//配置在启动的时候创建表
	//        tokenRepository.setCreateTableOnStartup(true);
			return tokenRepository;

		}


		@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()
					.authorizeRequests()
					.antMatchers(
							SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
							SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
							securityProperties.getBrowser().getLoginPage(),
							SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*")
					.permitAll()
					.anyRequest()
					.authenticated()
					.and()
					.csrf().disable();

		}
	}

之后就可以实现短信的验证了

04-13 21:40