中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringSceurity怎么實現短信驗證碼登陸

發布時間:2020-06-28 11:26:52 來源:億速云 閱讀:220 作者:清晨 欄目:開發技術

這篇文章主要介紹SpringSceurity怎么實現短信驗證碼登陸,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

一、短信登錄驗證機制原理分析

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

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

1、賬號密碼登陸的流程

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

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

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

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

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

5、在該 Filter 的父類 AbstractAuthenticationProcessingFilter 中,會根據上一步驗證的結果,跳轉到 successHandler 或者是 failureHandler。

流程圖

SpringSceurity怎么實現短信驗證碼登陸

2、短信驗證碼登陸流程

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

密碼登陸來實現短信驗證碼登陸。

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

SpringSceurity怎么實現短信驗證碼登陸

這個圖是網上找到,自己不想畫了

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

1、先經過 SmsAuthenticationFilter,構造一個沒有鑒權的 SmsAuthenticationToken,然后交給 AuthenticationManager處理。

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

3、驗證通過后,重新構造一個有鑒權的SmsAuthenticationToken,并返回給SmsAuthenticationFilter。
filter 根據上一步的驗證結果,跳轉到成功或者失敗的處理邏輯。

二、代碼實現

1、SmsAuthenticationToken

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

說明

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

代碼

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

 private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

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

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

 /**
 * 構建擁有鑒權的 SmsCodeAuthenticationToken
 */
 public SmsCodeAuthenticationToken(Object principal, Collection<&#63; 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 的源碼,直接粘過來,改一改。

說明

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

代碼

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
 /**
 * form表單中手機號碼的字段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 挑中,其次要在這個類中處理驗證邏輯。

說明

實現 AuthenticationProvider 接口,實現 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);
 // 此時鑒權成功后,應當重新 new 一個擁有鑒權的 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("手機號碼不正確");
 }

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

 @Override
 public boolean supports(Class<&#63;> 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();
 //需要將通過用戶名查詢用戶信息的接口換成通過手機號碼實現
 smsCodeAuthenticationProvider.setUserDetailsService(smsUserService);

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

5、SmsUserService

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

@Service
@Slf4j
public class SmsUserService implements UserDetailsService {

 @Autowired
 private UserMapper userMapper;

 @Autowired
 private RolesUserMapper rolesUserMapper;

 @Autowired
 private RolesMapper rolesMapper;

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

6、總結

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

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

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

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

三、測試

1、獲取驗證碼

SpringSceurity怎么實現短信驗證碼登陸

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

向手機號為:15612345678的用戶發送驗證碼:254792

2、登陸

1)驗證碼輸入不正確

SpringSceurity怎么實現短信驗證碼登陸

發現登陸失敗,同樣如果手機號碼輸入不對也是登陸失敗

2)登陸成功

SpringSceurity怎么實現短信驗證碼登陸

當手機號碼 和 短信驗證碼都正確的情況下 ,登陸就成功了。


以上是SpringSceurity怎么實現短信驗證碼登陸的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

樟树市| 明溪县| 子长县| 大余县| 高密市| 镇赉县| 宁海县| 太湖县| 临高县| 盘山县| 天水市| 沿河| 青海省| 太湖县| 靖宇县| 肇东市| 亳州市| 霍邱县| 上虞市| 临沧市| 阳东县| 白山市| 古蔺县| 灯塔市| 寿光市| 理塘县| 和静县| 积石山| 高唐县| 广河县| 新竹县| 深州市| 台南市| 普兰县| 白朗县| 应城市| 延津县| 芮城县| 体育| 甘德县| 永川市|