激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

香港云服务器
服務器之家 - 編程語言 - JAVA教程 - Spring Security實現短信驗證碼登陸

Spring Security實現短信驗證碼登陸

2020-06-28 12:16雨點的名字 JAVA教程

這篇文章主要介紹了Spring Security實現短信驗證碼登陸,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

 

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

 

了解短信驗證碼的登陸機制之前,我們首先是要了解用戶賬號密碼登陸的機制是如何的,我們來簡要分析一下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。

流程圖

Spring Security實現短信驗證碼登陸

 

2、短信驗證碼登陸流程

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

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

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

Spring Security實現短信驗證碼登陸

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

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

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

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

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

 

二、代碼實現

 

1、SmsAuthenticationToken

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

說明

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

代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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<? 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
剩下來的方法把無效的刪刪改改就好了。

代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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() 方法。

代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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<?> authentication) {
 // 判斷 authentication 是不是 SmsCodeAuthenticationToken 的子類或子接口
 return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
 }
 
 public UserDetailsService getUserDetailsService() {
 return userDetailsService;
 }
 
 public void setUserDetailsService(UserDetailsService userDetailsService) {
 this.userDetailsService = userDetailsService;
 }
}

4、SmsCodeAuthenticationSecurityConfig

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

代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@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、獲取驗證碼

Spring Security實現短信驗證碼登陸

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

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

 

2、登陸

1)驗證碼輸入不正確

Spring Security實現短信驗證碼登陸

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

2)登陸成功

Spring Security實現短信驗證碼登陸

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

參考

1、Spring Security技術棧開發企業級認證與授權(JoJo)

2、Spring Security實現短信驗證碼功能的示例代碼

到此這篇關于Spring Security實現短信驗證碼登陸的文章就介紹到這了,更多相關Spring Security短信驗證碼登陸內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/qdhxhz/p/12977015.html

延伸 · 閱讀

精彩推薦
907
主站蜘蛛池模板: 综合色视频 | 欧美日韩网站在线观看 | 亚洲第一成人在线 | 亚洲成人国产综合 | 在线观看精品视频 | 亚洲欧美一区二区三区在线观看 | 久久区二区 | 国产一国产精品一级毛片 | 羞羞视频免费入口网站 | 一区二区三区四区视频在线观看 | 久久精品在这里 | 未成年人在线观看 | 精品一区二区免费视频视频 | 操操电影 | 久久精品国产亚洲aa级女大片 | 青久草视频| 成人福利网 | 九九热视频免费观看 | 成人爱情偷拍视频在线观看 | 久久精品一区二区三区四区五区 | 欧美精品一区二区久久久 | 91精品国产乱码久 | 成人免费看片a | 亚洲特黄a级毛片在线播放 激情视频免费看 | 国产成年人视频 | 日韩黄色片网站 | 欧美色性| 久久草草亚洲蜜桃臀 | 日韩av片在线免费观看 | 欧美一级片免费在线观看 | sese在线视频 | 日韩精品中文字幕在线播放 | 日日草夜夜操 | 精品一区二区在线播放 | 成人午夜亚洲 | 欧美成人午夜影院 | 永久在线观看电影 | 青草伊人网 | 男女羞羞视频在线免费观看 | 国产三级a三级三级 | 春光影院理论片 |