須要實現對手機的發送驗證碼,這裏只是簡單的處理,打印出來java
定義接口 //發送短信的接口 public interface SmsCodeSender {web
void send(String mobile, String code); } //生成短信接口 public interface ValidateCodeGenerator { ValidateCode generate(ServletWebRequest request); }
實現接口spring
//發送短信的接口的實現 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; } }
發送手機驗證碼session
@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()); }
上面的部分實現了手機驗證碼的生成,接下來要作的就是手機驗證碼的驗證。app
手機驗證碼的驗證由於不像用戶名密碼那樣能夠直接登陸,爲了實現手機的驗證,須要模仿用戶名和密碼的登陸方式,對用戶名登陸的那一套進行改造,須要建立一個短信驗證過濾器SmsAuthenticationFilter,短信驗證碼的token,SmsAuthenticaitonToken,而後須要一個驗證手機號的Provider,SmsAuthenticationProvider,而後將手機號傳遞給UserDetailService進行相關的處理.dom
SmsAuthenticationToken的代碼以下:ide
/** * 封裝登陸信息 */ 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.javaweb安全
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.javapost
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(); } }
以後就能夠實現短信的驗證了