亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringSceurity實現(xiàn)短信驗證碼登陸

 更新時間:2020年06月28日 09:01:25   作者:雨點(diǎn)的名字  
這篇文章主要介紹了SpringSceurity實現(xiàn)短信驗證碼登陸,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

一、短信登錄驗證機(jī)制原理分析

了解短信驗證碼的登陸機(jī)制之前,我們首先是要了解用戶賬號密碼登陸的機(jī)制是如何的,我們來簡要分析一下Spring Security是如何驗證基于用戶名和密碼登錄方式的,

分析完畢之后,再一起思考如何將短信登錄驗證方式集成到Spring Security中。

1、賬號密碼登陸的流程

一般賬號密碼登陸都有附帶 圖形驗證碼 和 記住我功能 ,那么它的大致流程是這樣的。

1、 用戶在輸入用戶名,賬號、圖片驗證碼后點(diǎn)擊登陸。那么對于springSceurity首先會進(jìn)入短信驗證碼Filter,因為在配置的時候會把它配置在
UsernamePasswordAuthenticationFilter之前,把當(dāng)前的驗證碼的信息跟存在session的圖片驗證碼的驗證碼進(jìn)行校驗。

2、短信驗證碼通過后,進(jìn)入 UsernamePasswordAuthenticationFilter 中,根據(jù)輸入的用戶名和密碼信息,構(gòu)造出一個暫時沒有鑒權(quán)的
 UsernamePasswordAuthenticationToken,并將 UsernamePasswordAuthenticationToken 交給 AuthenticationManager 處理。

3、AuthenticationManager 本身并不做驗證處理,他通過 for-each 遍歷找到符合當(dāng)前登錄方式的一個 AuthenticationProvider,并交給它進(jìn)行驗證處理
,對于用戶名密碼登錄方式,這個 Provider 就是 DaoAuthenticationProvider。

4、在這個 Provider 中進(jìn)行一系列的驗證處理,如果驗證通過,就會重新構(gòu)造一個添加了鑒權(quán)的 UsernamePasswordAuthenticationToken,并將這個
 token 傳回到 UsernamePasswordAuthenticationFilter 中。

5、在該 Filter 的父類 AbstractAuthenticationProcessingFilter 中,會根據(jù)上一步驗證的結(jié)果,跳轉(zhuǎn)到 successHandler 或者是 failureHandler。

流程圖

2、短信驗證碼登陸流程

因為短信登錄的方式并沒有集成到Spring Security中,所以往往還需要我們自己開發(fā)短信登錄邏輯,將其集成到Spring Security中,那么這里我們就模仿賬號

密碼登陸來實現(xiàn)短信驗證碼登陸。

1、用戶名密碼登錄有個 UsernamePasswordAuthenticationFilter,我們搞一個SmsAuthenticationFilter,代碼粘過來改一改。
2、用戶名密碼登錄需要UsernamePasswordAuthenticationToken,我們搞一個SmsAuthenticationToken,代碼粘過來改一改。
3、用戶名密碼登錄需要DaoAuthenticationProvider,我們模仿它也 implenments AuthenticationProvider,叫做 SmsAuthenticationProvider。

這個圖是網(wǎng)上找到,自己不想畫了

我們自己搞了上面三個類以后,想要實現(xiàn)的效果如上圖所示。當(dāng)我們使用短信驗證碼登錄的時候:

1、先經(jīng)過 SmsAuthenticationFilter,構(gòu)造一個沒有鑒權(quán)的 SmsAuthenticationToken,然后交給 AuthenticationManager處理。

2、AuthenticationManager 通過 for-each 挑選出一個合適的 provider 進(jìn)行處理,當(dāng)然我們希望這個 provider 要是 SmsAuthenticationProvider。

3、驗證通過后,重新構(gòu)造一個有鑒權(quán)的SmsAuthenticationToken,并返回給SmsAuthenticationFilter。
filter 根據(jù)上一步的驗證結(jié)果,跳轉(zhuǎn)到成功或者失敗的處理邏輯。

