Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
豬腳本(原飛豬腳本)以按鍵精靈教學(xué)為主,涉及UiBot,Python,Lua等腳本編程語(yǔ)言,教學(xué)包括全自動(dòng)辦公腳本,游戲輔助腳本,引流腳本,網(wǎng)頁(yè)腳本,安卓腳本,IOS腳本,注冊(cè)腳本,點(diǎn)贊腳本,閱讀腳本以及網(wǎng)賺腳本等各個(gè)領(lǐng)域。想制作腳本和學(xué)習(xí)按鍵精靈的朋友可以添加按鍵精靈學(xué)習(xí)交流群:554127455 學(xué)習(xí)路上不再孤單,金豬腳本伴你一同成長(zhǎng).
答案顯然是有的,今天就給大家介紹下飛Q手機(jī)短信驗(yàn)證平臺(tái)并分享下06大大基于這個(gè)平臺(tái)寫(xiě)的命令庫(kù)與范例調(diào)用模板
[tr=rgb(154,205,50)]飛Q手機(jī)短信驗(yàn)證碼系統(tǒng)
關(guān)于該平臺(tái)的介紹具體可以查看:
飛q短信驗(yàn)證碼官網(wǎng)幫助中心:http://sms.xudan123.com/help.html
飛q短信驗(yàn)證碼平臺(tái)小視頻介紹:http://www.iqiyi.com/w_19rrp70df9.html?list=19rro6qr82
[tr=rgb(154,205,50)]模板范例下載
天縱命令.rar (36.58 K, 下載次數(shù):132)
該范例有用到表格控件,請(qǐng)使用最新版表格控件版本導(dǎo)入,下載地址:
【7月24日】按鍵2014.03版新增表格與瀏覽器控件(附例子與源碼),新版本更穩(wěn)定!
下載解壓后把天縱BASE.qml與天縱VCode.qml這倆個(gè)文件導(dǎo)入到命令庫(kù),然后再導(dǎo)入范例就可以進(jìn)行調(diào)試了
友情免費(fèi)提供測(cè)試賬號(hào):dd8878dd 密碼:15987530(余額只有2元)
飛Q獲取驗(yàn)證碼的一般流程
[tr=rgb(154,205,50)]范例摸板測(cè)試演示
以搜房網(wǎng)賬號(hào)手動(dòng)模擬注冊(cè)為例
手機(jī)號(hào)登錄的時(shí)候是不需要密碼登錄的,而是通過(guò)短信驗(yàn)證碼實(shí)現(xiàn)免密登錄。具體步驟如下 :
代碼如下:
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("你好你的驗(yàn)證碼%s,請(qǐng)勿泄露他人。", code);
log.info("向手機(jī)號(hào)" + mobile + "發(fā)送的短信為:" + sendCode);
return true;
}
}
注:因?yàn)檫@里是示例所以就沒(méi)有真正的使用第三方發(fā)送短信平臺(tái)。
@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;
/**
*生成手機(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";
}
}
<!--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á)式紅線沒(méi)關(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>
.and().authorizeRequests()
.antMatchers("/login/page","/code/image","/mobile/page","/code/mobile").permitAll()
短信驗(yàn)證碼的校驗(yàn)過(guò)濾器,實(shí)際上和圖片驗(yàn)證過(guò)濾器原理一致。都都是繼承OncePerRequestFilter實(shí)現(xiàn)一個(gè)Spring環(huán)境下的過(guò)濾器。@Component注解不可少其核心校驗(yàn)規(guī)則如下:
@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);
// 獲取用戶(hù)輸入的驗(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);
}
}
創(chuàng)建com.security.learn.filter.SmsCodeAuthenticationFilter,仿照UsernamePassword AuthenticationFilter進(jìn)行代碼實(shí)現(xiàn),不過(guò)將用戶(hù)名、密碼換成手機(jī)號(hào)進(jìn)行認(rèn)證,短信驗(yàn)證碼在此部分已經(jīng)沒(méi)有用了,因?yàn)槲覀冊(cè)赟msCodeValidateFilter已經(jīng)驗(yàn)證過(guò)了。
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ù)名稱(chēng)
private boolean postOnly = true; //指定當(dāng)前過(guò)濾器是否只處理POST請(qǐng)求
public SmsCodeAuthenticationFilter() {
//指定當(dāng)前過(guò)濾器處理的請(qǐng)求
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());
}
//從請(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;
}
}
創(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)證之后存放登錄的用戶(hù)
private final Object principal;
/**
* 開(kāi)始認(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)證通過(guò)后,會(huì)重新創(chuàng)建一個(gè)新的SmsCodeAuthenticationToken,來(lái)標(biāo)識(shí)它已經(jīng)認(rèn)證通過(guò),
* @param principal 用戶(hù)信息
* @param authorities 用戶(hù)權(quán)限
*/
public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);//表示認(rèn)證通過(guò)
}
/**
* 在父類(lèi)中是一個(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();
}
}
創(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. 通過(guò) 手機(jī)號(hào) 去數(shù)據(jù)庫(kù)查詢(xún)用戶(hù)信息(UserDeatilsService)
* 2. 再重新構(gòu)建認(rèn)證信息
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//利用UserDetailsService獲取用戶(hù)信息,拿到用戶(hù)信息后重新組裝一個(gè)已認(rèn)證的Authentication
SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken)authentication;
UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal()); //根據(jù)手機(jī)號(hào)碼拿到用戶(hù)信息
if(user == null){
throw new AuthenticationServiceException("無(wú)法獲取用戶(hù)信息");
}
SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user,user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
/**
* AuthenticationManager挑選一個(gè)AuthenticationProvider
* 來(lái)處理傳入進(jìn)來(lái)的Token就是根據(jù)supports方法來(lái)判斷的
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);
}
}
創(chuàng)建com.security.learn.impl.SmsCodeUserDetailsService類(lèi),不要注入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è)試就沒(méi)有去數(shù)據(jù)庫(kù)中獲取手機(jī)號(hào)
最后我們將以上實(shí)現(xiàn)進(jìn)行組裝,并將以上接口實(shí)現(xiàn)以配置的方式告知Spring Security。因?yàn)榕渲么a比較多,所以我們單獨(dú)抽取一個(gè)關(guān)于短信驗(yàn)證碼的配置類(lèi)SmsCodeSecurityConfig,繼承自SecurityConfigurerAdapter。將上面定義的組件綁定起來(lái),添加到容器中:
注意添加@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)過(guò)濾器實(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);
//在用戶(hù)密碼過(guò)濾器前面加入短信驗(yàn)證碼校驗(yàn)過(guò)濾器
http.addFilterBefore(smsCodeValidateFilter, UsernamePasswordAuthenticationFilter.class);
//在用戶(hù)密碼過(guò)濾器后面加入短信驗(yàn)證碼認(rèn)證授權(quán)過(guò)濾器
http.authenticationProvider(smsCodeAuthenticationProvider)
.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
http.csrf().disable() //禁用跨站csrf攻擊防御,后面的章節(jié)會(huì)專(zhuān)門(mén)講解
//.addFilterBefore(codeValidateFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(smsCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
.and()
.apply(smsCodeSecurityConfig)
具體實(shí)現(xiàn)如圖
smsCodeAuthenticationFilter.setRememberMeServices(http.getSharedObject(RememberMeServices.class));
<span>記住我</span><input type="checkbox" name="remember-me-test" > <br>
重啟項(xiàng)目,訪問(wèn) http://localhost:8888/mobile/page輸入手機(jī)號(hào)與驗(yàn)證碼, 勾選 記住我 , 點(diǎn)擊登錄
查看數(shù)據(jù)庫(kù)中中 persistent_logins 表的記錄
關(guān)閉瀏覽器, 再重新打開(kāi)瀏覽器訪問(wèn)http://localhost:8888 , 發(fā)現(xiàn)會(huì)跳轉(zhuǎn)回用戶(hù)名密碼登錄頁(yè),而正常應(yīng)該勾選了 記住我 , 這一步應(yīng)該是可以正常訪問(wèn)的.
數(shù)據(jù)庫(kù)中 username 為 手機(jī)號(hào) 1333383XXXX, 當(dāng)你訪問(wèn)http://localhost:8888默認(rèn)RememberMeServices 是調(diào)
用 CustomUserDetailsService 通過(guò)用戶(hù)名查詢(xún), 而 當(dāng)前在 CustomUserDetailsService 判斷了用戶(hù)名為 admin才通
過(guò)認(rèn)證, 而此時(shí)傳入的用戶(hù)名是 1333383XXXX, 所以查詢(xún)不到 1333383XXXX用戶(hù)數(shù)據(jù)
數(shù)據(jù)庫(kù)中的 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è)用戶(hù)名. 比如當(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í)際上是寫(xiě)為固定admin了實(shí)際上需要通過(guò)數(shù)據(jù)庫(kù)根據(jù)手機(jī)號(hào)獲取用戶(hù)信息。
關(guān)閉瀏覽再打開(kāi)訪問(wèn)http://localhost:8888就無(wú)需手動(dòng)登錄認(rèn)證了。因?yàn)槟J(rèn)采用的 CustomUserDetailsService 查詢(xún)可查詢(xún)到用戶(hù)名為 admin 的信息,即認(rèn)證通過(guò)
如果您覺(jué)得本文不錯(cuò),歡迎關(guān)注,點(diǎn)贊,收藏支持,您的關(guān)注是我堅(jiān)持的動(dòng)力!
原創(chuàng)不易,轉(zhuǎn)載請(qǐng)注明出處,感謝支持!如果本文對(duì)您有用,歡迎轉(zhuǎn)發(fā)分享!
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。