日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Spring Security构建Rest服务-0702-短信验证码登录

發布時間:2025/3/21 110 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Security构建Rest服务-0702-短信验证码登录 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

先來看下?Spring Security密碼登錄大概流程,模擬這個流程,開發短信登錄流程

1,密碼登錄請求發送給過濾器 UsernamePasswordAuthenticationFilter?

2,過濾器拿出用戶名密碼組裝成 UsernamePasswordAuthenticationToken 對象傳給AuthenticationManager

3,AuthenticationManager 會從一堆 AuthenticationProvider 里選出一個Provider 處理認證請求。挑選的依據是AuthenticationProvider 里有個

boolean supports(Class<?> authentication);方法,判斷當前的provider是否支持傳進的token,如果支持就用這個provider認證這個token,并調用authenticate() 方法 進行認證

4,認證過程會調用UserDetailsService獲取用戶信息,跟傳進來的登錄信息做比對。認證通過會把UsernamePasswordAuthenticationToken做一個標識? ?標記已認證,放進session。

做短信登錄,不能在這個流程上改,這是兩種不同的登錄方式,混在一起代碼質量不好,需要仿照這個流程寫一套自己的流程:

SmsAuthenticationFilter:攔截短信登錄請求,從請求中獲取手機號,封裝成 SmsAuthenticationToken 也會傳給AuthenticationManager,AuthenticationManager會找適合的provider,自定義SmsAuthenticationProvider校驗SmsAuthenticationToken 里手機號信息。也會調UserDetailsService 看是否能登錄,能的話標記為已登錄。

其中SmsAuthenticationFilter 參考UsernamePasswordAuthenticationFilter寫,SmsCodeAuthenticationToken參考UsernamePasswordAuthenticationToken寫,其實就是就是復制粘貼改改

從上圖可知,需要寫三個類:

1,SmsAuthenticationToken:復制UsernamePasswordAuthenticationToken,把沒用的去掉

package com.imooc.security.core.authentication.mobile;import java.util.Collection;import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion;/*** 模仿UsernamePasswordAuthenticationToken寫的短信登錄token* ClassName: SmsCodeAuthenticationToken * @Description: TODO* @author lihaoyang* @date 2018年3月7日*/ public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;//沒登陸,放手機號,登錄成功,放用戶信息private final Object principal;/*** 沒登錄放手機號* <p>Description: </p>* @param mobile*/public SmsCodeAuthenticationToken(String mobile) {super(null);this.principal = mobile;//沒登錄放手機號setAuthenticated(false);//沒登錄 }public SmsCodeAuthenticationToken(Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;super.setAuthenticated(true); // must use super, as we override }// ~ Methods// ========================================================================================================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);}@Overridepublic void eraseCredentials() {super.eraseCredentials();}@Overridepublic Object getCredentials() {return null;} }

2,SmsCodeAuthenticationFilter,參考UsernamePasswordAuthenticationFilter