二、代碼實現(xiàn)

1、SmsAuthenticationToken

首先我們編寫 SmsAuthenticationToken,這里直接參考 UsernamePasswordAuthenticationToken 源碼,直接粘過來,改一改。

說明

principal 原本代表用戶名,這里保留,只是代表了手機(jī)號碼。
credentials 原本代碼密碼,短信登錄用不到,直接刪掉。
SmsCodeAuthenticationToken() 兩個構(gòu)造方法一個是構(gòu)造沒有鑒權(quán)的,一個是構(gòu)造有鑒權(quán)的。
剩下的幾個方法去除無用屬性即可。

代碼

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

 private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

 /**
 * 在 UsernamePasswordAuthenticationToken 中該字段代表登錄的用戶名,
 * 在這里就代表登錄的手機(jī)號碼
 */
 private final Object principal;

 /**
 * 構(gòu)建一個沒有鑒權(quán)的 SmsCodeAuthenticationToken
 */
 public SmsCodeAuthenticationToken(Object principal) {
 super(null);
 this.principal = principal;
 setAuthenticated(false);
 }

 /**
 * 構(gòu)建擁有鑒權(quán)的 SmsCodeAuthenticationToken
 */
 public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
 super(authorities);
 this.principal = principal;
 // must use super, as we override
 super.setAuthenticated(true);
 }

 @Override
 public Object getCredentials() {
 return null;
 }

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

 @Override
 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、SmsAuthenticationFilter

然后編寫 SmsAuthenticationFilter,參考 UsernamePasswordAuthenticationFilter 的源碼,直接粘過來,改一改。

說明

原本的靜態(tài)字段有 username 和 password,都干掉,換成我們的手機(jī)號字段。
SmsCodeAuthenticationFilter() 中指定了這個 filter 的攔截 Url,我指定為 post 方式的 /sms/login。
剩下來的方法把無效的刪刪改改就好了。

代碼

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
 /**
 * form表單中手機(jī)號碼的字段name
 */
 public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";

 private String mobileParameter = "mobile";
 /**
 * 是否僅 POST 方式
 */
 private boolean postOnly = true;

 public SmsCodeAuthenticationFilter() {
 //短信驗證碼的地址為/sms/login 請求也是post
 super(new AntPathRequestMatcher("/sms/login", "POST"));
 }

 @Override
 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 String getMobileParameter() {
 return mobileParameter;
 }

 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;
 }
}

3、SmsAuthenticationProvider

這個方法比較重要,這個方法首先能夠在使用短信驗證碼登陸時候被 AuthenticationManager 挑中,其次要在這個類中處理驗證邏輯。

說明

實現(xiàn) AuthenticationProvider 接口,實現(xiàn) authenticate() 和 supports() 方法。

代碼

public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

 private UserDetailsService userDetailsService;

 /**
 * 處理session工具類
 */
 private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

 String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_SMS";

 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;

 String mobile = (String) authenticationToken.getPrincipal();

 checkSmsCode(mobile);

 UserDetails userDetails = userDetailsService.loadUserByUsername(mobile);
 // 此時鑒權(quán)成功后,應(yīng)當(dāng)重新 new 一個擁有鑒權(quán)的 authenticationResult 返回
 SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
 authenticationResult.setDetails(authenticationToken.getDetails());

 return authenticationResult;
 }

 private void checkSmsCode(String mobile) {
 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
 // 從session中獲取圖片驗證碼
 SmsCode smsCodeInSession = (SmsCode) sessionStrategy.getAttribute(new ServletWebRequest(request), SESSION_KEY_PREFIX);
 String inputCode = request.getParameter("smsCode");
 if(smsCodeInSession == null) {
  throw new BadCredentialsException("未檢測到申請驗證碼");
 }

 String mobileSsion = smsCodeInSession.getMobile();
 if(!Objects.equals(mobile,mobileSsion)) {
  throw new BadCredentialsException("手機(jī)號碼不正確");
 }

 String codeSsion = smsCodeInSession.getCode();
 if(!Objects.equals(codeSsion,inputCode)) {
  throw new BadCredentialsException("驗證碼錯誤");
 }
 }

 @Override
 public boolean supports(Class<?> authentication) {
 // 判斷 authentication 是不是 SmsCodeAuthenticationToken 的子類或子接口
 return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
 }

 public UserDetailsService getUserDetailsService() {
 return userDetailsService;
 }

 public void setUserDetailsService(UserDetailsService userDetailsService) {
 this.userDetailsService = userDetailsService;
 }
}

