从手头上的一点点小小的东西开始整理,一步一步往上爬,随着时间的流逝,总有一天肯定能成为一只老蜗牛🐌,哈哈。
- gzip,deflate,brotli
- 啊link
博客仓库,记录一些觉得要记的东西
从手头上的一点点小小的东西开始整理,一步一步往上爬,随着时间的流逝,总有一天肯定能成为一只老蜗牛🐌,哈哈。
通过userAgent判断具体是哪个系统,是否在微信内打开,然后通过调整respones解决;
以上两种方式都可以解决这个问题,基于一些环境原因,选择用Nginx的方式去解决
server {
listen 80;
server_name www.a.com a.com;
charset utf-8;
location /applink {
#微信浏览器
if ($http_user_agent ~* "micromessenger" ){
rewrite ^/.* https://a.com/ulink/action/ redirect;
}
if ($http_user_agent ~* "android"){
rewrite ^/.* https://a.com/apk/appname.apk redirect;
}
if ($http_user_agent ~* "iphone"){
rewrite ^/.* https://apps.apple.com/cn/app/ redirect;
}
rewrite ^/.* http://lizhifm.cn/d redirect;
}
}
在对web安全问题的了解过程中,会发现很多时候对于cookie的管理不到位,会导致很多可以被攻击的点,下面从防御的出发点,了解几个cookie的使用特性,规避安全风险
禁止js通过Document.cookie 访问 cookie,但在发送请求时,cookie会在http header cookie里带上;
防范XSS攻击
SameSite 是 HTTP响应头 Set-Cookie的属性之一。用来声明该Cookie是否仅限与一方或者同一站点上下文;
用户访问了A站点,并登陆了账号,这时,服务写了个用户身份标识的cookie
Set-Cookie: sessionid=38afes7a8;Path=/
接着,用户打开了恶意网站,恶意网站利用静态资源加载的漏洞,调用了A站点的api,这时,浏览器会把A站点的cookie一起发送,用户在不知情的情况下,被执行了某些操作
<html>
<body>
<img src="http://a.com/api/delete/xsdfsd"
</body>
</html>
从小案例内看到,如果浏览器能知道第三方站点访问A站点,不发送Cookie不就可以了;基于这个想法,简单了解一下SameSite的几个值;
Lax
--- Cookies允许与顶级导航仪器发送,并将与第三方网站发起的GET请求一起发送,该值是浏览器默认。Strict
--- Cookie只会在第一方上下文中发送,不会与第三方发起的请求一起发送。None
--- Cookie将在所有上下文中发送,即允许跨域发送从上面三个属性的信息看,如果想规避这个风险,那么应该是这样设置cookie
Set-Cookie: sessionid=38afes7a8; HttpOnly; Path=/;SameSite=Strict
MDN文档
在前后端分离的大背景下,跨域是非常常见的一个问题,简单整理一下对应解决的方案。
同源策略是一个安全策略,限制了一个orgin的文档或者他加载的脚本如何与另外一个源的资源进行交互,可以杜绝恶意文档,减少可能被攻击的媒介。
两个Url的 protocol,port和host都是相同的话,那么这两个Url是同源,反之则不同源。
当文档对不同源的服务发起数据交互,那么这个时候发的就是跨域请求。
注:1. 跨域是浏览器的一个自身的行为,出发点是web安全。
使用额外的http头告诉浏览器,让当前orgin的web应用可以访问不同源服务器上的指定资源。
**QA:**为什么有些请求在浏览器的调试工具network面板有多一个options?
**AN:**因为通常跨域请求可以非为“简单请求”以及“非简单请求”,在发起非简单请求的时候,浏览器会事先发一个预检请求(options)询问源服务器是否能访问对应的资源;
以上简单请求的一个满足条件,只要有一条满足不了,那么就属于非简单请求,在发起跨域请求的时候,浏览器就是发送预检请求。
以下是基于nodejs express 的处理,基于 中间件 cors
普通处理
var express = require('express');
var cors = require('cors');
var app = express();
/*
cors 中间件的默认配置,
defaults = {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204
};
*/
app.use(cors());
只允许a.com这个根域名跨域请求
app.use(cors({
origin:function(origin,callback){
callback(null,/^\w+?\.a\.com$/.test(orign));
}
}))
需要跨域带cookie的情况
app.use(
cors({
origin:function(origin,callback){
callback(null,true)
},
credentials:true
})
);
设置特殊的头部信息(例如:header添加token字段[CSRF的一种解决方案])
app.use(
cors({
allowedHeaders:'X-Requested-With,Cache-Control,Content-Language,Content-Type,deviceType,appID,subAppID,deviceId,clientVersion,sessionKey'
origin:function(origin,callback){
callback(null,true)
},
credentials:true
})
);
当发送复杂请求的时候,不想每次都发options,例如:轮询的一个场景
app.use(
cors({
allowedHeaders:'X-Requested-With,Cache-Control,Content-Language,Content-Type,deviceType,appID,subAppID,deviceId,clientVersion,sessionKey'
origin:function(origin,callback){
callback(null,true)
},
credentials:true,
maxAge:600 //单位:秒
})
);
Nginx需要处理的是对options做处理,以及对其他请求添加对应的头部信息,然后转发给服务器
查看 nginx.conf 的 include xxxx/*.conf,到xxxx目录下面添加 abc.conf,内容如下
server {
listen abc.com
location/api {
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE';
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'appId,Token,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,X_Requested_With,If-Modified-Since,Cache-Control,Content-Type,appId,clientVersion,deviceId,deviceType,subAppId';
add_header 'Access-Control-Max-Age' 600;
if ($request_method = 'OPTIONS'){
return 204;
}
proxy_pass http:127.0.0.1:8080
}
}
在需要带cookie的情况,除了设置allow-credentials为true之外,allow-origin也不能设置为 * ;对应部分配置如下
location/api {
...
add_header 'Access-Control-Allow-Origin' $http_origin
add_header 'Access-Control-Allow-Credentials' 'true';
}
利用网页可以访问跨域静态资源的特性,以script callbackfn的形式来实现跨域数据交互。
如下:
//客户端
function handleCallback(result) {
console.log(result.message);
}
var jsonp = document.createElement('script');
var ele = document.getElementById('demo');
jsonp.type = 'text/javascript';
jsonp.src = 'http://localhost:8080?callback=handleCallback';
ele.appendChild(jsonp);
ele.removeChild(jsonp);
//node
router.get('/',(req,res)=>{
let {callback} = req.query;
let data = {
test:1111
};
if(callback){
res.type('text/javascipt');
res.send(`${callback}(${JSON.stringify(data)})`);
}
res.send(`${callback}(${JSON.string(data)})`)
});
window.postMessage()方法可以安全地实现跨域通信。通过获取对应窗口的引用,在窗口上调用targetWindow.postmessage的方法发送一个messageEvent消息,接收方通过监听message事件来捕获message
场景:一个运营后台需要预览编辑的问卷在移动端web页面显示的情况,在后台跟移动端web不同域名的情况下,用postMessage来解决数据通信。
代码如下:
//后台
...
async handlePreView(){
let data = await this.$form.validateFields(),
{
rules=[],
questions=[]
} = data;
if(rules.length < 1 || questions < 1){
window.message.error('题目和计分规则不能为空');
return;
}
this.$iframe.contentWindow.postMessage(JSON.stringify(data),'*');
}
//移动端
window.addEventListener('message',(event)=>{
if(event.data !== 'string') return;
const data = JSON.parse(event.data);
console.log(data);
},false)
在接入微信h5支付遇到了referrer为空的问题之后,也看过很多博主整理的相关文档,然后还是自己简单整理一下,加深一下理解。
Referer 首部包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 首部识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等
在一个不火热的旅游景点(很优美没有过度开发的那种)开了个客栈,有一个很无聊的老板,当有游客入住的时候,都会问
场景一:
场景二:
referrer 是否发送,在chrome默认的行为里面是会带的(除了是新标签直接访问页面),这样会带来一个问题
<img src="data:" />
配置 referrer-policty
属性值 | 描述 |
---|---|
no-referrer | 不发送referrer |
no-referrer-when-downgrage | 安全等级降级的时候不发,例如https->http |
origin | 只发送“源” |
origin-when-cross-origin | 非同源只发送“源”,同源访问发送具体的url |
same-origin | 同源访问发送具体url |
strict-origin | 同等安全等级的页面访问才发url |
strict-origin-when-cross-origin | 同等安全等级且同源的情况下才发送url |
unsafe-url | 无论是同源请求还是非同源请求都发送完整的url |
配置的地方
例如:
<a href=“xxx.com” referrerpolicy=“origin” />
或者
<a href=“xxx.com” rel=“noreferrer” /> 这个时候跳转后,页面http里面的信息的referer就为空
<meta name=“referrer” conent=“origin” /> 这个时候,referer只会显示origin
1.匿名
有时候匿名者不希望被知道自己的身份,会主动从http报文中删除ua,ip,referer来保证私密性跟匿名性,
2.根据referer去做对应的处理
3.防御 CSRF(跨站请求伪造)
注意:看到上面其实有referer,也有referrer,referer 是错误的写法,正确的写法是referrer。http header 里面还是referer属性,没有修正。其他的才修正了写法。
st=>start: 请求到达
cond1=>condition: 是否已缓存?
cond2=>condition: 是否足够新鲜?
cond3=>condition: 再验证过了
op1=>operation: 与服务器再验证
op2=>operation: 从服务器获取
op3=>operation: 存入缓存
op4=>operation: 对已缓存文档的新鲜度进行更新
e=>end: 提供给客户端
st->cond1(yes)->cond2(yes)->e
cond1(no)->op2
op2->op3(left)->e
cond2(no)->op1(right)->cond3(yes)->e
cond3(no)->op2
作用
特点
语法
ETag: W/"<etag_value>"
ETag: "<eag_value>"
客户端再次发起资源请求
特点
语法
Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
客户端再次发起资源请求
if-unmodified-Since:Date
从Date时间起,文件没被修改
用途:断点续传,post,或是非幂等请求
总之一句话,一个是修改了才下载资源,一个是没修改才下载资源
用文字來說,就是斐波那契數列由0和1開始,之後的斐波那契數就是由之前的兩數相加而得出
根據高德納(Donald Ervin Knuth)的《計算機程序設計藝術》(The Art of Computer Programming),1150年印度數學家Gopala和金月在研究箱子包裝物件長宽剛好為1和2的可行方法數目時,首先描述這個數列。在西方,最先研究這個數列的人是比薩的李奧納多(義大利人斐波那契Leonardo Fibonacci),他描述兔子生長的數目時用上了這數列:
兔子对的数量就是斐波那契数列
第一個月初有一對剛誕生的兔子
第二個月之後(第三個月初)牠們可以生育
每月每對可生育的兔子會誕生下一對新兔子
兔子永不死去
假設在n月有兔子總共a對,n+1月總共有b對。在n+2月必定總共有a+b對:因為在n+2月的時候,前一月(n+1月)的b對兔子可以存留至第n+2月(在當月屬於新誕生的兔子尚不能生育)。而新生育出的兔子對數等於所有在n月就已存在的a對
```javascript
function rabbitCount(monthNumber = 1){
let count = [1],
times = 0;
while(times <= monthNumber){
count.unshift(times > 2 ? count[0]+count[1] : count[0]);
times += 1;
}
console.log(count[0]);
}
年度盛典,作为一年一次的一个活动,除了包含复制的活动逻辑之外,就是“视觉盛宴”了,设计大佬毫不吝啬的用上各种“高级”的设计元素,然后前端死在了css的苦海里边
在一些样式上,如果能用css在实现个人觉得是更优的一个选择,相比于通过图片来呈现
参见用于类型判断的方式有:
以上方式都可以做数据类型判断;typeof 跟 instanceof 在一些场景下是不能判断具体的类型的,例如 typeof null 是 object;可以覆盖全部类似的是 Object.prototype.toString.call;
function isType(arg,type){
return `${Object.prototype.toString.call(arg)}`.slice(8,-1).toLocaleLowerCase() === `${type}`.toLocaleLowerCase();
}
const isString = (arg) => isType(arg,'string');
const isArray = (arg) => isType(arg,'array');
const isFunction = (arg) => isType(arg,'function');
link,很普通的一个标签,在刚开始接触html的时候就知道了可以用来引入css文件,但其实它不仅仅只是用来引入css文件,还有很多很妙的应用场景。
一、链接样式表
基于页面的维护、文档大小、解析优化等,通常我们会把css抽离成一个.css文件,然后通过link引用,如下:
<link rel="stylesheet" href="xxxx.css" />
有时候我们会碰到站点需要提供“多主题选择功能”的需求,那么通常比较直接的就是用 class 去控制,通过切换class的方式来实现;例如:
.hightLight{
color:#000
...
}
.dark{
color:#fff
...
}
二、指定favicon
为站点提供一个icon,如下:
<link rel="icon" href="favicon32.png" />
如果想要更好的展示效果,可以根据不同的设备设置不同的icon,通过 rel
和 sizes
属性的配置,如下:
<!-- third-generation iPad with high-resolution Retina display: -->
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="favicon144.png">
<!-- iPhone with high-resolution Retina display: -->
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="favicon114.png">
<!-- first- and second-generation iPad: -->
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="favicon72.png">
<!-- non-Retina iPhone, iPod Touch, and Android 2.1+ devices: -->
<link rel="apple-touch-icon-precomposed" href="favicon57.png">
<!-- basic favicon -->
<link rel="icon" href="favicon32.png">
以上这两个用法可能都有用过,但是下面的几种用法,对于页面的优化有很好的效果,却发现很多人原来都没用过,这~~~没看过MDN文档,估计是。
在我们的站点内发起一个接口请求的时候,不可避免过程:dns解析->建立tcp链接(https还会有ssl的握手),如果这一过程可以前置,那么是可以节省一定的时间的
一、 dns-prefetch
向浏览器提示,可以提前解析该域名,以便可以更快的获取链接内容。
<link rel="dns-prefetch" content="//a.com" />
二、preconnect
向浏览器提供提示,建议浏览器提前打开与链接网站的连接,而不会泄露任何私人信息或下载任何内容,以便在跟随链接时可以更快地获取链接内容。
<link rel="preconnect" content="//a.com" />
三、preload,as,importance
当前页面需要使用的资源。
浏览器有预加载扫描器,在打开chrome devTool之后,看到network面板上Priority,标识了浏览器对于资源下载的优先级。
在浏览器自己的预加载机制之外,开发者也可以人为指定一些资源预加载,通过preload + as(显式申明资源类型)。
<link ret="preload" content="//a.com/statis/test.js" as="script" />
对于设置了 rel="preload" 的link标签,正确的使用as是必要的,浏览器会根据内容的类型来匹配优先级;
例如:
//html
<img src="xxxx.png" />
//html
<img src="xxxx.png" style="display:none;" />
以上两个img的图片都会被下载,但由于display:none的标签在并不会渲染出来的原因,对应这张图片的Priority是low,如下:
importance
指定资源下载的优先级,分别是,auto(无设置,根据浏览器设定),hight(优先级较高),low(优先级较低)
四、prefetch
提示浏览器提前加载链接的资源,因为它**可能**会被用户请求。
//腾讯视频pc web
<link rel="prefetch" href="https://v.qq.com/x/cover/mzc00200xh9313v.html" />
以上是从腾讯视频站点采集的,把当前热门的电视剧页面加了prefetch,应该是根据站点的热门点击动态调整。
五、crossorigin
指定在加载相关资源时是否必须使用 CORS,启用了CORS的图片可以在 Canvas 中使用
(曾经有遇到过类似问题,那时候的处理方式是后端用同域下的接口获取文件返回文件流)
“anonymous”
会发起一个跨域请求(即包含 Origin: HTTP 头). 但不会发送任何认证信息 (即不发送 cookie, X.509 证书和 HTTP 基本认证信息). 如果服务器没有给出源站凭证 (不设置 Access-Control-Allow-Origin: HTTP 头), 资源就会被污染并限制使用.
"use-credentials"
会发起一个带有认证信息 (发送 cookie, X.509 证书和 HTTP 基本认证信息) 的跨域请求 (即包含 Origin: HTTP 头). 如果服务器没有给出源站凭证 (不设置 Access-Control-Allow-Origin: HTTP 头), 资源就会被污染并限制使用.
当不设置此属性时, 资源将会不使用 CORS 加载
//facebook
<link href="https://static.xx.fbcdn.net/rsrc.php/v3ipIp4/yX/l/zh_CN/suwOLbpGGk-.js?_nc_x=Ij3Wp8lg5Kz" rel="preload" as="script" crossorigin="anonymous">
从fb的站点下面,可以看到大量的link标签的应用。
六、alternate + hreflang
alternate 谷歌翻译为”备用“;在实际的使用场景表现也确实是如此;
例如,下图是googlePlay页面的部分dom;通过 alternate + href + hreflang 来实现多语言seo的优化;
官方例子则是通过 alternate + title 来实现网页多样式显示,如下:
//用户可以在浏览器菜单 "查看>页面样式" 来选择网页的样式。通过这一办法,可以用多种样式浏览网页
<link href="default.css" rel="stylesheet" title="Default Style">
<link href="fancy.css" rel="alternate stylesheet" title="Fancy">
<link href="basic.css" rel="alternate stylesheet" title="Basic">
通过webpack的插件的方式实现部分常规优化点自动化。
利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令到网页,使用户加载并执行攻击装恶意制造的网页程序;攻击成功后,可能得到更高的权限、私密网页内容、会话和cookie等各种内容
方式:通过注入恶意内容进行攻击
目的:获取用户隐私内容或者执行恶意操作
一、通过url注入
http://test.com/static/shareLink/index.html?shareUserName=<script>alert(document.cookie)</script>
<html>
<head>
<script>
let $referer = document.querySelectorAll('.referer');
referer.innerHtml = shareUserName;
</script>
</head>
<body>
<p>
<span class='referer'></span>分享给你
</p>
</body>
</html>
二、通过npm包注入
例如:《高达 800 万次下载量的 npm 包被黑客篡改了代码,你的设备或正成为挖矿机》;
event-stream,是一个用于处理 Node.js 流数据的 JavaScript npm 包, event-stream 突然被发现包含一个名为 flatmap-stream 的依赖项,而这个依赖项被植入了窃取比特币的后门,这意味着使用到该模块的开发者们,你们的设备或许早已在自己不知情的情况下变成了挖矿机。
三、通过表单注入
通过表单提交永久注入,在其他用户,在加载到该内容直接执行了恶意脚本
//Form
<input userName value="<script>alert(documemt.cookie)</sctipt>" />
//Html
<p>《安全防御》---作者:<script>alert(documemt.cookie)</sctipt></p>
四、SQL注入
当系统的用户登录校验是通过“SELECT * FROM accounts WHERE username='admin' and pasword='password' ”这类显式的sql进行校验;
//用户名
<input name="username" />
//密码
<input name="psw" type="password" />
当用户在以上 username 输入框输入 "admin' and 1=1 /*"
, 系统的校验SQL 语句是这样的
SELECT * FROM accounts WHERE username='admin' and 1=1 /*' and password = ''
因为 /*后的语句直接被当成注释忽略,用户直接登录成功了
一、特殊符号转义
const escapeHTML = (str)=>{
return str.replace(/[&<>'"]/g,(tag)=>(
{
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
}[tag] || tag
));
}
二、在引用第三方包之前,提前评估风险,尽量使用比较多人使用的npm包,npm包的版本加上lock
三、在设计应用程序时,完全使用完全参数化(Parameterized Query)来设计资料存取功能
set @userName := xxx;
set @passowrd := xxx;
UPDATE myTable SET c1 = @c1, c2 = @c2, c3 = @c3 WHERE c4 = @c4
SELECT * FROM accounts WHERE username=@userName and password = @passowrd
在工作中对于h5支付这个,遇到的问题很多,所以想记录一下,以后说不定就忘记了,没对接过的,看一下也可以避免很多坑。
因需要更灵活的运营场景,在app内需要接入第三方(微信、支付宝)h5支付
微信h5支付开发及常见问题:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4
基本的接入方式不做过多的说明,下面以项目的维度简单说明一下在使用h5支付的过程中碰到的问题,以及处理方式;
星座小游戏是一个卡片类抽金币的小游戏,用户通过选择对应的牌来抽取大于支付金额等值的金币;
一、星座牌小游戏调起微信h5支付
// 打开微信支付
let payParams = JSON.parse(ret.data.payParams)
let openWxUrl = payParams.payUrl + "&redirect_url=" + encodeURIComponent(redirectUrl)
let hideFrame = document.createElement('iframe')
hideFrame.setAttribute('src', openWxUrl);
hideFrame.setAttribute('sandbox','allow-scripts allow-top-navigation allow-same-origin')
document.body.appendChild(hideFrame)
/*
这里更加优雅的可以用iframe onload 去处理,因为用户加载iframe的时间是受网络环境影响,直接2s后移除不可控
setTimeout(function() {
hideFrame.parentNode.removeChild(hideFrame);
}, 2000);
*/
hideFrame.onload = function(){
//其他逻辑
setTimeout(function() {
hideFrame.parentNode.removeChild(hideFrame);
}, 100);
}
上面的代码,有几个处逻辑处理,对应的,通过下面的几个问题解释;
为什么通过iframe的形式加载payUrl?
由星座牌小游戏的交互决定,因为用户在支付完之后,还需要在当前页面开奖,因此,通过iframe的形式处理,还能解决微信h5支付对于payUrl 加载的referer验证问题;
iframe 为什么要设置sandbox?,为什么要设定setTimeout去移除iframe?
对应的异常log如下
message:Uncaught SecurityError: Failed to set the 'href' property on 'Location': The current window does not have permission to navigate the target frame to 'weixin://wap/pay?prepayid%3Dwx241530497040689dd1394a4f1296524900&package=3510274145&noncestr=1563953450&sign=7867a2dd7feb974db9285725cb2822d0'.
从log看到的代码error,其实不是业务本身的代码,而是payUrl加载后,支付页里面的"top.location.href"异常,关键代码如下
//payUrl
var is_postmsg="";
if(is_postmsg=="1")
{
parent.postMessage(JSON.stringify({
action : "send_deeplink",
data : {
deeplink : "weixin://wap/pay?prepayid%3Dwx28141344091494d0c83d3c3cd472e50000&package=3738825662&noncestr=1609136025&sign=02d138bef13beb9222479014a4a4ea85"
}
}), "");
}
else
{
var url="weixin://wap/pay?prepayid%3Dwx28141344091494d0c83d3c3cd472e50000&package=3738825662&noncestr=1609136025&sign=02d138bef13beb9222479014a4a4ea85";
var redirect_url="https://ulink.com/ulink/lucky/index.html";
top.location.href=url;
if(redirect_url)
{
setTimeout(
function(){
top.location.href=redirect_url;
},
5000
);
}
else
{
setTimeout(
function(){
window.history.back();
},
5000);
}
}
从上面的代码块可以看到payUrl调起微信的逻辑
调起微信走的else的逻辑(这里的postmsg 的逻辑暂时未从微信官方文档内找到对于的配置方式),通过 top.loaction.href= url 的形式 加载 scheme调起;
如果有redirect_url,会设置个定时器, 5秒后重定向到redirect_url --- 这也是为什么星座牌小游戏需要添加一个remove iframe的逻辑的原因,
整体梳理下来,问题的原因就是pp在用新的android sdk打包之后,webview的内核版本的提升,对应的内容安全策略(csp)调整(默认设置调整),阻止了payUrl通过top的方式直接访问父页面的api;那么这个问题是通过iframe设置sandbox属性解决:
/* sandbox
allow-forms 允许进行提交表单
allow-scripts 运行执行脚本
allow-same-origin 允许同域请求,比如ajax,storage
allow-top-navigation 允许iframe能够主导window.top进行页面跳转
allow-popups 允许iframe中弹出新窗口,比如,window.open,target=”_blank”
allow-pointer-lock 在iframe中可以锁定鼠标,主要和鼠标锁定有关
*/
hideFrame.setAttribute('sandbox','allow-scripts allow-top-navigation allow-same-origin')
3、redirectUrl是回调地址,微信是怎么跳回指定的地址的?redirectUrl为什么配置ulink的地址?
微信处理redirectUrl的逻辑
if(redirect_url)
{
setTimeout(
function(){
top.location.href=redirect_url;
},
5000);
}
else
{
setTimeout(
function(){
window.history.back();
},
5000);
}
redirectUrl为什么配置ulink的地址?
android在跳到微信支付完成后,点击支付成功页面的“完成”按钮,会回到调起微信支付的app,ios点击完成不会做这部分操作,在有配置redirectUrl的情况下会用safari打开redirectUrl;且redirectUrl也有域名的验证;
配置成ulink的地址,是为了解决ios在支付完成后回到app内的处理方式;
注:ios也有不能直接回到app的情况,而是通过safari打开了ulink的页面,这种情况,经过跟客户端同事的一同排查的结果是因为用户在安装app的时候,没有下载到apple-app-site-association这个文件导致;
内嵌页h5充值页面,提供方便用户充值的页面,并且支持微信支付,支付宝支付,对应app内的充值;
同星座牌小游戏类似,不同的地方是开奖变成了用户余额查询;
避免了星座牌小游戏上喷到的问题之后,暂时(这个在子app的充值弹窗上又遇到了新的问题)没其他的问题出现;
加载payUrl后交互时序图(非官方):
支付宝h5支付,返回的不是payUrl,而是一段formDomString;
<!-- 支付宝h5支付,接口返回的form信息 -->
<form name=\"punchout_form\" method=\"post\" action=\"https://openapi.alipay.com/gateway.do?charset=UTF-8&method=alipay.trade.wap.pay&sign=HfcRgFeT%2FSVj1soSQrBQYCV%2BaoQzrBVupczUmmjM0sQ2FqXlFHMqOti4EexmhSh3Ap%2FRAAG8MXlo%2FTbzVquR59bXe3deuTXc30S5cgsV9l00jaKPOKXdSfJah2r%2FR5onafKys9caXLaaQmVwtrSrWr5hMFz%2FmtfZvZWwch%2FFvJuVS0wlGT128GBG0KSiUue0g2Bs%2BVg%2B3WKhIiQLCBMKB7BuuyFCnvwnpjeLiGafjIYr6CNBn83uzac1QX9OBuzp91EVLGbBSwAFyyxALhporUh4pDe27SqJbwg15kQd6tDp2f7423M6AoQGkEDMdzaBWRTu2UrMenzaqDOpFpilHA%3D%3D&return_url=https%3A%2F%2Fapp.test.com%2Fstatic%2Fh5Conversion%2Findex.html¬ify_url=https%3A%2F%2Fapp.com%2Fcallback%2Fppywforkylin%2Falipay%2Falipay%2F5103092247759423283&version=1.0&app_id=2021001145660238&sign_type=RSA2×tamp=2020-12-28+15%3A28%3A39&alipay_sdk=alipay-sdk-java-3.4.49.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{"body":"10金币","out_trade_no":"1231231233123123123","product_code":"QUICK_WAP_WAY","subject":"10金币","timeout_express":"2m","total_amount":"1"}\">\n<inp
1、以上form代码段直接通过innerHtml插入页面还是不行的,script内的submit并不会被执行
HTML 5 中指定不执行由 innerHTML 插入的 <script> 标签,解决方式如下:
//业务处理
$iframe.innerHtml = payForm;
//HTML 5 中指定不执行由 innerHTML 插入的 <script> 标签。
$iframe.qeuerySelector('form').submit();
提交Form表单后,支付宝部分代码
支付宝scheme唤起逻辑
// 安卓走iframe方式唤起
if (ua.indexOf('android')>-1 && !noIntentTest) {
canIntent = false;
}
/**
* open client
*/
_AP.open = function (params) {
if (!domLoaded && (ua.indexOf('360 aphone')>-1 || canIntent)) {
var arg = arguments;
delayToRun = function () {
_AP.open.apply(null, arg);
delayToRun = null;
};
return;
}
if (locked) {
return;
}
locked = true;
var o;
// START:: 回跳 scheme 处理
var iosScheme, androidScheme, backScheme;
if (backScheme) {
iosScheme = backScheme.ios;
androidScheme = backScheme.android;
try {
window.tracker.log({
code: 11,
msg: 'scheme来源: '+ JSON.stringify(backScheme),
sampleRate: 1,
});
}catch(e){
console.warn('scheme来源获取错误:', e)
}
}
if (typeof params === 'object') {
if (iosScheme) {
params.h5FromAppUrlScheme = iosScheme;
params.sourceSceneType = 'h5Route';
}
o = {
'ios': encodeURIComponent(JSON.stringify(params)),
'android': encodeURIComponent(params.dataString)
};
if (androidScheme) {
o.android = o.android + '&sourceSceneType=h5Route&h5FromAppUrlScheme=' + androidScheme
}
} else {
console.error('params error, pls use JSON format!')
}
// END
// params fault tolerance
if (typeof o.ios !== 'string') {
o.ios = '';
} else if(typeof o.android !== 'string') {
o.android = '';
}
// nonsupport Android intent
if (!canIntent) {
if(isAndroid) {
var alipaysUrl = 'alipays://platformapi/startApp?appId=20000125&orderSuffix=' + o.android +'#Intent;scheme=alipays;package=com.eg.android.AlipayGphone;end';
}
//fix for iOS QQ browser
else if (ua.indexOf('mqqbrowser') > -1) {
var alipaysUrl = 'alipay://alipayclient/?' + o.android;
}
else {
var alipaysUrl = 'alipay://alipayclient/?' + o.ios;
}
//FIXME: 直接判断ios,不判断os版本号
if ( ua.indexOf('qq/') > -1 || ( ua.indexOf('safari') > -1 && ua.indexOf('os 9_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 10_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 11_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 12_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 13_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 14_') > -1 ) ) {
var openSchemeLink = document.getElementById('openSchemeLink');
if (!openSchemeLink) {
openSchemeLink = document.createElement('a');
openSchemeLink.id = 'openSchemeLink';
openSchemeLink.style.display = 'none';
document.body.appendChild(openSchemeLink);
}
//openSchemeLink.href = alipaysUrl;
// oppo浏览器兼容写法
openSchemeLink.onclick = function() {
window.location.href = alipaysUrl;
};
// trigger click
openSchemeLink.dispatchEvent(customClickEvent());
}
else {
var ifr = document.createElement('iframe');
ifr.src = alipaysUrl;
ifr.style.display = 'none';
document.body.appendChild(ifr);
}
$('.J-startapp').attr('href', alipaysUrl);
}
//support Android intent
else {
var packageKey = 'AlipayGphone';
var intentUrl = 'alipays://platformapi/startApp?appId=20000125&orderSuffix='+o.android+'#Intent;scheme=alipays;package=com.eg.android.'+ packageKey +';end';
var openIntentLink = document.getElementById('openIntentLink');
if (!openIntentLink) {
openIntentLink = document.createElement('a');
openIntentLink.id = 'openIntentLink';
openIntentLink.style.display = 'none';
document.body.appendChild(openIntentLink);
}
//openIntentLink.href = intentUrl;
// oppo浏览器兼容写法
openIntentLink.onclick = function() {
window.location.href = intentUrl;
};
// trigger click
openIntentLink.dispatchEvent(customClickEvent());
}
setTimeout(function () {
locked = false;
}, 2500)
支付宝结果轮询,以及returnUrl 逻辑
//
//轮询
var payquery = function () {
if(stopQuery) { return }
var argumentsPayquery = arguments;
Zepto.ajax({
type: 'post',
url: '/h5/h5RoutePayResultQuery.json?h5_route_token=RZ42FugnN5SrDYUBQr3vTTeQg3magDmobilecashierRZ42&need_invoke_app=true', /*/h5/h5RoutePayResultQuery.json?h5_route_token=*/
data:{
'_input_charset': 'utf-8',
'params': $('input[name=params]').val(),
'session': 'RZ42FugnN5SrDYUBQr3vTTeQg3magDmobilecashierRZ42'
},
timeout: 30000,
dataType: 'json',
success: function (data) {
//已唤起支付宝客户端
if(data.data.invokeAlipay && time){
time = 0;
}
//成功
if(data.control_type == 'pay_success') {
if(data.data && data.data.returnUrl && data.data.returnUrl != '') {
window.location.replace(decodeURIComponent(data.data.returnUrl));
}
}
//继续轮询
else if (data.control_type == 'h5_route_need_pay_query') {
if(!data.data.stopQuery) {
setTimeout(function(){
argumentsPayquery.callee();
}, data.data.dismisstime);
}
}
}
});
}
app内需要有一个h5充值的半屏弹窗,只接入微信h5支付
问题:
1、iphone用户,在h5调起微信app支付界面后,取消或者完成支付后没有返回app
原因:客户端的ua问题
1、子app与主app的ua比较
子app:
userAgent%20/3.9.0_build119317%20NetType/WiFi%20Language/zh-Hans-CN),
主app:
Mozilla/5.0%20(iPhone;%20CPU%20iPhone%20OS%2012_4_1%20like%20Mac%20OS%20X)%20AppleWebKit/605.1.15%20(KHTML,%20like%20Gecko)%20Mobile/15E148%20/1.8.8_build132540%20NetType/WiFi%20Language/zh-Hans-CN)
调试:通过whistle调整userAgent为添加mobile标识后,测试正常(推测微信支付界面点击完成或者取消用safari访问redirectUrl的逻辑中,有ua的判断逻辑)
表现:子app调起微信支付后,点击取消,不执行回调url
处理方案:下个版本需要更新ios webview的ua
备注:这边尝试用扫一扫打开星座牌小游戏,结果是一样的,都是不能回到app(这边没找到星座牌小游戏的入口,只能通过扫一扫测试,历史遗留问题)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.