日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringSecurity短信验证码登录

發布時間:2023/12/31 javascript 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringSecurity短信验证码登录 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

短信驗證碼登錄

時下另一種非常常見的網站登錄方式為手機短信驗證碼登錄,但Spring Security默認只提供了賬號密碼的登錄認證邏輯,所以要實現手機短信驗證碼登錄認證功能,我們需要模仿Spring Security賬號密碼登錄邏輯代碼來實現一套自己的認證邏輯。

短信驗證碼生成

我們先定義一個短信驗證碼對象SmsCode :

public class SmsCode {private String code;private LocalDateTime expireTime;public SmsCode(String code, int expireIn) {this.code = code;this.expireTime = LocalDateTime.now().plusSeconds(expireIn);}public SmsCode(String code, LocalDateTime expireTime) {this.code = code;this.expireTime = expireTime;}boolean isExpire() {return LocalDateTime.now().isAfter(expireTime);}// get,set略 }

接著在ValidateCodeController中加入生成短信驗證碼相關請求對應的方法:

@RestController public class ValidateController {public final static String SESSION_KEY_SMS_CODE = "SESSION_KEY_SMS_CODE";private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();@GetMapping("/code/sms")public void createSmsCode(HttpServletRequest request, HttpServletResponse response, String mobile) throws IOException {SmsCode smsCode = createSMSCode();sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY_SMS_CODE + mobile, smsCode);// 輸出驗證碼到控制臺代替短信發送服務System.out.println("您的登錄驗證碼為:" + smsCode.getCode() + ",有效時間為60秒");}private SmsCode createSMSCode() {String code = RandomStringUtils.randomNumeric(6);return new SmsCode(code, 60);} }

這里我們使用createSMSCode方法生成了一個6位的純數字隨機數,有效時間為60秒。然后通過SessionStrategy對象的setAttribute方法將短信驗證碼保存到了Session中,對應的key為SESSION_KEY_SMS_CODE。

我們需要在Spring Security中配置/code/sms路徑免驗證:

@Override protected void configure(HttpSecurity http) throws Exception {http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加驗證碼校驗過濾器.formLogin() // 表單登錄// http.httpBasic() // HTTP Basic.loginPage("/authentication/require") // 登錄跳轉 URL.loginProcessingUrl("/login") // 處理表單登錄 URL.successHandler(authenticationSucessHandler) // 處理登錄成功.failureHandler(authenticationFailureHandler) // 處理登錄失敗.and().authorizeRequests() // 授權配置.antMatchers("/authentication/require","/login.html", "/code/image","/code/sms").permitAll() // 無需認證的請求路徑.anyRequest() // 所有請求.authenticated() // 都需要認證.and().csrf().disable(); }

改造登錄頁

我們在登錄頁面中加入一個與手機短信驗證碼認證相關的Form表單:

<form class="login-page" action="/login/mobile" method="post"><div class="form"><h3>短信驗證碼登錄</h3><input type="text" placeholder="手機號" name="mobile" value="17777777777" required="required"/><span style="display: inline"><input type="text" name="smsCode" placeholder="短信驗證碼" style="width: 50%;"/><a href="/code/sms?mobile=17777777777">發送驗證碼</a></span><button type="submit">登錄</button></div> </form>

如圖:

控制臺:

您的登錄驗證碼為:875067,有效時間為60秒

添加短信驗證碼認證

在Spring Security中,使用用戶名密碼認證的過程大致如下圖所示:

Spring Security使用UsernamePasswordAuthenticationFilter過濾器來攔截用戶名密碼認證請求,將用戶名和密碼封裝成一個UsernamePasswordToken對象交給AuthenticationManager處理。AuthenticationManager將挑出一個支持處理該類型Token的AuthenticationProvider(這里為DaoAuthenticationProvider,AuthenticationProvider的其中一個實現類)來進行認證,認證過程中DaoAuthenticationProvider將調用UserDetailService的loadUserByUsername方法來處理認證,如果認證通過(即UsernamePasswordToken中的用戶名和密碼相符)則返回一個UserDetails類型對象,并將認證信息保存到Session中,認證后我們便可以通過Authentication對象獲取到認證的信息了。

一、定義SmsAuthenticationToken

查看UsernamePasswordAuthenticationToken的源碼,將其復制出來重命名為SmsAuthenticationToken,并稍作修改,修改后的代碼如下所示:

public class SmsAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;private final Object principal;public SmsAuthenticationToken(String mobile) {super(null);this.principal = mobile;setAuthenticated(false);}public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;super.setAuthenticated(true); // must use super, as we override}@Overridepublic 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);}@Overridepublic void eraseCredentials() {super.eraseCredentials();} }

二、定義SmsAuthenticationFilter

定義完SmsAuthenticationToken后,我們接著定義用于處理短信驗證碼登錄請求的過濾器SmsAuthenticationFilter,同樣的復制UsernamePasswordAuthenticationFilter源碼并稍作修改:

public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {public static final String MOBILE_KEY = "mobile";private String mobileParameter = MOBILE_KEY;private boolean postOnly = true;public SmsAuthenticationFilter() {super(new AntPathRequestMatcher("/login/mobile", "POST"));}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();SmsAuthenticationToken authRequest = new SmsAuthenticationToken(mobile);setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}protected String obtainMobile(HttpServletRequest request) {return request.getParameter(mobileParameter);}protected void setDetails(HttpServletRequest request,SmsAuthenticationToken authRequest) {authRequest.setDetails(authenticationDetailsSource.buildDetails(request));}public void setMobileParameter(String mobileParameter) {Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");this.mobileParameter = mobileParameter;}public void setPostOnly(boolean postOnly) {this.postOnly = postOnly;}public final String getMobileParameter() {return mobileParameter;} }

構造函數中指定了當請求為/login/mobile,請求方法為POST的時候該過濾器生效。mobileParameter屬性值為mobile,對應登錄頁面手機號輸入框的name屬性。attemptAuthentication方法從請求中獲取到mobile參數值,并調用SmsAuthenticationToken的SmsAuthenticationToken(String mobile)構造方法創建了一個SmsAuthenticationToken。下一步就如流程圖中所示的那樣,SmsAuthenticationFilter將SmsAuthenticationToken交給AuthenticationManager處理。

三、定義SmsAuthenticationProvider

在創建完SmsAuthenticationFilter后,我們需要創建一個支持處理該類型Token的類,即SmsAuthenticationProvider,該類需要實現AuthenticationProvider的兩個抽象方法:

public class SmsAuthenticationProvider implements AuthenticationProvider {private UserDetailService userDetailService;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {SmsAuthenticationToken authenticationToken = (SmsAuthenticationToken) authentication;UserDetails userDetails = userDetailService.loadUserByUsername((String) authenticationToken.getPrincipal());if (userDetails == null)throw new InternalAuthenticationServiceException("未找到與該手機號對應的用戶");SmsAuthenticationToken authenticationResult = new SmsAuthenticationToken(userDetails, userDetails.getAuthorities());authenticationResult.setDetails(authenticationToken.getDetails());return authenticationResult;}@Overridepublic boolean supports(Class<?> aClass) {return SmsAuthenticationToken.class.isAssignableFrom(aClass);}public UserDetailService getUserDetailService() {return userDetailService;}public void setUserDetailService(UserDetailService userDetailService) {this.userDetailService = userDetailService;} }

其中supports方法指定了支持處理的Token類型為SmsAuthenticationToken,authenticate方法用于編寫具體的身份認證邏輯。在authenticate方法中,我們從SmsAuthenticationToken中取出了手機號信息,并調用了UserDetailService的loadUserByUsername方法。該方法在用戶名密碼類型的認證中,主要邏輯是通過用戶名查詢用戶信息,如果存在該用戶并且密碼一致則認證成功;而在短信驗證碼認證的過程中,該方法需要通過手機號去查詢用戶,如果存在該用戶則認證通過。認證通過后接著調用SmsAuthenticationToken的SmsAuthenticationToken(Object principal, Collection authorities)構造函數構造一個認證通過的Token,包含了用戶信息和用戶權限。

你可能會問,為什么這一步沒有進行短信驗證碼的校驗呢?實際上短信驗證碼的校驗是在SmsAuthenticationFilter之前完成的,即只有當短信驗證碼正確以后才開始走認證的流程。所以接下來我們需要定一個過濾器來校驗短信驗證碼的正確性。

四、定義SmsCodeFilter

package com.jcl.security.filter;import com.jcl.security.controller.ValidateController; import com.jcl.security.domain.SmsCode; import com.jcl.security.exception.ValidateCodeException; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.social.connect.web.HttpSessionSessionStrategy; import org.springframework.social.connect.web.SessionStrategy; import org.springframework.stereotype.Component; 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 javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;/*** @author 朝花不遲暮* @version 1.0* @date 2020/10/30 10:03*/ @Component public class SmsCodeFilter extends OncePerRequestFilter {@Autowiredprivate AuthenticationFailureHandler authenticationFailureHandler;private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException{if (StringUtils.equalsIgnoreCase("/login/mobile", httpServletRequest.getRequestURI())&& StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")) {try {validateCode(new ServletWebRequest(httpServletRequest));} catch (ValidateCodeException e) {authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);return;}}filterChain.doFilter(httpServletRequest, httpServletResponse);}private void validateCode(ServletWebRequest servletWebRequest) throws ServletRequestBindingException{String smsCodeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "smsCode");String mobileInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "mobile");SmsCode codeInSession = (SmsCode) sessionStrategy.getAttribute(servletWebRequest, ValidateController.SESSION_KEY_SMS_CODE + mobileInRequest);if (StringUtils.isBlank(smsCodeInRequest)) {throw new ValidateCodeException("短信驗證碼不能為空!");}if (codeInSession == null) {throw new ValidateCodeException("短信驗證碼不存在!");}if (codeInSession.isExpire()) {sessionStrategy.removeAttribute(servletWebRequest, ValidateController.SESSION_KEY_IMAGE_CODE);throw new ValidateCodeException("短信驗證碼已過期!");}if (!StringUtils.equalsIgnoreCase(codeInSession.getCode(), smsCodeInRequest)) {throw new ValidateCodeException("短信驗證碼不正確!");}sessionStrategy.removeAttribute(servletWebRequest, ValidateController.SESSION_KEY_IMAGE_CODE);} }

五、配置生效

在定義完所需的組件后,我們需要進行一些配置,將這些組件組合起來形成一個和上面流程圖對應的流程。創建一個配置類SmsAuthenticationConfig:

@Component public class SmsAuthenticationConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {@Autowiredprivate AuthenticationSuccessHandler authenticationSuccessHandler;@Autowiredprivate AuthenticationFailureHandler authenticationFailureHandler;@Autowiredprivate UserDetailService userDetailService;@Overridepublic void configure(HttpSecurity http) throws Exception {SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter();smsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));smsAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);smsAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider();smsAuthenticationProvider.setUserDetailService(userDetailService);http.authenticationProvider(smsAuthenticationProvider).addFilterAfter(smsAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);} }

在流程中第一步需要配置SmsAuthenticationFilter,分別設置了AuthenticationManager、AuthenticationSuccessHandler和AuthenticationFailureHandler屬性。這些屬性都是來自SmsAuthenticationFilter繼承的AbstractAuthenticationProcessingFilter類中。

第二步配置SmsAuthenticationProvider,這一步只需要將我們自個的UserDetailService注入進來即可。

最后調用HttpSecurity的authenticationProvider方法指定了AuthenticationProvider為SmsAuthenticationProvider,并將SmsAuthenticationFilter過濾器添加到了UsernamePasswordAuthenticationFilter后面。

到這里我們已經將短信驗證碼認證的各個組件組合起來了,最后一步需要做的是配置短信驗證碼校驗過濾器,并且將短信驗證碼認證流程加入到Spring Security中。在BrowserSecurityConfig的configure方法中添加如下配置:

@Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate MyAuthenticationSucessHandler authenticationSucessHandler;@Autowiredprivate MyAuthenticationFailureHandler authenticationFailureHandler;@Autowiredprivate ValidateCodeFilter validateCodeFilter;@Autowiredprivate SmsCodeFilter smsCodeFilter;@Autowiredprivate SmsAuthenticationConfig smsAuthenticationConfig;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加驗證碼校驗過濾器.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加短信驗證碼校驗過濾器.formLogin() // 表單登錄// http.httpBasic() // HTTP Basic.loginPage("/authentication/require") // 登錄跳轉 URL.loginProcessingUrl("/login") // 處理表單登錄 URL.successHandler(authenticationSucessHandler) // 處理登錄成功.failureHandler(authenticationFailureHandler) // 處理登錄失敗.and().authorizeRequests() // 授權配置.antMatchers("/authentication/require","/login.html","/code/image","/code/sms").permitAll() // 無需認證的請求路徑.anyRequest() // 所有請求.authenticated() // 都需要認證.and().csrf().disable().apply(smsAuthenticationConfig); // 將短信驗證碼認證配置加到 Spring Security 中} }

重啟項目,訪問http://localhost:8080/login.html,點擊發送驗證碼,控制臺輸出如下:

總結

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

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

主站蜘蛛池模板: 精品久久久亚洲 | 精品国产亚洲AV | 黄色网页在线看 | 女性私密整形视频 | 农村少妇无套内谢粗又长 | 男女污视频 | 亚洲视频中文字幕 | 亚洲资源站 | 韩国一区二区三区在线观看 | 欧美s码亚洲码精品m码 | 午夜精品视频一区二区三区在线看 | 国产一区二区三区四区五区六区 | 国产黄色小视频在线观看 | 国产一区二区精彩视频 | 毛片中文字幕 | 精品无码一区二区三区免费 | 中国一级免费毛片 | 一区二区成人网 | 男女激情av| 中文字幕永久在线播放 | 538国产精品一区二区 | 久久久久久久久久久久久久 | 精品无码久久久久成人漫画 | 手机看片1024国产 | 韩国三级中文字幕hd久久精品 | 黄色片视频免费 | 日韩成人午夜 | 日韩在线免费视频 | 成人录像 | 1769国产精品 | 午夜影院久久久 | 久草天堂 | 看毛片网站| 男人天堂tv | 国产日本欧美一区二区 | 美女热逼 | 水牛影视av一区二区免费 | 久久视频在线看 | 国产色婷婷一区二区 | 成人福利在线播放 | 亚洲九九精品 | 69精品人妻一区二区三区 | 国产真实老熟女无套内射 | 精品无码国产污污污免费网站 | 中文字幕在线观看亚洲 | av在线天堂 | 中文字幕日本在线观看 | 国产精品一二三区视频 | 久久精品一 | 7777在线视频 | 在线免费av播放 | 人人狠狠综合久久亚洲 | 欧洲精品视频在线 | 窝窝午夜精品一区二区 | 99精彩视频 | 五月天婷婷爱 | 波多一区 | 清纯唯美第一页 | 欧美天天性影院 | 午夜电影一区二区 | 少妇无套内谢久久久久 | 超碰男人的天堂 | 老司机免费在线视频 | 脱美女衣服亲摸揉视频 | 成人av免费在线 | 日本人xxxⅹ18hd19hd | 视色av | 久久riav| 日本免费一二区 | 全黄一级裸体 | 黄色精品免费 | 少妇又色又爽又黄的视频 | 99久久伊人 | 日韩av一区二区三区在线观看 | 岳狂躁岳丰满少妇大叫 | 亚洲欧美日韩精品在线 | tube国产麻豆 | 在线观看免费高清在线观看 | 日韩亚洲欧美在线观看 | 综合天天色 | 日韩综合精品 | av毛片在线免费观看 | 亚洲女则毛耸耸bbw 边吃奶边添下面好爽 | 精品久久久久亚洲 | 欧美在线a| 国产精品综合一区二区 | 欧美日韩视频网站 | 亚洲麻豆精品 | 不卡欧美 | 激情久 | 精品视频一二区 | 少妇性l交大片 | 黄色小视频在线免费观看 | 香蕉91视频| 欧洲成人在线 | 玖玖爱在线观看 | 国产高清成人 | 欧美成人精品欧美一级私黄 | 成人国产精品久久久 |