跨越万水千山认识你,跨根域的单点登陆设计

现在我们越来越喜欢使用第三方账号(QQ、微博)来登陆各种网站,比如说只要你的 QQ 在线,点几下鼠标就可以登陆一个第三方网站,最多让你在第一次登陆的时候补充一下手机号或者邮箱地址,下次就可以直接使用 QQ登陆了。但是,我们这篇文章并不是解释这种登陆模式的,这种登陆模式用户体系其实在 QQ 和 微博获取,如果需要单点登陆的网站的用户数据本来就拿到,就不用费这么都周折了,比如说下面这种。

本教程链接 https://blog.whyun.com/posts//the-design-of-sso-between-diffrent-domain/ ,转载请注明出处

想想这种情况,假设你是一个电商网站,叫某宝,后来你的业务发展了,你又做了一个子产品网站,叫某猫,你肯定想用户登陆一次,就能畅游两个网站。也许你会想,某宝和某猫可以做成挂在同一个一级域名下的二级域名,比如说 tao.xxx.com 和 mao.xxx.com,然后登陆的时候 cookie 写入 xxx.com,就能实现 session 共享了。很遗憾的告诉你,某宝和某猫是两个平行的子公司,根本就不会共用一个一级域名。我们姑且将这两个公司的域名记为 tao.com 和 mao.com 。

用户在一个节点登陆完成之后,我们在前端需要将登陆凭证写入 cookie ,这样用户再在这个节点上发起请求的时候,会把 cookie 带入请求头发给这个节点的服务器端,服务器端就能判断用户是否在线了。不过现在用户在节点 A 登陆后,根本就没法将 cookie 写入 B 节点,好像问题陷入了死结。我们再来从头分析一下,问题的症结是 cookie 不能跨一级域名写入,那么我们就让它写入一个节点好了,解决问题的关键就是增加一个中心节点,专门用来记录 cookie,哪个节点需要读取 cookie,就从这个中心节点读取即可了。

sso登陆时序图

关键的一个步骤,就是第 2 步,jsonp 的原理其实就是生成一个异域的 script 标签,但是这里请求 sso.com 的时候,会在请求上带入 sso.com 本身的 cookie!然后我们再在 JavaScript 代码中将这个 cookie 信息赋值给 js 变量,这样 tao.com 就能通过 js 得到 sso.com 的 cookie 了。

为了简化教程,这里只给出 tao.com 前端代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>首页</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1>首页</h1>
    <input type="hidden" id="js-data-hidden" js-domain="<%=domain%>">
    <script src="https://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js"></script>
    <script>
        $(document).ready(function() {
            var $data = $('#js-data-hidden');
            var domain = $data.attr('js-domain');
            var redirect = encodeURIComponent ('http://' + domain);
            var ssoBaseUrl = 'http://sso.com';
            $.getJSON(ssoBaseUrl + '/get-ticket?callback=?').then(function(data) {
                if (data.ticket) {
                    return $.get('/get-user-info?ticket='+data.ticket);
                }
                location.href = ssoBaseUrl + '/login?redirect=' + redirect;
            }).then(function(result) {
                if (result.code != 0) {
                    return alert(result.msg || '逻辑错误');
                }
                location.href = '/user-backend';
            }).fail(function() {
                alert('网络错误');
            });
        });
    </script>
  </body>
</html>

代码 1.1 tao.com 登陆状态获取

这里我们用 ticket 来表示登陆成功之后写入 sso.com 的 cookie 的变量。上面代码的逻辑还是比较清晰的,首先通过 jsonp 获取 ticket :如果获取到了就到了就在 tao.com 的后端,通过 ticket 查询出对应的用户 ID 回来,然后将登陆用户数据写入 tao.com 本身的 session中;如果没有获取到 ticket,就跳转到 sso.com 完成登陆,并生产成 ticket,登陆完成后再跳转会 tao.com。

本教程关联的完整代码,参见这里 https://gitee.com/yunnysunny/sso-demo

Comments

JNI系列教程之四——在NDK中使用第三方库

4.在NDK中使用第三方库

4.1 背景

既然使用NDK,一般两个常见的原因,一个就是java代码运行效率低,还有一个就是之前和c相关的类库已经在其它项目中准备好了,这样使用NDK就可以尽可能的复用代码。

本文源地址:http://blog.whyun.com/posts/jni/use-thrid-part-library-in-ndk/ 转载请注明出处。

4.2 使用第三方库源码

假设你将第三方库做成动态库,并且你在JNI中还引用了这个动态库,不要企盼着把你的JNI库和这个第三方库放到同一个目录(比如说android项目的libs/armeabi目录)下就万事大吉了,很不幸的告诉你,JNI代码在被运行在android上时不能引用非/system/lib下的动态库。安卓操作系统的系统库文件都是放到/system/lib下的,如果你的JNI代码想引用一些第三方库的功能,就得考虑将第三方库做成静态库,继而打入你生成的jni库中。
再假设你就是第三方库的提供方,你需要将你写的代码提供给你的客户,而且你还想在做完库文件后还有一个测试程序,那么你可以在Android.mk中先编译出来一个静态库,然后再编译一个测试用的JNI动态库。你可能会有疑问,同一个Android.mk可以生成多个文件吗,答案是肯定的。
还是先看一个官方的例子,在NDK路径下,进入目录sample\two-libs,然后打开jni目录中的Android.mk

