SpringSecurity-9-實(shí)現(xiàn)通過手機(jī)短信進(jìn)行認(rèn)證功能
手機(jī)短信流程分析
手機(jī)號(hào)登錄的時(shí)候是不需要密碼登錄的嘉冒,而是通過短信驗(yàn)證碼實(shí)現(xiàn)免密登錄。具體步驟如下 :
- 向手機(jī)發(fā)送驗(yàn)證碼衔憨,第三方短信發(fā)送平臺(tái),如阿里云短信
- 手機(jī)獲取驗(yàn)證碼后站超,在表單中輸入驗(yàn)證碼
- 使用自定義過濾器SmsCodeValidateFilter
- 短信校驗(yàn)通過后,使用自定義手機(jī)認(rèn)證過濾器SmsCodeAuthenticationFilter校驗(yàn)手機(jī)號(hào)碼是否存在
- 自定義SmsCodeAuthenticationToken提供給SmsCodeAuthenticationFilter
- 自定義SmsCodeAuthenticationProvider提供給AuthenticationManager
- 創(chuàng)建針對(duì)手機(jī)號(hào)查詢用戶信息的SmsCodeUserDetailsService寸癌,提交給SmsCodeAuthenticationProvider
- 自定義SmsCodeSecurityConfig配置類將上面組件連接起來
- 將SmsCodeSecurityConfig添加到LearnSrpingSecurity安全配置的過濾器鏈上
創(chuàng)建短信發(fā)送接口
- 定義發(fā)生短信的服務(wù)接口com.security.learn.sms.SmsCodeSend
代碼如下:
public interface SmsCodeSend {
? ? boolean sendSmsCode(String mobile, String code);
}
- 實(shí)現(xiàn)短信發(fā)生服務(wù)接口com.security.learn.sms.impl.SmsCodeSendImpl代碼如下
@Slf4j
public class SmsCodeSendImpl implements SmsCodeSend {
? ? @Override
? ? public boolean sendSmsCode(String mobile, String code) {
? ? ? ? String sendCode = String.format("你好你的驗(yàn)證碼%s,請(qǐng)勿泄露他人弱贼。", code);
? ? ? ? log.info("向手機(jī)號(hào)" + mobile + "發(fā)送的短信為:" + sendCode);
? ? ? ? return true;
? ? }
}
注:因?yàn)檫@里是示例所以就沒有真正的使用第三方發(fā)送短信平臺(tái)蒸苇。
- 將smsCodeSend注入到容器實(shí)現(xiàn)如下
@Configuration
public class Myconfig {
? ? @Bean
? ? public PasswordEncoder passwordEncoder(){
? ? ? ? return? new BCryptPasswordEncoder();
? ? }
? ? @Bean
? ? @ConditionalOnMissingBean(SmsCodeSend.class)
? ? public SmsCodeSend smsCodeSend(){
? ? ? ? return? new SmsCodeSendImpl();
? ? }
}
手機(jī)登錄頁與發(fā)送短信驗(yàn)證碼
- 創(chuàng)建SmsController實(shí)現(xiàn)短信發(fā)送驗(yàn)證碼的API,代碼如下:
@Controller
public class SmsController {
? ? public static final String SESSION_KEY = "SESSION_KEY_MOBILE_CODE";
? ? @RequestMapping("/mobile/page")
? ? public String toMobilePage(){
? ? ? ? return "login-mobile";
? ? }
? ? @Autowired
? ? private SmsCodeSend smsCodeSend;
? ? /**
? ? *生成手機(jī)驗(yàn)證碼并發(fā)送
? ? * @param request
? ? * @return? ? */
? ? @RequestMapping("/code/mobile")
? ? @ResponseBody
? ? public String smsCode(HttpServletRequest request){
? ? ? ? // 1. 生成一個(gè)手機(jī)驗(yàn)證碼
? ? ? ? String code = RandomStringUtils.randomNumeric(4);
? ? ? ? request.getSession().setAttribute(SESSION_KEY, code);
? ? ? ? String mobile = request.getParameter("mobile");
? ? ? ? smsCodeSend.sendSmsCode(mobile, code);
? ? ? ? return "200";
? ? }
}
- 在
src\main\resources\templates
文件夾下添加login-mobile.html靜態(tài)頁面,具體實(shí)現(xiàn)如下
<!--suppress ALL-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
? ? <meta charset="utf-8">
? ? <meta http-equiv="X-UA-Compatible" content="IE=edge">
? ? <title>springboot葵花寶典手機(jī)登錄</title>
? ? <!-- Tell the browser to be responsive to screen width -->
? ? <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body >
<div >
? ? <a href="#">springboot葵花寶典手機(jī)登錄</a>? <br>
? ? <a th:href="@{/login/page}" href="login.html" >
? ? ? ? <span>使用密碼驗(yàn)證登錄</span>
? ? </a> <br>
? ? <form th:action="@{/mobile/form}" action="index.html" method="post">
? ? ? ? <span>手機(jī)號(hào)碼</span> <input id="mobile" name="mobile" type="text" class="form-control" placeholder="手機(jī)號(hào)碼"><br>
? ? ? ? <span>驗(yàn)證碼</span> <input type="text" name="smsCode" class="form-control" placeholder="驗(yàn)證碼">? <a id="sendCode" th:attr="code_url=@{/code/mobile?mobile=}" href="#"> 獲取驗(yàn)證碼 </a><br>
? ? ? ? <!-- 提示信息, 表達(dá)式紅線沒關(guān)系哮洽,忽略它 -->
? ? ? ? <div th:if="${param.error}">
? ? ? ? ? ? <span th:text="${session.SPRING_SECURITY_LAST_EXCEPTION?.message}" style="color:#ff0000"></span>
? ? ? ? </div>
? ? ? ? <span>記住我</span><input type="checkbox" name="remember-me-test" >? <br>
? ? ? ? <button type="submit" class="btn btn-primary btn-block">登錄</button>
? ? </form>
</div>
<script th:src="@{/plugins/jquery/jquery.min.js}" src="plugins/jquery/jquery.min.js"></script>
<script>
? ? // 發(fā)送驗(yàn)證碼
? ? $("#sendCode").click(function () {
? ? ? ? var mobile = $('#mobile').val().trim();
? ? ? ? if(mobile == '') {
? ? ? ? ? ? alert("手機(jī)號(hào)不能為空");
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? var url = $(this).attr("code_url") + mobile;
? ? ? ? $.get(url, function(data){
? ? ? ? ? ? alert(data === "200" ? "發(fā)送成功": "發(fā)送失敗");
? ? ? ? });
? ? });
</script>
</body>
</html>
- 在LearnSrpingSecurity的configure(HttpSecurity http)方法中添加手機(jī)免密登錄允許的url
? .and().authorizeRequests()
? ? ? ? ? ? ? ? .antMatchers("/login/page","/code/image","/mobile/page","/code/mobile").permitAll()
短信驗(yàn)證碼校驗(yàn)過濾器 SmsCodeValidateFilter
短信驗(yàn)證碼的校驗(yàn)過濾器填渠,實(shí)際上和圖片驗(yàn)證過濾器原理一致。都都是繼承OncePerRequestFilter實(shí)現(xiàn)一個(gè)Spring環(huán)境下的過濾器鸟辅。@Component注解不可少其核心校驗(yàn)規(guī)則如下:
- 登錄時(shí)候手機(jī)號(hào)碼不可為空
- 登錄時(shí)手機(jī)輸入碼不可為空
- 登錄時(shí)輸入的短信驗(yàn)證碼必須和“謎底”中的驗(yàn)證碼一致
@Component
public class SmsCodeValidateFilter extends OncePerRequestFilter {
? ? @Autowired
? ? MyAuthenticationFailureHandler failureHandler;
? ? @Override
? ? protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
? ? ? ? if (request.getRequestURI().equals("/mobile/form")
? ? ? ? ? ? ? ? && request.getMethod().equalsIgnoreCase("post")) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? validate(request);
? ? ? ? ? ? }catch (AuthenticationException e){
? ? ? ? ? ? ? ? failureHandler.onAuthenticationFailure(
? ? ? ? ? ? ? ? ? ? ? ? request,response,e);
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? filterChain.doFilter(request,response);
? ? }
? ? private void validate(HttpServletRequest request)? {
? ? ? ? //獲取session中的手機(jī)驗(yàn)證碼
? ? ? ? HttpSession session = request.getSession();
? ? ? ? String sessionCode = (String)request.getSession().getAttribute(SmsController.SESSION_KEY);
? ? ? ? // 獲取用戶輸入的驗(yàn)證碼
? ? ? ? String inpuCode = request.getParameter("smsCode");
? ? ? ? //手機(jī)號(hào)
? ? ? ? String mobileInRequest = request.getParameter("mobile");
? ? ? ? if(StringUtils.isEmpty(mobileInRequest)){
? ? ? ? ? ? throw new ValidateCodeException("手機(jī)號(hào)碼不能為空氛什!");
? ? ? ? }
? ? ? ? if(StringUtils.isEmpty(inpuCode)){
? ? ? ? ? ? throw new ValidateCodeException("短信驗(yàn)證碼不能為空!");
? ? ? ? }
? ? ? ? if(StrUtil.isBlank(sessionCode)){
? ? ? ? ? ? throw new ValidateCodeException("短信驗(yàn)證碼不存在匪凉!");
? ? ? ? }
? ? ? ? if(!sessionCode.equalsIgnoreCase(inpuCode)){
? ? ? ? ? ? throw new ValidateCodeException("輸入的短信驗(yàn)證碼錯(cuò)誤枪眉!");
? ? ? ? }
? ? ? ? session.removeAttribute(SmsController.SESSION_KEY);
? ? }
}
實(shí)現(xiàn)手機(jī)認(rèn)證SmsCodeAuthenticationFilter過濾器
創(chuàng)建com.security.learn.filter.SmsCodeAuthenticationFilter,仿照UsernamePasswordAuthenticationFilter進(jìn)行代碼實(shí)現(xiàn),不過將用戶名再层、密碼換成手機(jī)號(hào)進(jìn)行認(rèn)證贸铜,短信驗(yàn)證碼在此部分已經(jīng)沒有用了,因?yàn)槲覀冊(cè)赟msCodeValidateFilter已經(jīng)驗(yàn)證過了聂受。
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
? ? public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
? ? private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY ;? ? //請(qǐng)求中攜帶手機(jī)號(hào)的參數(shù)名稱
? ? private boolean postOnly = true;? ? //指定當(dāng)前過濾器是否只處理POST請(qǐng)求
? ? public SmsCodeAuthenticationFilter() {
? ? ? ? //指定當(dāng)前過濾器處理的請(qǐng)求
? ? ? ? super(new AntPathRequestMatcher("http://mobile/form", "POST"));
? ? }
? ? public Authentication attemptAuthentication(
? ? ? ? ? ? HttpServletRequest request,? ? ? ? ? ? HttpServletResponse response)
? ? ? ? ? ? throws AuthenticationException {
? ? ? ? if (this.postOnly && !request.getMethod().equals("POST")) {
? ? ? ? ? ? throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
? ? ? ? }
? ? ? ? //從請(qǐng)求中獲取手機(jī)號(hào)碼
? ? ? ? String mobile = this.obtainMobile(request);
? ? ? ? if (mobile == null) {
? ? ? ? ? ? mobile = "";
? ? ? ? }
? ? ? ? mobile = mobile.trim();
? ? ? ? SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
? ? ? ? this.setDetails(request, authRequest);
? ? ? ? return this.getAuthenticationManager().authenticate(authRequest);
? ? }
? ? /**
? ? * 從從請(qǐng)求中獲取手機(jī)號(hào)碼
? ? * @param request
? ? * @return? ? */
? ? protected String obtainMobile(HttpServletRequest request) {
? ? ? ? return request.getParameter(this.mobileParameter);
? ? }
? ? /**
? ? * 將請(qǐng)求中的Sessionid和host主句ip放到SmsCodeAuthenticationToken中
? ? * @param request
? ? * @param authRequest? ? */
? ? protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
? ? ? ? authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
? ? }
? ? public void setMobileParameter(String mobileParameter) {
? ? ? ? Assert.hasText(mobileParameter, "Username parameter must not be empty or null");
? ? ? ? this.mobileParameter = mobileParameter;
? ? }
? ? public void setPostOnly(boolean postOnly) {
? ? ? ? this.postOnly = postOnly;
? ? }
? ? public final String getMobileParameter() {
? ? ? ? return this.mobileParameter;
? ? }
}
封裝手機(jī)認(rèn)證Token? SmsCodeAuthenticationToken
創(chuàng)建com.security.learn.filter.SmsCodeAuthenticationToken蒿秦,仿照UsernamePasswordAuthenticationToken進(jìn)行代碼實(shí)現(xiàn)
public class SmsCodeAuthenticationToken? extends AbstractAuthenticationToken {
? ? private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
? ? //存放認(rèn)證信息,認(rèn)證之前存放手機(jī)號(hào)蛋济,認(rèn)證之后存放登錄的用戶
? ? private final Object principal;
? ? /**
? ? * 開始認(rèn)證時(shí),SmsCodeAuthenticationToken 接收的是手機(jī)號(hào)碼, 并且 標(biāo)識(shí)未認(rèn)證
? ? * @param mobile? ? */
? ? public SmsCodeAuthenticationToken(String mobile) {
? ? ? ? super(null);
? ? ? ? this.principal = mobile;
? ? ? ? this.setAuthenticated(false);
? ? }
? ? /**
? ? *? 當(dāng)認(rèn)證通過后,會(huì)重新創(chuàng)建一個(gè)新的SmsCodeAuthenticationToken,來標(biāo)識(shí)它已經(jīng)認(rèn)證通過,
? ? * @param principal 用戶信息
? ? * @param authorities 用戶權(quán)限? ? */
? ? public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
? ? ? ? super(authorities);
? ? ? ? this.principal = principal;
? ? ? ? super.setAuthenticated(true);//表示認(rèn)證通過
? ? }
? ? /**
? ? * 在父類中是一個(gè)抽象方法,所以要實(shí)現(xiàn), 但是它是密碼,而當(dāng)前不需要,則直接返回null
? ? * @return? ? */
? ? public Object getCredentials() {
? ? ? ? return null;
? ? }
? ? /**
? ? * 手機(jī)號(hào)獲取
? ? * @return? ? */
? ? 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);
? ? }
? ? public void eraseCredentials() {
? ? ? ? super.eraseCredentials();
? ? }
}
手機(jī)認(rèn)證提供者 SmsCodeAuthenticationProvider
創(chuàng)建com.security.learn.filter.SmsCodeAuthenticationProvider棍鳖,提供給底層的ProviderManager代碼實(shí)現(xiàn)如下
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
? ? @Autowired
? ? @Qualifier("smsCodeUserDetailsService")
? ? private UserDetailsService userDetailsService;
? ? public UserDetailsService getUserDetailsService() {
? ? ? ? return userDetailsService;
? ? }
? ? public void setUserDetailsService(UserDetailsService userDetailsService) {
? ? ? ? this.userDetailsService = userDetailsService;
? ? }
? ? /**
? ? * 處理認(rèn)證:
? ? * 1. 通過 手機(jī)號(hào) 去數(shù)據(jù)庫查詢用戶信息(UserDeatilsService)
? ? * 2. 再重新構(gòu)建認(rèn)證信息
? ? * @param authentication
? ? * @return
? ? * @throws AuthenticationException? ? */
? ? @Override
? ? public Authentication authenticate(Authentication authentication) throws AuthenticationException {
? ? ? ? //利用UserDetailsService獲取用戶信息,拿到用戶信息后重新組裝一個(gè)已認(rèn)證的Authentication
? ? ? ? SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken)authentication;
? ? ? ? UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());? //根據(jù)手機(jī)號(hào)碼拿到用戶信息
? ? ? ? if(user == null){
? ? ? ? ? ? throw new AuthenticationServiceException("無法獲取用戶信息");
? ? ? ? }
? ? ? ? SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user,user.getAuthorities());
? ? ? ? authenticationResult.setDetails(authenticationToken.getDetails());
? ? ? ? return authenticationResult;
? ? }
? ? /**
? ? * AuthenticationManager挑選一個(gè)AuthenticationProvider
? ? * 來處理傳入進(jìn)來的Token就是根據(jù)supports方法來判斷的
? ? * @param aClass
? ? * @return? ? */
? ? @Override
? ? public boolean supports(Class<?> aClass) {
? ? ? ? return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);
? ? }
}
手機(jī)號(hào)獲取用戶信息 SmsCodeUserDetailsService
創(chuàng)建com.security.learn.impl.SmsCodeUserDetailsService類碗旅,不要注入PasswordEncoder
@Slf4j
@Component("smsCodeUserDetailsService")
public class SmsCodeUserDetailsService implements UserDetailsService {
? ? @Override
? ? public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
? ? ? ? log.info("請(qǐng)求的手機(jī)號(hào)是:" + mobile);
? ? ? ? return new User(mobile, "", true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN"));
? ? }
}
因?yàn)闇y(cè)試就沒有去數(shù)據(jù)庫中獲取手機(jī)號(hào)
自定義管理認(rèn)證配置 SmsCodeSecurityConfig
最后我們將以上實(shí)現(xiàn)進(jìn)行組裝渡处,并將以上接口實(shí)現(xiàn)以配置的方式告知Spring Security。因?yàn)榕渲么a比較多祟辟,所以我們單獨(dú)抽取一個(gè)關(guān)于短信驗(yàn)證碼的配置類SmsCodeSecurityConfig医瘫,繼承自SecurityConfigurerAdapter。將上面定義的組件綁定起來旧困,添加到容器中:
注意添加@Component注解
@Component
public class SmsCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
? ?
? ? @Autowired
? ? @Qualifier("smsCodeUserDetailsService")
? ? private SmsCodeUserDetailsService smsCodeUserDetailsService;
? ? @Resource
? ? private SmsCodeValidateFilter? smsCodeValidateFilter;
? ? @Override
? ? public void configure(HttpSecurity http) throws Exception {
? ? ? ? //創(chuàng)建手機(jī)校驗(yàn)過濾器實(shí)例
? ? ? ? SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
? ? ? ? //接收 AuthenticationManager 認(rèn)證管理器
? ? ? ? smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
? ? ? ? //處理成功handler
? ? ? ? //smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
? ? ? ? //處理失敗handler
? ? ? ? //smsCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
? ? ? ? smsCodeAuthenticationFilter.setRememberMeServices(http.getSharedObject(RememberMeServices.class));
? ? ? ? // 獲取驗(yàn)證碼提供者
? ? ? ? SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
? ? ? ? smsCodeAuthenticationProvider.setUserDetailsService(smsCodeUserDetailsService);
? ? ? ? //在用戶密碼過濾器前面加入短信驗(yàn)證碼校驗(yàn)過濾器
? ? ? ? http.addFilterBefore(smsCodeValidateFilter, UsernamePasswordAuthenticationFilter.class);
? ? ? ? //在用戶密碼過濾器后面加入短信驗(yàn)證碼認(rèn)證授權(quán)過濾器
? ? ? ? http.authenticationProvider(smsCodeAuthenticationProvider)
? ? ? ? ? ? ? ? .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
? ? }
}
綁定到安全配置 LearnSrpingSecurity
. 向 LearnSrpingSecurity中注入 SmsCodeValidateFilter和 SmsCodeSecurityConfig實(shí)例
將 SmsCodeValidateFilter實(shí)例添加到 UsernamePasswordAuthenticationFilter 前面
? http.csrf().disable() //禁用跨站csrf攻擊防御醇份,后面的章節(jié)會(huì)專門講解
? ? ? ? ? ? ? ? //.addFilterBefore(codeValidateFilter, UsernamePasswordAuthenticationFilter.class)
? ? ? ? ? ? ? ? .addFilterBefore(smsCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
- 在 LearnSrpingSecurity#confifigure(HttpSecurity http) 方法體最后調(diào)用 apply 添加 SmsCodeSecurityConfig
? ? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? .apply(smsCodeSecurityConfig)
具體實(shí)現(xiàn)如圖
實(shí)現(xiàn)手機(jī)登錄RememberMe功能
實(shí)現(xiàn)分析
在UsernamePasswordAuthenticationFilter過濾器中有一個(gè)RememberMeServices引用,它的父類AbstractAuthenticationProcessingFilter吼具,提供提供的 setRememberMeServices方法被芳。
而在實(shí)現(xiàn)手機(jī)短信驗(yàn)證碼登錄時(shí),我們自定了一個(gè)? MobileAuthenticationFilter? 也一樣的繼承了AbstractAuthenticationProcessingFilter 它馍悟,我們只要向其 setRememberMeServices 方法手動(dòng)注入一個(gè) RememberMeServices 實(shí)例即可畔濒。
代碼實(shí)現(xiàn)
- 在com.security.learn.config.SmsCodeSecurityConfig中向SmsCodeAuthenticationFilter中注入RememberMeServices實(shí)例
smsCodeAuthenticationFilter.setRememberMeServices(http.getSharedObject(RememberMeServices.class));
- 檢查 記住我 的 input 標(biāo)簽的 name="remember-me-test"
<span>記住我</span><input type="checkbox" name="remember-me-test" >? <br>
- rememberMeParameter設(shè)置from表單“自動(dòng)登錄”勾選框的參數(shù)名稱。如果這里改了锣咒,from表單中checkbox的name屬性要對(duì)應(yīng)的更改侵状。如果不設(shè)置默認(rèn)是remember-me赞弥。
- rememberMeCookieName設(shè)置了保存在瀏覽器端的cookie的名稱,如果不設(shè)置默認(rèn)也是remember-me趣兄。如下圖中查看瀏覽器的cookie绽左。
測(cè)試
重啟項(xiàng)目,訪問 http://localhost:8888/mobile/page輸入手機(jī)號(hào)與驗(yàn)證碼, 勾選 記住我? , 點(diǎn)擊登錄
查看數(shù)據(jù)庫中中 persistent_logins 表的記錄
關(guān)閉瀏覽器, 再重新打開瀏覽器訪問http://localhost:8888 , 發(fā)現(xiàn)會(huì)跳轉(zhuǎn)回用戶名密碼登錄頁,而正常應(yīng)該勾選了 記住我? , 這一步應(yīng)該是可以正常訪問的.
錯(cuò)誤原因
數(shù)據(jù)庫中 username 為 手機(jī)號(hào) 1333383XXXX, 當(dāng)你訪問http://localhost:8888默認(rèn)RememberMeServices 是調(diào)
用 CustomUserDetailsService? 通過用戶名查詢, 而 當(dāng)前在 CustomUserDetailsService 判斷了用戶名為 admin才通
過認(rèn)證, 而此時(shí)傳入的用戶名是 1333383XXXX, 所以查詢不到 1333383XXXX用戶數(shù)據(jù)
錯(cuò)誤解決方式
數(shù)據(jù)庫中的 persistent_logins 表為什么存儲(chǔ)的是手機(jī)號(hào)?原因是當(dāng)前在 SmsCodeUserDetailsService中返回的 User 對(duì)象中的 username 屬性設(shè)置的是手機(jī)號(hào) mobile,而應(yīng)該設(shè)置這個(gè)手機(jī)號(hào)所對(duì)應(yīng)的那個(gè)用戶名. 比如當(dāng)前username 的值
@Slf4j
@Component("smsCodeUserDetailsService")
public class SmsCodeUserDetailsService implements UserDetailsService {
? ? @Override
? ? public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
? ? ? ? log.info("請(qǐng)求的手機(jī)號(hào)是:" + mobile);
? ? ? ? return new User("admin", "", true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN"));
? ? }
}
注:我們這里實(shí)際上是寫為固定admin了實(shí)際上需要通過數(shù)據(jù)庫根據(jù)手機(jī)號(hào)獲取用戶信息艇潭。
關(guān)閉瀏覽再打開訪問http://localhost:8888就無需手動(dòng)登錄認(rèn)證了拼窥。因?yàn)槟J(rèn)采用的 CustomUserDetailsService 查詢可查詢到用戶名為 admin 的信息,即認(rèn)證通過
如果您覺得本文不錯(cuò)蹋凝,歡迎關(guān)注,點(diǎn)贊,收藏支持鲁纠,您的關(guān)注是我堅(jiān)持的動(dòng)力!
原創(chuàng)不易鳍寂,轉(zhuǎn)載請(qǐng)注明出處改含,感謝支持!如果本文對(duì)您有用迄汛,歡迎轉(zhuǎn)發(fā)分享捍壤!