4、SmsCodeAuthenticationSecurityConfig

既然自定義了攔截器,可以需要在配置里做改動。

代碼

@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
 @Autowired
 private SmsUserService smsUserService;
 @Autowired
 private AuthenctiationSuccessHandler authenctiationSuccessHandler;
 @Autowired
 private AuthenctiationFailHandler authenctiationFailHandler;

 @Override
 public void configure(HttpSecurity http) {
 SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
 smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
 smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(authenctiationSuccessHandler);
 smsCodeAuthenticationFilter.setAuthenticationFailureHandler(authenctiationFailHandler);

 SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
 //需要將通過用戶名查詢用戶信息的接口換成通過手機(jī)號碼實現(xiàn)
 smsCodeAuthenticationProvider.setUserDetailsService(smsUserService);

 http.authenticationProvider(smsCodeAuthenticationProvider)
  .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
 }
}

5、SmsUserService

因為用戶名,密碼登陸最終是通過用戶名查詢用戶信息,而手機(jī)驗證碼登陸是通過手機(jī)登陸,所以這里需要自己再實現(xiàn)一個SmsUserService

@Service
@Slf4j
public class SmsUserService implements UserDetailsService {

 @Autowired
 private UserMapper userMapper;

 @Autowired
 private RolesUserMapper rolesUserMapper;

 @Autowired
 private RolesMapper rolesMapper;

 /**
 * 手機(jī)號查詢用戶
 */
 @Override
 public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
 log.info("手機(jī)號查詢用戶,手機(jī)號碼 = {}",mobile);
 //TODO 這里我沒有寫通過手機(jī)號去查用戶信息的sql,因為一開始我建user表的時候,沒有建mobile字段,現(xiàn)在我也不想臨時加上去
 //TODO 所以這里暫且寫死用用戶名去查詢用戶信息(理解就好)
 User user = userMapper.findOneByUsername("小小");
 if (user == null) {
  throw new UsernameNotFoundException("未查詢到用戶信息");
 }
 //獲取用戶關(guān)聯(lián)角色信息 如果為空說明用戶并未關(guān)聯(lián)角色
 List<RolesUser> userList = rolesUserMapper.findAllByUid(user.getId());
 if (CollectionUtils.isEmpty(userList)) {
  return user;
 }
 //獲取角色I(xiàn)D集合
 List<Integer> ridList = userList.stream().map(RolesUser::getRid).collect(Collectors.toList());
 List<Roles> rolesList = rolesMapper.findByIdIn(ridList);
 //插入用戶角色信息
 user.setRoles(rolesList);
 return user;
 }
}

6、總結(jié)

到這里思路就很清晰了,我這里在總結(jié)下。

1、首先從獲取驗證的時候,就已經(jīng)把當(dāng)前驗證碼信息存到session,這個信息包含驗證碼和手機(jī)號碼。

2、用戶輸入驗證登陸,這里是直接寫在SmsAuthenticationFilter中先校驗驗證碼、手機(jī)號是否正確,再去查詢用戶信息。我們也可以拆開成用戶名密碼登陸那樣一個
過濾器專門驗證驗證碼和手機(jī)號是否正確,正確在走驗證碼登陸過濾器。