LOCAL_PATH:= $(call my-dir)

# first lib, which will be built statically
#
include $(CLEAR_VARS)

LOCAL_MODULE    := libtwolib-first
LOCAL_SRC_FILES := first.c

include $(BUILD_STATIC_LIBRARY)

# second lib, which will depend on and include the first one
#
include $(CLEAR_VARS)

LOCAL_MODULE    := libtwolib-second
LOCAL_SRC_FILES := second.c

LOCAL_STATIC_LIBRARIES := libtwolib-first

include $(BUILD_SHARED_LIBRARY)

代码4.2.1 Android.mk
一般情况下,我们都会写include $(BUILD_SHARED_LIBRARY)来生成JNI库,但是这个项目在编译动态库之前先编译生成了一个静态库,编译静态库的时候同样指定了参数LOCAL_MODULELOCAL_SRC_FILES参数,但是下面编译动态库的时候也指定了这两个参数啊,难道不冲突?当然不冲突,因为我们用了这句话——include $(CLEAR_VARS),调用了它就会把之前所有定义的LOCAL_开头的变量全部清空。配置的前半部分生成了静态库libtwolib-first.a,配置的后半部分注意LOCAL_STATIC_LIBRARIES这个变量,其值正是静态库的LOCAL_MODULE名称。

4.3 使用第三方库文件

我们在4.2中做出来了一个静态库(那个静态库在项目的obj\local\{abi}目录可以得到,abi为具体的cpu类型),本意是给客户提供一个可以调用的库文件,现在假设你就是那个客户,手里拿到了库文件,但是没有源码,该怎么使用来?这就要提到PREBUILD_类型变量,先看例子:

LOCAL_PATH:= $(call my-dir)

# first lib, which will be built statically
#
include $(CLEAR_VARS)

#$(info $(TARGET_ARCH_ABI))

LOCAL_MODULE := libtwolib-first
LOCAL_SRC_FILES := ../obj/local/$(TARGET_ARCH_ABI)/libtwolib-first.a
include $(PREBUILT_STATIC_LIBRARY)

# second lib, which will depend on and include the first one
#
include $(CLEAR_VARS)

LOCAL_MODULE    := libtwolib-second
LOCAL_SRC_FILES := second.c

LOCAL_STATIC_LIBRARIES := libtwolib-first

include $(BUILD_SHARED_LIBRARY)

代码 4.3.1 Android-static.mk
一般LOCAL_SRC_FILES是要写c/c++文件的,现在却直接写了一个动态库文件,然后引入PREBUILT_STATIC_LIBRARY来使用NDK的预编译功能,告诉编译器这个库文件已经编译好了,可以直接在下面的编译的中引用。我们看到最终在编译twolib-second库中,使用变量LOCAL_STATIC_LIBRARIES来将其引入。 最后运行ndk-build APP_BUILD_SCRIPT=Android-static.mk进行编译,因为我们这里没有使用默认的mk文件名,所以使用参数APP_BUILD_SCRIPT来指定使用的mk文件。

4.4 指定预编译库的头文件路径

一些成熟的开源库提供了对于安卓环境的编译支持,你可以使用ndk-build来生成库文件。然后使用4.3的方法引入预编译库,但是对于这种开源库,我们引入头文件都是采用#include<xxx.h>这种方式,这时候就需要指定一个头文件的搜索路径,这就是LOCAL_EXPORT_C_INCLUDES变量的作用。下面是一个引用openssl的例子:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := crypto
LOCAL_SRC_FILES := libcrypto.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE    := libchapter4
#LOCAL_C_INCLUDES := $(SYSROOT)/usr/include
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog

LOCAL_SRC_FILES := chapter4.c

LOCAL_SHARED_LIBRARIES := crypto
include $(BUILD_SHARED_LIBRARY)

TARGET_PLATFORM := android-3

这里在和chapter4.c同级的目录下,须有有个include文件夹,然后在里面放openssl文件夹,当然这个openssl文件夹里就是一堆openssl的头文件。这样我们在代码中就可以这么写了:
#include <openssl/pkcs12.h>
在编译的时候,编译器就会去加载jni目录下的include/openssl/pkcs12.h文件。

其实libcrypto.so在安卓系统的/system/lib就存在了,虽然编译完成之后libcrypto.so会被拷贝到安卓项目的libs目录的armeabi下,但是在APP运行时读取的还是/system/lib下的libcrypto.so。可以把libcrypto.so和openssl文件目录分别拷贝到NDK目录下的platforms\android-3\arch-arm\usr中的libinclude目录,这样不需要写include $(PREBUILT_SHARED_LIBRARY)代码块了。

本文用的代码可以从http://git.oschina.net/yunnysunny/ndk/tree/master/chapter4 获取。
Comments