SpringSecurity 手機(jī)號(hào)登錄功能實(shí)現(xiàn)
一、工作流程

1.向手機(jī)發(fā)送驗(yàn)證碼,第三方短信發(fā)送平臺(tái),如阿里云短信。
2.手機(jī)獲取驗(yàn)證碼后,在表單中輸入驗(yàn)證碼。
3.使用自定義過(guò)濾器?SmsCodeValidateFilter?。
4.短信校驗(yàn)通過(guò)后,使用自定義手機(jī)認(rèn)證過(guò)濾器?SmsCodeAuthenticationFilter校驗(yàn)手機(jī)號(hào)碼是否存在?。
5.自定義?SmsCodeAuthenticationToken?提供給?SmsCodeAuthenticationFilter?。
6.自定義?SmsCodeAuthenticationProvider?提供給?AuthenticationManager?。
7.創(chuàng)建針對(duì)手機(jī)號(hào)查詢用戶信息的?SmsCodeUserDetailsService?,提交給?。SmsCodeAuthenticationProvider?。
8.自定義?SmsCodeSecurityConfig?配置類將上面組件連接起來(lái)。
9.將?SmsCodeSecurityConfig?添加到?LearnSrpingSecurity?安全配置的過(guò)濾器鏈上。
二、實(shí)現(xiàn)
2.1、驗(yàn)證碼生成、發(fā)送
/**
* 創(chuàng)建驗(yàn)證碼生成器
*/
@Component
public class SmsCodeGenerator {
public String generate() {
return RandomStringUtils.randomNumeric(4);
}
}
/**
* 驗(yàn)證碼發(fā)送器
*/
@Component
public class SmsCodeSender {
public void send(String mobile, String code) {
System.out.println("向手機(jī)" + mobile + "發(fā)送短信驗(yàn)證碼" + code);
}
}
/**
* 發(fā)送短信接口
*/
@RestController
public class ValidateCodeController {
@Autowired
private SmsCodeGenerator smsCodeGenerator;
@Resource
private SmsCodeSender smsCodeSender;
@Resource
private RedisTemplate redisTemplate;
@GetMapping("/code/sms")
public String createSmsCode(@RequestParam String mobile) throws IOException {
//獲取驗(yàn)證碼
String smsCode = smsCodeGenerator.generate();
//把驗(yàn)證碼設(shè)置到redis
redisTemplate.boundValueOps(SecurityConstants.getValidCodeKey(mobile)).set(smsCode, 300, TimeUnit.SECONDS);
smsCodeSender.send("18360903475", "登錄驗(yàn)證碼為:" + smsCode + ",五分鐘過(guò)期");
return "驗(yàn)證碼是 : " + smsCode;
}
}2.2、手機(jī)號(hào)碼認(rèn)證 Token
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
/**
* 手機(jī)號(hào)碼認(rèn)證 Token
*/
public class PhoneNumAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
/**
* principal的作用有兩個(gè), 在未登錄之前是用戶名,那么在登錄之后是用戶的信息。
*/
private final Object principal;
/**
* 構(gòu)造
* @param principal 手機(jī)號(hào)碼
*/
public PhoneNumAuthenticationToken(Object principal) {
super(null);
this.principal = principal;
// 用于指示AbstractSecurityInterceptor是否應(yīng)向AuthenticationManager提供身份驗(yàn)證令牌
setAuthenticated(false);
}
/**
* 構(gòu)造
* @param principal 用戶信息
* @param authorities 用戶權(quán)限列表
*/
public PhoneNumAuthenticationToken(Object principal,Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
// 用于指示AbstractSecurityInterceptor是否應(yīng)向AuthenticationManager提供身份驗(yàn)證令牌
setAuthenticated(true);
}
/**
* 正常這個(gè)是返回密碼,但手機(jī)登錄沒(méi)有密碼,不用管
*/
@Override
public Object getCredentials() {
return null;
}
/**
* 獲取手機(jī)號(hào)或用戶信息
*/
@Override
public Object getPrincipal() {
return this.principal;
}
}2.3、攔截請(qǐng)求、獲取手機(jī)號(hào)碼
/**
* 手機(jī)號(hào)碼攔截器, 獲取手機(jī)號(hào)碼
*/
public class PhoneNumAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public PhoneNumAuthenticationFilter() {
super(new AntPathRequestMatcher("/phoneLogin", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!Objects.equals(request.getMethod(),"POST")) {
throw new AuthenticationServiceException("身份驗(yàn)證方法需為:'POST'請(qǐng)求");
}
// 獲取手機(jī)號(hào)
String phoneNum = Optional.ofNullable(request.getParameter(Constants.PHONE_NUM_PARAMETER)).map(String::trim).orElse("");
// new 手機(jī)號(hào)碼驗(yàn)證Token
PhoneNumAuthenticationToken authRequest = new PhoneNumAuthenticationToken(phoneNum);
// 身份驗(yàn)證詳細(xì)信息
authRequest.setDetails(super.authenticationDetailsSource.buildDetails(request));
return this.getAuthenticationManager().authenticate(authRequest);
}
}2.4、短信驗(yàn)證碼驗(yàn)證過(guò)濾器
/**
* 短信驗(yàn)證碼驗(yàn)證過(guò)濾器
*/
@Component
public class SmsCodeFilter extends OncePerRequestFilter {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private CustomizeAuthencationFailureHandler customizeAuthencationFailureHandler;
@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {
/**
* uri = /phoneLogin 即手機(jī)號(hào)碼登錄才攔截
*/
if (Objects.equals(Constants.SMS_LOGIN_URI,request.getRequestURI())) {
try{
// 驗(yàn)證手機(jī)驗(yàn)證碼
validateProcess(request);
}catch (AuthenticationException ex) {
customizeAuthencationFailureHandler.onAuthenticationFailure(request, response, ex);
return;
}
}
filterChain.doFilter(request, response);
}
/**
* 驗(yàn)證手機(jī)驗(yàn)證碼
*/
private void validateProcess(HttpServletRequest request){
// 獲取手機(jī)號(hào)
String msgCode = stringRedisTemplate.opsForValue().get(Constants.SMS_CODE_SESSION_KEY);
String code = request.getParameter(Constants.MSG_CODE);
if(Strings.isBlank(code)) {
throw new InternalAuthenticationServiceException("短信驗(yàn)證碼不能為空.");
}
if(null == msgCode) {
throw new InternalAuthenticationServiceException("短信驗(yàn)證碼已失效.");
}
if(!code.equals(msgCode)) {
throw new InternalAuthenticationServiceException("短信驗(yàn)證碼錯(cuò)誤.");
}
}
}2.5、繼承 WebSecurityConfigurerAdapter 配置 HttpSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 數(shù)據(jù)源
*/
@Resource
private DataSource dataSource;
/**
* 用戶信息服務(wù)
*/
@Resource
private UserAuthentication userAuthentication;
/**
* 成功處理
*/
@Resource
private CustomizeAuthencationSuccessHandler customizeAuthencationSuccessHandler;
/**
* 失敗處理
*/
@Resource
private CustomizeAuthencationFailureHandler customizeAuthencationFailureHandler;
/**
* 用戶登出處理
*/
@Resource
private UserLogoutSuccessHandler userLogoutSuccessHandler;
/**
* 多用戶登錄處理
*/
@Resource
private MutilpleSessionHandler mutilpleSessionHandler;
/**
* 密碼編碼器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 手機(jī)號(hào)碼登錄驗(yàn)證處理
*/
@Resource
private DaoPhoneNumAuthenticationProvider daoPhoneNumAuthenticationProvider;
/**
* 信息驗(yàn)證碼過(guò)濾器
*/
@Resource
private SmsCodeFilter smsCodeFilter;
/**
* 把AuthenticationManager公開(kāi)
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 配置自定義驗(yàn)證查詢/加密工具
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userAuthentication).passwordEncoder(passwordEncoder());
}
/**
* 手機(jī)號(hào)碼登錄攔截器
*/
@Bean
public PhoneNumAuthenticationFilter phoneNumAuthenticationFilter() throws Exception {
// 手機(jī)號(hào)碼攔截器, 獲取手機(jī)號(hào)碼
PhoneNumAuthenticationFilter phoneNumAuthenticationFilter = new PhoneNumAuthenticationFilter();
phoneNumAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
//使用手機(jī)號(hào)登錄失敗了如何處理
phoneNumAuthenticationFilter.setAuthenticationFailureHandler(customizeAuthencationFailureHandler);
// 使用手機(jī)號(hào)登錄成功了如何處理
phoneNumAuthenticationFilter.setAuthenticationSuccessHandler(customizeAuthencationSuccessHandler);
return phoneNumAuthenticationFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 加入短信驗(yàn)證碼過(guò)濾器
.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)
// 加入手機(jī)號(hào)碼登錄過(guò)濾器
.addFilterAfter(phoneNumAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
// 加入手機(jī)號(hào)碼登錄驗(yàn)證提供者
.authenticationProvider(daoPhoneNumAuthenticationProvider)
// 表單登錄
.formLogin()
// 未登錄跳轉(zhuǎn)登錄頁(yè)面
.loginPage("/login.html")
// 指定登錄路徑
.loginProcessingUrl("/login")
// 用戶登錄成功的處理
.successHandler(customizeAuthencationSuccessHandler)
// 用戶登錄失敗的處理
.failureHandler(customizeAuthencationFailureHandler)
// 因?yàn)橛脩魝魅脒^(guò)來(lái)的token, 需要再次進(jìn)行校驗(yàn)
.userDetailsService(userAuthentication)
.tokenValiditySeconds(3600)
// .alwaysRemember(true)
// 認(rèn)證配置
.and()
.authorizeRequests()
//不攔截的Url
.antMatchers("/login.html", "/image/code", "/smsCode", "/css/**", "/js/**", "/phoneLogin").permitAll()
.anyRequest() //所有的請(qǐng)求
.authenticated() //認(rèn)證之后就可以訪問(wèn)
// 多端登錄限制,限制一個(gè)賬號(hào)同時(shí)只能一個(gè)人登錄
.and()
.sessionManagement()
.maximumSessions(1)
.expiredSessionStrategy(mutilpleSessionHandler)
.and()
// 登出配置
.and()
.logout()
.logoutUrl("/logout")
// 登出成功處理
.logoutSuccessHandler(userLogoutSuccessHandler)
.and()
.csrf().disable();
}
}到此這篇關(guān)于SpringSecurity 手機(jī)號(hào)登錄的文章就介紹到這了,更多相關(guān)SpringSecurity 手機(jī)號(hào)登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity整合springBoot、redis實(shí)現(xiàn)登錄互踢功能
- SpringSecurity構(gòu)建基于JWT的登錄認(rèn)證實(shí)現(xiàn)
- springboot+jwt+springSecurity微信小程序授權(quán)登錄問(wèn)題
- 詳解SpringSecurity中的Authentication信息與登錄流程
- SpringBoot整合SpringSecurity和JWT的示例
- SpringSecurity 默認(rèn)表單登錄頁(yè)展示流程源碼
- SpringSecurity動(dòng)態(tài)加載用戶角色權(quán)限實(shí)現(xiàn)登錄及鑒權(quán)功能
- 解析SpringSecurity自定義登錄驗(yàn)證成功與失敗的結(jié)果處理問(wèn)題
相關(guān)文章
背包問(wèn)題-動(dòng)態(tài)規(guī)劃java實(shí)現(xiàn)的分析與代碼
這篇文章主要給大家介紹了關(guān)于背包問(wèn)題動(dòng)態(tài)規(guī)劃java實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Java動(dòng)態(tài)顯示文件上傳進(jìn)度實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Java動(dòng)態(tài)顯示文件上傳進(jìn)度實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
JavaBean valication驗(yàn)證實(shí)現(xiàn)方法示例
這篇文章主要介紹了JavaBean valication驗(yàn)證實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了JavaBean valication驗(yàn)證相關(guān)概念、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-03-03
springboot 打包部署 共享依賴包(分布式開(kāi)發(fā)集中式部署微服務(wù))
這篇文章主要介紹了springboot 打包部署 共享依賴包(分布式開(kāi)發(fā)集中式部署微服務(wù))的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-06-06
Java注冊(cè)郵箱激活驗(yàn)證實(shí)現(xiàn)代碼
這篇文章主要介紹了Java注冊(cè)郵箱激活驗(yàn)證實(shí)現(xiàn)代碼,有需要的朋友可以參考一下2013-12-12
Mybatis實(shí)現(xiàn)動(dòng)態(tài)SQL編寫詳細(xì)代碼示例
這篇文章主要為大家詳細(xì)介紹了Mybatis中動(dòng)態(tài)SQL的編寫使用,動(dòng)態(tài)SQL技術(shù)是一種根據(jù)特定條件動(dòng)態(tài)拼裝SQL語(yǔ)句的功能,它存在的意義是為了解決拼接SQL語(yǔ)句字符串時(shí)的痛點(diǎn)問(wèn)題,感興趣想要詳細(xì)了解可以參考下文2023-05-05

