【H5 Mui】 App 统一身份认证登录过程的记录
在 h5 app 开发的过程中,用到到统一认证登录的功能(统一身份认证登接口),来进行登录验证。
在开发 h5 app 的时候,一般会提供 app 网页版的,这时候会发现,网页版和打包的APP几乎相同,都能满足需求。
在项目中有这样一个需求:每次评论检查是否登录,否则跳转统一的登录接口,之后在进行评论。这个需求前提是自己的项目大部分浏览不需要用户登录,且对用户账号的控制并不多,这时候一般采用别的更方便的接口或API来快速搭建需要登录的模块。
- 遇到的问题:在网页版,认证登录等操作都是基于Web的,登录成功后跳转一切正常,但是打包的 apk 却存在问题,无法跳转和跳转后如何获取到登录成功后的 code (表示登录用户的查询码,用来查询是谁,表示这个用户的唯一授权,过期需要重新登录) 。
- CODE 的保存与使用 ,中间可能会遇到跨域传值问题。
带着上面的问题,继续看看我实际遇到的问题与解决方案。
common.js
APP的所有页面都引用这个 js
// 某 统一身份认证的接口
var login_url ="http://xxx.xxxxx.cn/login?client_id=2100&redirect_uri=http://xxx.xxx.xxx.xxx/app/pages/loginto.html&response_type=code&scope=user_info&state=&toonType=100";
// 上面的地址可分为几个部分
// 一、认证接口 :http://xxx.xxxxx.cn/login
// 二、客户端ID:client_id = 2100
// 三、认证成功跳转地址:redirect_uri = http://xxx.xxx.xxx.xxx/app/pages/loginto.html
// 四、跳转后的附加参数: &response_type=code&scope=user_info&state=&toonType=100
// 关于疑问:
// 第三项中,loginto.html 实质上是个中介页面,接受回调地址上的参数,显示“登录中正跳转”的提示
// 第四项中,包含很多关于API的控制,具体看认证接口
// code 的作用 一般是用来查询用户授权的信息(表示当前用户的唯一值)
function loginToken(code){
if (!code) return;
// app 去查询统一身份认证平台 的接口
$.ajaxJSONP('xx.xxx/xxx?code='+code,false,function(response){
if (!response) return;
if (response["state"] == "success") {
window.token = response["token"];
// localStorage 全局变量中保存,h5 的 app 每一个页面都能访问到这个值
localStorage.setItem("token",window.token);
}
});
}
// 获取 Token
function getToken(){
var token="";
if(window.token){
token = window.token;
}else if(localStorage.getItem("token")){
token = localStorage.getItem("token");
}else{
token = commonjs.getOneUrlParams("token");
}
return token;
}
// 判断是否登录
function loginIn(){
var token = getToken();
if(!token) {
return false;
}else {
return true;
}
// 也可以继续验证 token 是否有效
}
// 前往认证中心
function toAuth(){
// 使用 mui 的方式 并设置不使用缓存 (打包 apk 使用缓存会出现问题)
openwindow("login.html?random=" + Math.random(),{cachemode:"noCache"});
}
//跳转窗口
function openwindow(_url, _styles, _extras) {
if(localStorage.getItem("token")) window.token = localStorage.getItem("token");
window.token && (_url = _url + (_url.indexOf("?") == -1 ? "?" : "&") + "token=" + window.token);
mui.openWindow({
url: _url,
id: _url,
styles: _styles ? _styles : {},
extras: _extras ? _extras : {}, //新窗口的额外扩展参数,可用来处理页面间传值
createNew: false,
show: {
autoShow: true
},
waiting: {
autoShow: true,
title: '正在加载...'
}
})
}
有了上面这样大概的认证的方法后,继续看下面 login.html
页面 和 中介 页面 loginto.html
的关系。
- login.html 页面 在 网页版 和 apk 版 是做为登录页面的外壳而设计的,用来接收子页面的传值,子页面放在一个Iframe中。
- loginto.html 页面 无论 网页版 还是 apk 版 都是作为回调地址 和 中介页面设计的,用来登录后接收参数,并将 code 传递给 外层(谁装载了 loginto.html 谁就是外层)
- 上面的操作设计到跨域传值问题,解决方案:
Messenger.js
只要让中介 页面 和装载他的页面 都引用这个 js 使用几个简单的方法就能将值传递过去。
Messenger.js
跨域传值的工具 js 源文件 这部分文件源码是别人写的,拿来复制,省的上传文件丢了,或不愿了解它的内部原理
/**
* __ ___
* / |/ /___ _____ _____ ___ ____ ____ _ ___ _____
* / /|_/ // _ \ / ___// ___// _ \ / __ \ / __ `// _ \ / ___/
* / / / // __/(__ )(__ )/ __// / / // /_/ // __// /
* /_/ /_/ \___//____//____/ \___//_/ /_/ \__, / \___//_/
* /____/
*
* @description MessengerJS, a common cross-document communicate solution.
* @author biqing kwok
* @version 2.0
* @license release under MIT license
*/
window.Messenger = (function(){
// 消息前缀, 建议使用自己的项目名, 避免多项目之间的冲突
// !注意 消息前缀应使用字符串类型
var prefix = "[PROJECT_NAME]",
supportPostMessage = 'postMessage' in window;
// Target 类, 消息对象
function Target(target, name, prefix){
var errMsg = '';
if(arguments.length < 2){
errMsg = 'target error - target and name are both required';
} else if (typeof target != 'object'){
errMsg = 'target error - target itself must be window object';
} else if (typeof name != 'string'){
errMsg = 'target error - target name must be string type';
}
if(errMsg){
throw new Error(errMsg);
}
this.target = target;
this.name = name;
this.prefix = prefix;
}
// 往 target 发送消息, 出于安全考虑, 发送消息会带上前缀
if ( supportPostMessage ){
// IE8+ 以及现代浏览器支持
Target.prototype.send = function(msg){
this.target.postMessage(this.prefix + '|' + this.name + '__Messenger__' + msg, '*');
};
} else {
// 兼容IE 6/7
Target.prototype.send = function(msg){
var targetFunc = window.navigator[this.prefix + this.name];
if ( typeof targetFunc == 'function' ) {
targetFunc(this.prefix + msg, window);
} else {
throw new Error("target callback function is not defined");
}
};
}
// 信使类
// 创建Messenger实例时指定, 必须指定Messenger的名字, (可选)指定项目名, 以避免Mashup类应用中的冲突
// !注意: 父子页面中projectName必须保持一致, 否则无法匹配
function Messenger(messengerName, projectName){
this.targets = {};
this.name = messengerName;
this.listenFunc = [];
this.prefix = projectName || prefix;
this.initListen();
}
// 添加一个消息对象
Messenger.prototype.addTarget = function(target, name){
var targetObj = new Target(target, name, this.prefix);
this.targets[name] = targetObj;
};
// 初始化消息监听
Messenger.prototype.initListen = function(){
var self = this;
var generalCallback = function(msg){
if(typeof msg == 'object' && msg.data){
msg = msg.data;
}
var msgPairs = msg.split('__Messenger__');
var msg = msgPairs[1];
var pairs = msgPairs[0].split('|');
var prefix = pairs[0];
var name = pairs[1];
for(var i = 0; i < self.listenFunc.length; i++){
if (prefix + name === self.prefix + self.name) {
self.listenFunc[i](msg);
}
}
};
if ( supportPostMessage ){
if ( 'addEventListener' in document ) {
window.addEventListener('message', generalCallback, false);
} else if ( 'attachEvent' in document ) {
window.attachEvent('onmessage', generalCallback);
}
} else {
// 兼容IE 6/7
window.navigator[this.prefix + this.name] = generalCallback;
}
};
// 监听消息
Messenger.prototype.listen = function(callback){
var i = 0;
var len = this.listenFunc.length;
var cbIsExist = false;
for (; i < len; i++) {
if (this.listenFunc[i] == callback) {
cbIsExist = true;
break;
}
}
if (!cbIsExist) {
this.listenFunc.push(callback);
}
};
// 注销监听
Messenger.prototype.clear = function(){
this.listenFunc = [];
};
// 广播消息
Messenger.prototype.send = function(msg){
var targets = this.targets,
target;
for(target in targets){
if(targets.hasOwnProperty(target)){
targets[target].send(msg);
}
}
};
return Messenger;
})();
login.html
统一身份认证登录的外壳页面
<!DOCTYPE html>
<html>
<head>
<title>登录</title>
</head>
<body>
<iframe id="login" src="" ></iframe>
<script src="../js/mui.min.js"></script>
<!-- 其他JS 引用 包括 Messenger.js Common.js -->
<script type="text/javascript">
var messenger = new Messenger('parent', 'monitor');
var login = document.getElementById('login');
login.src = login_url; // 转载登录页面
messenger.listen(function (msg) {
// 登录成功后 中介页面会调用 Messenger 的方法传值 ,这里就进行接收。
loginToken(msg); // 根据 code 获取 token
openwindow("home.html?random=" + Math.random(),{cachemode:"noCache"}); // 回到你想回到的地方
});
</sctipt>
</body>
</html>
loginto.html
登录的中介页面 这个是网络页面,apk 用来跳转本地页面准备的
<!DOCTYPE html>
<html>
<head>
<title>登录跳转</title>
</head>
<body>
<!-- 显示其他动画 -->
<!-- 其他JS 引用 包括 Messenger.js -->
<script type="text/javascript">
var messenger = new Messenger('login', 'monitor');
messenger.addTarget(window.parent, "parent");
if(parent!=window) {
var msg = window.location.search;
if(msg){
msg = msg.split("code=")[1].split("&")[0];
// 先外层传值
messenger.targets["parent"].send(msg);
}
}
</sctipt>
</body>
</html>
在进行登录时:
- 首先会跳转到
login.html
页面,这时候 Iframe 装载的是统一身份认证的登录页,它的域不外层域内。
登录成功时:
- Iframe 登录页 自动跳转
loginto.html
,这时的 Iframe 装载的是网络版的 中介页面而且,url 也会带有登录成功的 code 。
中介页面传值:
-
loginto.html
把 url 中的 code ,传递给外层,他的工能就完成了。
外层页面接收值:
- code 传入后保存起来,这里外层就是你登录前的那个域内,跳转后的其它页面获取到 token 就算成功!。
注意 如果 apk 版直接跳转统一认证页面,登录成功后可能就跳转到网页版的域中,后续使用的文件全部都是 网络获取 ,而不是本地文件,这之后出现的问题都是网页版的,如果没有跳转成功,可能是 apk 的配置或者跳转方式有问题,有可能是缓存,也有可能是限制或禁止,会导致 登录后的 token 获取不到。
如有误导请联系,我会进行修正。
邮箱 hbck_gwx@qq.com