package com.imooc.security.core.authentication.mobile;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 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;/*** 模仿UsernamePasswordAuthenticationFilter 寫的短信驗證碼過濾器* ClassName: SmsCodeAuthenticationFilter * @Description: TODO* @author lihaoyang* @date 2018年3月8日*/ public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter{public static final String IMOOC_FORM_MOBILE_KEY = "mobile";private String mobilePatameter = IMOOC_FORM_MOBILE_KEY;private boolean postOnly = true;// ~ Constructors// ===================================================================================================public SmsCodeAuthenticationFilter() {//過濾的請求url,登錄表單的urlsuper(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);//在這里把SmsCodeAuthenticationToken交給AuthenticationManagerreturn this.getAuthenticationManager().authenticate(authRequest);}/*** 獲取手機號* @Description: TODO* @param @param request* @param @return * @return String * @throws* @author lihaoyang* @date 2018年3月7日*/private String obtainMobile(HttpServletRequest request) {return request.getParameter(mobilePatameter);} protected void setDetails(HttpServletRequest request,SmsCodeAuthenticationToken authRequest) {authRequest.setDetails(authenticationDetailsSource.buildDetails(request));} public void setPostOnly(boolean postOnly) {this.postOnly = postOnly;}}

3,SmsCodeAuthenticationProvider:

?在?SmsCodeAuthenticationFilter 里?attemptAuthentication方法的最后,?return this.getAuthenticationManager().authenticate(authRequest);這句話就是進到 SmsCodeAuthenticationProvider 先調用?supports() 方法,通過后,再調用?authenticate()方法進行認證

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;/*** AuthenticationManager 認證時候需要用的一個Provider* ClassName: SmsCodeAuthenticationProvider * @Description: TODO* @author lihaoyang* @date 2018年3月8日*/ public class SmsCodeAuthenticationProvider implements AuthenticationProvider {private UserDetailsService userDetailsService;/*** 認證*/@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {//能進到這說明authentication是SmsCodeAuthenticationToken,轉一下SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken)authentication;//token.getPrincipal()就是手機號 mobileUserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());//認證沒通過if(user == null){throw new InternalAuthenticationServiceException("無法獲取用戶信息");}//認證通過SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());//把認證之前得token里存的用戶信息賦值給認證后的token對象 authenticationResult.setDetails(authenticationToken.getDetails());return authenticationResult;}/*** 告訴AuthenticationManager,如果是SmsCodeAuthenticationToken的話用這個類處理*/@Overridepublic boolean supports(Class<?> authentication) {//判斷傳進來的authentication是不是SmsCodeAuthenticationToken類型的return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);}public UserDetailsService getUserDetailsService() {return userDetailsService;}public void setUserDetailsService(UserDetailsService userDetailsService) {this.userDetailsService = userDetailsService;}}

短信 驗證碼過濾器,照著圖片驗證碼過濾器寫,其實可以重構,不會弄:

package com.imooc.security.core.validate.code;import java.io.IOException; import java.util.HashSet; import java.util.Set;import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.social.connect.web.HttpSessionSessionStrategy; import org.springframework.social.connect.web.SessionStrategy; import org.springframework.util.AntPathMatcher; import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.filter.OncePerRequestFilter;import com.imooc.security.core.properties.SecurityConstants; import com.imooc.security.core.properties.SecurityProperties;/*** 短信驗證碼過濾器* ClassName: ValidateCodeFilter * @Description:* 繼承OncePerRequestFilter:spring提供的工具,保證過濾器每次只會被調用一次* 實現 InitializingBean接口的目的:* 在其他參數都組裝完畢的時候,初始化需要攔截的urls的值* @author lihaoyang* @date 2018年3月2日*/ public class SmsCodeFilter extends OncePerRequestFilter implements InitializingBean{private Logger logger = LoggerFactory.getLogger(getClass());//認證失敗處理器private AuthenticationFailureHandler authenticationFailureHandler;//獲取session工具類private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();//需要攔截的url集合private Set<String> urls = new HashSet<>();//讀取配置private SecurityProperties securityProperties;//spring工具類private AntPathMatcher antPathMatcher = new AntPathMatcher();/*** 重寫InitializingBean的方法,設置需要攔截的urls*/@Overridepublic void afterPropertiesSet() throws ServletException {super.afterPropertiesSet();//讀取配置的攔截的urlsString[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getSms().getUrl(), ",");//如果配置了需要驗證碼攔截的url,不判斷,如果沒有配置會空指針if(configUrls != null && configUrls.length > 0){for (String configUrl : configUrls) {logger.info("ValidateCodeFilter.afterPropertiesSet()--->配置了驗證碼攔截接口:"+configUrl);urls.add(configUrl);}}else{logger.info("----->沒有配置攔驗證碼攔截接口<-------");}//短信驗證碼登錄一定攔截 urls.add(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {//如果是 登錄請求 則執行 // if(StringUtils.equals("/authentication/form", request.getRequestURI()) // &&StringUtils.equalsIgnoreCase(request.getMethod(), "post")){ // try { // validate(new ServletWebRequest(request)); // } catch (ValidateCodeException e) { // //調用錯誤處理器,最終調用自己的 // authenticationFailureHandler.onAuthenticationFailure(request, response, e); // return ;//結束方法,不再調用過濾器鏈 // } // }/*** 可配置的驗證碼校驗* 判斷請求的url和配置的是否有匹配的,匹配上了就過濾*/boolean action = false;for(String url:urls){if(antPathMatcher.match(url, request.getRequestURI())){action = true;}}if(action){try {validate(new ServletWebRequest(request));} catch (ValidateCodeException e) {//調用錯誤處理器,最終調用自己的 authenticationFailureHandler.onAuthenticationFailure(request, response, e);return ;//結束方法,不再調用過濾器鏈 }}//不是登錄請求,調用其它過濾器鏈 filterChain.doFilter(request, response);}/*** 校驗驗證碼* @Description: 校驗驗證碼* @param @param request* @param @throws ServletRequestBindingException * @return void * @throws ValidateCodeException* @author lihaoyang* @date 2018年3月2日*/private void validate(ServletWebRequest request) throws ServletRequestBindingException {//拿出session中的ImageCode對象ValidateCode smsCodeInSession = (ValidateCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY_SMS);//拿出請求中的驗證碼String imageCodeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "smsCode");//校驗if(StringUtils.isBlank(imageCodeInRequest)){throw new ValidateCodeException("驗證碼不能為空");}if(smsCodeInSession == null){throw new ValidateCodeException("驗證碼不存在,請刷新驗證碼");} if(smsCodeInSession.isExpired()){//從session移除過期的驗證碼 sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY_SMS);throw new ValidateCodeException("驗證碼已過期,請刷新驗證碼");}if(!StringUtils.equalsIgnoreCase(smsCodeInSession.getCode(), imageCodeInRequest)){throw new ValidateCodeException("驗證碼錯誤");}//驗證通過,移除session中驗證碼 sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY_SMS);}public AuthenticationFailureHandler getAuthenticationFailureHandler() {return authenticationFailureHandler;}public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {this.authenticationFailureHandler = authenticationFailureHandler;}public SecurityProperties getSecurityProperties() {return securityProperties;}public void setSecurityProperties(SecurityProperties securityProperties) {this.securityProperties = securityProperties;}}

把新建的這三個類做下配置,讓spring security知道

SmsCodeAuthenticationSecurityConfig:

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;/*** 短信驗證碼配置* ClassName: SmsCodeAuthenticationSecurityConfig * @Description: TODO* @author lihaoyang* @date 2018年3月8日*/ @Component public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {@Autowiredprivate AuthenticationFailureHandler imoocAuthenticationFailureHandler;@Autowiredprivate AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;@Autowiredprivate UserDetailsService userDetailsService;@Overridepublic void configure(HttpSecurity http) throws Exception {//1,配置短信驗證碼過濾器SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));//設置認證失敗成功處理器 smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);smsCodeAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);//配置pproviderSmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);http.authenticationProvider(smsCodeAuthenticationProvider).addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}}

最后在BrowserSecurityConfig里配置短信驗證碼

@Configuration //這是一個配置 public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter{//讀取用戶配置的登錄頁配置 @Autowiredprivate SecurityProperties securityProperties;//自定義的登錄成功后的處理器 @Autowiredprivate AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;//自定義的認證失敗后的處理器 @Autowiredprivate AuthenticationFailureHandler imoocAuthenticationFailureHandler;//數據源 @Autowiredprivate DataSource dataSource;@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;@Autowiredprivate SpringSocialConfigurer imoocSocialSecurityConfig;//注意是org.springframework.security.crypto.password.PasswordEncoder @Beanpublic PasswordEncoder passwordencoder(){//BCryptPasswordEncoder implements PasswordEncoderreturn new BCryptPasswordEncoder();}/*** 記住我TokenRepository配置,在登錄成功后執行* 登錄成功后往數據庫存token的* @Description: 記住我TokenRepository配置* @param @return JdbcTokenRepositoryImpl* @return PersistentTokenRepository * @throws* @author lihaoyang* @date 2018年3月5日*/@Beanpublic PersistentTokenRepository persistentTokenRepository(){JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);//啟動時自動生成相應表,可以在JdbcTokenRepositoryImpl里自己執行CREATE_TABLE_SQL腳本生成表//第二次啟動表已存在,需要注釋 // jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;}//版本二:可配置的登錄頁 @Overrideprotected void configure(HttpSecurity http) throws Exception {//~~~-------------> 圖片驗證碼過濾器 <------------------ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();//驗證碼過濾器中使用自己的錯誤處理 validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);//配置的驗證碼過濾url validateCodeFilter.setSecurityProperties(securityProperties);validateCodeFilter.afterPropertiesSet();//~~~-------------> 短信驗證碼過濾器 <------------------SmsCodeFilter smsCodeFilter = new SmsCodeFilter();//驗證碼過濾器中使用自己的錯誤處理 smsCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);//配置的驗證碼過濾url smsCodeFilter.setSecurityProperties(securityProperties);smsCodeFilter.afterPropertiesSet();//實現需要認證的接口跳轉表單登錄,安全=認證+授權//http.httpBasic() //這個就是默認的彈框認證// http .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) // .apply(imoocSocialSecurityConfig)//社交登錄 // .and()//把驗證碼過濾器加載登錄過濾器前邊.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//表單認證相關配置 .formLogin() .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL) //處理用戶認證BrowserSecurityController//登錄過濾器UsernamePasswordAuthenticationFilter默認登錄的url是"/login",在這能改 .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM) .successHandler(imoocAuthenticationSuccessHandler)//自定義的認證后處理器.failureHandler(imoocAuthenticationFailureHandler) //登錄失敗后的處理 .and()//記住我相關配置 .rememberMe().tokenRepository(persistentTokenRepository())//TokenRepository,登錄成功后往數據庫存token的.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())//記住我秒數.userDetailsService(userDetailsService) //記住我成功后,調用userDetailsService查詢用戶信息 .and()//授權相關的配置 .authorizeRequests() // /authentication/require:處理登錄,securityProperties.getBrowser().getLoginPage():用戶配置的登錄頁 .antMatchers(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,securityProperties.getBrowser().getLoginPage(),//放過登錄頁不過濾,否則報錯SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*").permitAll() //驗證碼.anyRequest() //任何請求.authenticated() //都需要身份認證 .and().csrf().disable() //關閉csrf防護.apply(smsCodeAuthenticationSecurityConfig);//把短信驗證碼配置應用上 } }

訪問登陸頁,點擊發送驗證碼模擬發送驗證碼

輸入后臺打印的驗證碼

登錄成功:

完整代碼在github:https://github.com/lhy1234/spring-security

?

轉載于:https://www.cnblogs.com/lihaoyang/p/8523279.html

總結

以上是生活随笔為你收集整理的Spring Security构建Rest服务-0702-短信验证码登录的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。