html
實現短信認證流程
實現短信驗證碼接口
1. 生成驗證碼
2. 存入session
3. 發送給手機驗證碼
仿寫短信認證邏輯
1. token認證信息,創建token SmsCodeAuthenticationToken extends AbstractAuthenticationToken 2. 創建 Provider, 根據token類型AuthenticationManager 會調用相應的Provider,獲取UserDetailsService user信息等 SmsCodeAuthenticationProvider implements AuthenticationProvider 3. 創建filter 具體攔截什么請求,生成 Token信息 SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter 4. 創建配置類 SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> 實現configure 方法 添加自定義的SmsCodeAuthenticationProvider, 和SmsCodeAuthenticationFilter 5.在項目中引用: .apply(smsCodeAuthenticationSecurityConfig);
1.SmsCodeAuthenticationToken.java
package com.imooc.security.core.authentication.mobile; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import java.util.Collection; /** * @Title: SmsCodeAuthenticationToken * @ProjectName spring-security-main * @date 2020/12/211:17 */ public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private final Object principal; // 登錄之前存手機號, 登錄之后存用戶信息 public SmsCodeAuthenticationToken(String mobile) { super(null); this.principal = mobile; setAuthenticated(false); } public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; super.setAuthenticated(true); // must use super, as we override } // ~ Methods // ======================================================================================================== 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"); } super.setAuthenticated(false); } @Override public void eraseCredentials() { super.eraseCredentials(); } }
2. 創建 Provider
package com.imooc.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; /** * @Title: SmsCodeAuthenticationProvider * @ProjectName spring-security-main * @date 2020/12/211:37 */ public class SmsCodeAuthenticationProvider implements AuthenticationProvider { private UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 1 獲取token SmsCodeAuthenticationToken smsCodeAuthenticationToken = (SmsCodeAuthenticationToken) authentication; // 2. 獲取user UserDetails user = userDetailsService.loadUserByUsername((String) smsCodeAuthenticationToken.getPrincipal()); if (user == null) { throw new InternalAuthenticationServiceException("無法獲取用戶信息"); } SmsCodeAuthenticationToken token = new SmsCodeAuthenticationToken(user, user.getAuthorities()); token.setDetails(smsCodeAuthenticationToken.getDetails()); return token; } @Override public boolean supports(Class<?> authentication) { return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication); } public UserDetailsService getUserDetailsService() { return userDetailsService; } public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } }
3. 創建filter 具體攔截什么請求
package com.imooc.security.core.authentication.mobile; 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 javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Title: SmsCodeAuthenticationFilter * @ProjectName spring-security-main * @date 2020/12/211:24 */ public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter { // ~ Static fields/initializers // ===================================================================================== public static final String IMOOC_FORM_MOBILE_KEY = "mobile"; private String mobileParameter = IMOOC_FORM_MOBILE_KEY; private boolean postOnly = true; // ~ Constructors // =================================================================================================== public SmsCodeAuthenticationFilter() { super(new AntPathRequestMatcher("/authentication/mobile", "POST")); } // ~ Methods // ======================================================================================================== 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); return this.getAuthenticationManager().authenticate(authRequest); } protected String obtainMobile(HttpServletRequest request) { return request.getParameter(mobileParameter); } protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } }
4. 創建配置類
package com.imooc.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; @Component public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { // 自定義的成功失敗處理器 @Autowired private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler; @Autowired private AuthenticationFailureHandler imoocAuthenticationFailureHandler; @Autowired private UserDetailsService userDetailsService; /** * 加入自定義的filter , provider * @param http * @throws Exception */ @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); } }
5.在項目中引用:
package com.imooc.security.browser; import com.imooc.security.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig; import com.imooc.security.core.properties.SecurityProperties; import com.imooc.security.core.validate.code.SmsCodeFilter; import com.imooc.security.core.validate.code.ValidateCodeFilter; 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.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import javax.sql.DataSource; @Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler; @Autowired private AuthenticationFailureHandler imoocAuthenctiationFailureHandler; @Bean public PasswordEncoder passwordEncoder() { return new SCryptPasswordEncoder(); } @Autowired private SecurityProperties securityProperties; // PersistentTokenRepository @Autowired private DataSource dataSource; @Autowired private UserDetailsService userDetailsService; // MyUserDetailsService /** * 記住我功能 * 1. 創建PersistentTokenRepository * 2. 設置過期時間 * 3. 獲取UserDetailsService 用戶登錄信息 * 4. 配置rememberMe 生效 * @return */ @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); // 配置的dataSource // jdbcTokenRepository.setCreateTableOnStartup(true); // 自動創建存放記住我的表,如果存在會報錯 return jdbcTokenRepository; } @Autowired private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig; @Override public void configure(HttpSecurity http) throws Exception { // 圖片驗證碼過濾器 ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter(); validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenctiationFailureHandler); validateCodeFilter.setSecurityProperties(securityProperties); validateCodeFilter.afterPropertiesSet(); // 驗證碼過濾器 SmsCodeFilter smsCodeFilter = new SmsCodeFilter(); smsCodeFilter.setAuthenticationFailureHandler(imoocAuthenctiationFailureHandler); smsCodeFilter.setSecurityProperties(securityProperties); smsCodeFilter.afterPropertiesSet(); // 添加一個圖片驗證filter, 在UsernamePasswordAuthenticationFilter之前執行 http .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // .httpBasic() // 默認方式 .formLogin() // 設置認證的登錄方式 表單方式 .loginPage("/authentication/require") // 自定義登錄頁面 .loginProcessingUrl("/authentication/form") // 自定義表單提交的url, 默認是login .successHandler(imoocAuthenticationSuccessHandler) // 不適用默認的認證成功處理器 .failureHandler(imoocAuthenctiationFailureHandler) // 登錄失敗處理器 // .failureForwardUrl("/authentication/require") // .failureUrl("/authentication/require") .and() .rememberMe() .tokenRepository(persistentTokenRepository()) // rememberME 有效期 .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds()) .userDetailsService(userDetailsService) .and() .authorizeRequests() // 需要授權 // 當匹配到這個頁面時,不需要授權 .antMatchers("/authentication/require", securityProperties.getBrowser().getLoginPage(), "/code/*").permitAll() .anyRequest() // 所有請求 .authenticated() .and() // 關閉csrf .csrf() .disable() .apply(smsCodeAuthenticationSecurityConfig); } }
controller
@GetMapping("/code/sms") public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletRequestBindingException { ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request)); // 放入session sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_SMS_KEY, smsCode); // 通過短信服務商發送短信驗證碼到手機 String mobile = ServletRequestUtils.getStringParameter(request, "mobile"); smsCodeSender.send(mobile, smsCode.getCode()); }