3、在SmsAuthenticationFilter流程中也有關(guān)鍵的一步,就是用戶名密碼登陸是自定義UserService實現(xiàn)UserDetailsService后,通過用戶名查詢用戶名信息而這里是
通過手機(jī)號查詢用戶信息,所以還需要自定義SmsUserService實現(xiàn)UserDetailsService后。

三、測試

1、獲取驗證碼

獲取驗證碼的手機(jī)號是 15612345678 。因為這里沒有接第三方的短信SDK,只是在后臺輸出。

向手機(jī)號為:15612345678的用戶發(fā)送驗證碼:254792

2、登陸

1)驗證碼輸入不正確

發(fā)現(xiàn)登陸失敗,同樣如果手機(jī)號碼輸入不對也是登陸失敗

2)登陸成功

當(dāng)手機(jī)號碼 和 短信驗證碼都正確的情況下 ,登陸就成功了。

參考

1、Spring Security技術(shù)棧開發(fā)企業(yè)級認(rèn)證與授權(quán)(JoJo)

2、SpringSceurity實現(xiàn)短信驗證碼功能的示例代碼

到此這篇關(guān)于SpringSceurity實現(xiàn)短信驗證碼登陸的文章就介紹到這了,更多相關(guān)SpringSceurity短信驗證碼登陸內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • idea中創(chuàng)建maven的Javaweb工程并進(jìn)行配置(圖文教程)

    idea中創(chuàng)建maven的Javaweb工程并進(jìn)行配置(圖文教程)

    這篇文章主要介紹了idea中創(chuàng)建maven的Javaweb工程并進(jìn)行配置,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),文中給大家提到了tomcat的運(yùn)行方法,具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-02-02
  • Java集合之Map接口與實現(xiàn)類詳解

    Java集合之Map接口與實現(xiàn)類詳解

    這篇文章主要為大家詳細(xì)介紹了Java集合中的Map接口與實現(xiàn)類,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Java有一定的幫助,感興趣的可以了解一下
    2022-12-12
  • Java實現(xiàn)從jar包中讀取指定文件的方法

    Java實現(xiàn)從jar包中讀取指定文件的方法

    這篇文章主要介紹了Java實現(xiàn)從jar包中讀取指定文件的方法,涉及java針對jar文件的讀取及查找相關(guān)操作技巧,需要的朋友可以參考下
    2017-08-08
  • Java利用EasyExcel實現(xiàn)導(dǎo)出導(dǎo)入功能的示例代碼

    Java利用EasyExcel實現(xiàn)導(dǎo)出導(dǎo)入功能的示例代碼

    EasyExcel是一個基于Java的、快速、簡潔、解決大文件內(nèi)存溢出的Excel處理工具。本文廢話不多說,直接上手試試,用代碼試試EasyExcel是否真的那么好用
    2022-11-11
  • Springboot中的@Order如何使用

    Springboot中的@Order如何使用

    本文主要介紹了Springboot中的@Order如何使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • 關(guān)于Jackson的JSON工具類封裝 JsonUtils用法

    關(guān)于Jackson的JSON工具類封裝 JsonUtils用法

    這篇文章主要介紹了關(guān)于Jackson的JSON工具類封裝 JsonUtils用法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • 基于Java實現(xiàn)遍歷文件目錄并去除中文文件名

    基于Java實現(xiàn)遍歷文件目錄并去除中文文件名

    這篇文章主要為大家詳細(xì)介紹了如何使用Java實現(xiàn)遍歷文件目錄并去除中文文件名,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下
    2024-03-03
  • log4j2采用AsyncLogger出現(xiàn)的錯誤及解決方案

    log4j2采用AsyncLogger出現(xiàn)的錯誤及解決方案

    這篇文章主要介紹了log4j2采用AsyncLogger出現(xiàn)的錯誤及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Java Structs框架原理案例詳解

    Java Structs框架原理案例詳解

    這篇文章主要介紹了Java Structs框架原理案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Spring 配置文件字段注入到List、Map

    Spring 配置文件字段注入到List、Map

    這篇文章主要介紹了Spring 配置文件字段注入到List、Map,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10

最新評論