豬腳本(原飛豬腳本)以按鍵精靈教學為主,涉及UiBot,Python,Lua等腳本編程語言,教學包括全自動辦公腳本,游戲輔助腳本,引流腳本,網頁腳本,安卓腳本,IOS腳本,注冊腳本,點贊腳本,閱讀腳本以及網賺腳本等各個領域。想制作腳本和學習按鍵精靈的朋友可以添加按鍵精靈學習交流群:554127455 學習路上不再孤單,金豬腳本伴你一同成長.
答案顯然是有的,今天就給大家介紹下飛Q手機短信驗證平臺并分享下06大大基于這個平臺寫的命令庫與范例調用模板
[tr=rgb(154,205,50)]飛Q手機短信驗證碼系統
關于該平臺的介紹具體可以查看:
飛q短信驗證碼官網幫助中心:http://sms.xudan123.com/help.html
飛q短信驗證碼平臺小視頻介紹:http://www.iqiyi.com/w_19rrp70df9.html?list=19rro6qr82
[tr=rgb(154,205,50)]模板范例下載
天縱命令.rar (36.58 K, 下載次數:132)
該范例有用到表格控件,請使用最新版表格控件版本導入,下載地址:
【7月24日】按鍵2014.03版新增表格與瀏覽器控件(附例子與源碼),新版本更穩定!
下載解壓后把天縱BASE.qml與天縱VCode.qml這倆個文件導入到命令庫,然后再導入范例就可以進行調試了
友情免費提供測試賬號:dd8878dd 密碼:15987530(余額只有2元)
飛Q獲取驗證碼的一般流程
[tr=rgb(154,205,50)]范例摸板測試演示
以搜房網賬號手動模擬注冊為例
手機號登錄的時候是不需要密碼登錄的,而是通過短信驗證碼實現免密登錄。具體步驟如下 :
代碼如下:
public interface SmsCodeSend {
boolean sendSmsCode(String mobile, String code);
}
@Slf4j
public class SmsCodeSendImpl implements SmsCodeSend {
@Override
public boolean sendSmsCode(String mobile, String code) {
String sendCode = String.format("你好你的驗證碼%s,請勿泄露他人。", code);
log.info("向手機號" + mobile + "發送的短信為:" + sendCode);
return true;
}
}
注:因為這里是示例所以就沒有真正的使用第三方發送短信平臺。
@Configuration
public class Myconfig {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
@ConditionalOnMissingBean(SmsCodeSend.class)
public SmsCodeSend smsCodeSend(){
return new SmsCodeSendImpl();
}
}
@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;
/**
*生成手機驗證碼并發送
* @param request
* @return
*/
@RequestMapping("/code/mobile")
@ResponseBody
public String smsCode(HttpServletRequest request){
// 1. 生成一個手機驗證碼
String code = RandomStringUtils.randomNumeric(4);
request.getSession().setAttribute(SESSION_KEY, code);
String mobile = request.getParameter("mobile");
smsCodeSend.sendSmsCode(mobile, code);
return "200";
}
}
<!--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葵花寶典手機登錄</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葵花寶典手機登錄</a> <br>
<a th:href="@{/login/page}" href="login.html" >
<span>使用密碼驗證登錄</span>
</a> <br>
<form th:action="@{/mobile/form}" action="index.html" method="post">
<span>手機號碼</span> <input id="mobile" name="mobile" type="text" class="form-control" placeholder="手機號碼"><br>
<span>驗證碼</span> <input type="text" name="smsCode" class="form-control" placeholder="驗證碼"> <a id="sendCode" th:attr="code_url=@{/code/mobile?mobile=}" href="#"> 獲取驗證碼 </a><br>
<!-- 提示信息, 表達式紅線沒關系,忽略它 -->
<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>
// 發送驗證碼
$("#sendCode").click(function () {
var mobile = $('#mobile').val().trim();
if(mobile == '') {
alert("手機號不能為空");
return;
}
var url = $(this).attr("code_url") + mobile;
$.get(url, function(data){
alert(data === "200" ? "發送成功": "發送失敗");
});
});
</script>
</body>
</html>
.and().authorizeRequests()
.antMatchers("/login/page","/code/image","/mobile/page","/code/mobile").permitAll()
短信驗證碼的校驗過濾器,實際上和圖片驗證過濾器原理一致。都都是繼承OncePerRequestFilter實現一個Spring環境下的過濾器。@Component注解不可少其核心校驗規則如下:
@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中的手機驗證碼
HttpSession session = request.getSession();
String sessionCode = (String)request.getSession().getAttribute(SmsController.SESSION_KEY);
// 獲取用戶輸入的驗證碼
String inpuCode = request.getParameter("smsCode");
//手機號
String mobileInRequest = request.getParameter("mobile");
if(StringUtils.isEmpty(mobileInRequest)){
throw new ValidateCodeException("手機號碼不能為空!");
}
if(StringUtils.isEmpty(inpuCode)){
throw new ValidateCodeException("短信驗證碼不能為空!");
}
if(StrUtil.isBlank(sessionCode)){
throw new ValidateCodeException("短信驗證碼不存在!");
}
if(!sessionCode.equalsIgnoreCase(inpuCode)){
throw new ValidateCodeException("輸入的短信驗證碼錯誤!");
}
session.removeAttribute(SmsController.SESSION_KEY);
}
}
創建com.security.learn.filter.SmsCodeAuthenticationFilter,仿照UsernamePassword AuthenticationFilter進行代碼實現,不過將用戶名、密碼換成手機號進行認證,短信驗證碼在此部分已經沒有用了,因為我們在SmsCodeValidateFilter已經驗證過了。
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY ; //請求中攜帶手機號的參數名稱
private boolean postOnly = true; //指定當前過濾器是否只處理POST請求
public SmsCodeAuthenticationFilter() {
//指定當前過濾器處理的請求
super(new AntPathRequestMatcher("//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());
}
//從請求中獲取手機號碼
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);
}
/**
* 從從請求中獲取手機號碼
* @param request
* @return
*/
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(this.mobileParameter);
}
/**
* 將請求中的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;
}
}
創建com.security.learn.filter.SmsCodeAuthenticationToken,仿照UsernamePasswordAuthenticationToken進行代碼實現
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
//存放認證信息,認證之前存放手機號,認證之后存放登錄的用戶
private final Object principal;
/**
* 開始認證時,SmsCodeAuthenticationToken 接收的是手機號碼, 并且 標識未認證
* @param mobile
*/
public SmsCodeAuthenticationToken(String mobile) {
super(null);
this.principal = mobile;
this.setAuthenticated(false);
}
/**
* 當認證通過后,會重新創建一個新的SmsCodeAuthenticationToken,來標識它已經認證通過,
* @param principal 用戶信息
* @param authorities 用戶權限
*/
public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);//表示認證通過
}
/**
* 在父類中是一個抽象方法,所以要實現, 但是它是密碼,而當前不需要,則直接返回null
* @return
*/
public Object getCredentials() {
return null;
}
/**
* 手機號獲取
* @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();
}
}
創建com.security.learn.filter.SmsCodeAuthenticationProvider,提供給底層的ProviderManager代碼實現如下
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
@Autowired
@Qualifier("smsCodeUserDetailsService")
private UserDetailsService userDetailsService;
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
/**
* 處理認證:
* 1. 通過 手機號 去數據庫查詢用戶信息(UserDeatilsService)
* 2. 再重新構建認證信息
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//利用UserDetailsService獲取用戶信息,拿到用戶信息后重新組裝一個已認證的Authentication
SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken)authentication;
UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal()); //根據手機號碼拿到用戶信息
if(user == null){
throw new AuthenticationServiceException("無法獲取用戶信息");
}
SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user,user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
/**
* AuthenticationManager挑選一個AuthenticationProvider
* 來處理傳入進來的Token就是根據supports方法來判斷的
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);
}
}
創建com.security.learn.impl.SmsCodeUserDetailsService類,不要注入PasswordEncoder
@Slf4j
@Component("smsCodeUserDetailsService")
public class SmsCodeUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
log.info("請求的手機號是:" + mobile);
return new User(mobile, "", true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN"));
}
}
因為測試就沒有去數據庫中獲取手機號
最后我們將以上實現進行組裝,并將以上接口實現以配置的方式告知Spring Security。因為配置代碼比較多,所以我們單獨抽取一個關于短信驗證碼的配置類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 {
//創建手機校驗過濾器實例
SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
//接收 AuthenticationManager 認證管理器
smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
//處理成功handler
//smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
//處理失敗handler
//smsCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
smsCodeAuthenticationFilter.setRememberMeServices(http.getSharedObject(RememberMeServices.class));
// 獲取驗證碼提供者
SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(smsCodeUserDetailsService);
//在用戶密碼過濾器前面加入短信驗證碼校驗過濾器
http.addFilterBefore(smsCodeValidateFilter, UsernamePasswordAuthenticationFilter.class);
//在用戶密碼過濾器后面加入短信驗證碼認證授權過濾器
http.authenticationProvider(smsCodeAuthenticationProvider)
.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
http.csrf().disable() //禁用跨站csrf攻擊防御,后面的章節會專門講解
//.addFilterBefore(codeValidateFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(smsCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
.and()
.apply(smsCodeSecurityConfig)
具體實現如圖
smsCodeAuthenticationFilter.setRememberMeServices(http.getSharedObject(RememberMeServices.class));
<span>記住我</span><input type="checkbox" name="remember-me-test" > <br>
重啟項目,訪問 http://localhost:8888/mobile/page輸入手機號與驗證碼, 勾選 記住我 , 點擊登錄
查看數據庫中中 persistent_logins 表的記錄
關閉瀏覽器, 再重新打開瀏覽器訪問http://localhost:8888 , 發現會跳轉回用戶名密碼登錄頁,而正常應該勾選了 記住我 , 這一步應該是可以正常訪問的.
數據庫中 username 為 手機號 1333383XXXX, 當你訪問http://localhost:8888默認RememberMeServices 是調
用 CustomUserDetailsService 通過用戶名查詢, 而 當前在 CustomUserDetailsService 判斷了用戶名為 admin才通
過認證, 而此時傳入的用戶名是 1333383XXXX, 所以查詢不到 1333383XXXX用戶數據
數據庫中的 persistent_logins 表為什么存儲的是手機號?原因是當前在 SmsCodeUserDetailsService中返回的 User 對象中的 username 屬性設置的是手機號 mobile,而應該設置這個手機號所對應的那個用戶名. 比如當前username 的值
@Slf4j
@Component("smsCodeUserDetailsService")
public class SmsCodeUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
log.info("請求的手機號是:" + mobile);
return new User("admin", "", true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN"));
}
}
注:我們這里實際上是寫為固定admin了實際上需要通過數據庫根據手機號獲取用戶信息。
關閉瀏覽再打開訪問http://localhost:8888就無需手動登錄認證了。因為默認采用的 CustomUserDetailsService 查詢可查詢到用戶名為 admin 的信息,即認證通過
如果您覺得本文不錯,歡迎關注,點贊,收藏支持,您的關注是我堅持的動力!
原創不易,轉載請注明出處,感謝支持!如果本文對您有用,歡迎轉發分享!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。