分享IT技术,分享生活感悟,热爱摄影,热爱航天。
在和其他网站共享用户账号时,需要实现用户账号对接和单点登录的功能。而比较多的第三方账号登录功能多以OAuth的方式实现,而OAuth需要进行服务器之间的通信来认证授权并获取用户信息,会给服务器带来一些额外的开销和不确定性。而SAML则是一种完全不存在服务器直接通信的认证方式。
SAML的认证方式包括客户端Client,服务提供者SP和认证提供者IdP,用户访问SP,SP会返回给用户一个302重定向,将用户重定向到对应的IdP页面,用户输入登录信息后,IdP会根据用户的来源返回给用户一个POST到之前SP的表单,SP确认信息完成登录过程。其流程图如下所示
可以看到SP和IdP之间只通过引导用户重定向来交换数据,并不直接进行通信。为了确保通信的安全,建议SP和IdP都是用HTTPS协议。
SimpleSAMLphp是一个使用PHP实现的SAML类库,同时提供了一个可配置为SP或Idp的Web服务。其Web服务的Nginx配置如下
root /srv/http/simplesamlphp/www/; location / { index index.html index.htm index.php; } location ~ \.php { fastcgi_param PHP_VALUE "post_max_size=16M"; fastcgi_pass 127.0.0.1:9000; fastcgi_split_path_info ^(.+?\.php)(/.+)$; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_index index.php; include fastcgi.conf; }
其中主要两个问题,由于是用了path_info形式的URL风格,PHP的location不能配置成为\.php$,同时需要增加fastcgi_split_path_info的配置。
完成Web服务配置后,修改config/config.php,其中auth.adminpassword需要更改,否则一些功能会不能使用。将enable.saml20-idp设置为true。
'auth.adminpassword' => '123456', 'enable.saml20-idp' => true,
另外SimpleSAMLphp是用了PHP的Session作为会话保持,在使用多台服务器做负载均衡的情况下会存在不能共享的问题,这里可以开启SimpleSAMLphp使用MySQL存储Session的功能,其会在配置的数据库中创建三个表来存储Session信息
'store.sql.dsn' => 'mysql:host=localhost;port=3306;dbname=test', 'store.sql.username' => 'root', 'store.sql.password' => '', 'store.sql.prefix' => 'SimpleSAMLphp',
SimpleSAMLphp的SP配置在config/authsources.php文件中参考其中default-sp进行配置即可,其中需要注意有些Idp要求证书的签名算法必须使用SHA256,则此时使用SHA256生成签名证书,并将signature.algorithm设置为
'signature.algorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
访问SimpleSAMLphp的后台工具/module.php/core/frontpage_federation.php,可以查看到配置的SP,点击Show metadata可以看到此SP对应的metadata信息的XML文本,此XML可以用于某些Idp的配置。
增加Idp时,首先获得对方Idp的metadata信息,其中主要包括SingleSignOnService和SingleLogoutService以及签名证书的一些信息。将这些信息添加到metadata/saml20-idp-remote.php文件中的$metadata数组中即可。
而对于提供了metadata的XML的Idp,则可以使用SimpleSAMLphp提供的工具将XML转换为PHP数组,工具地址为/admin/metadata-converter.php,需要输入auth.adminpassword的密码。
在代码中需要调用SAML认证的逻辑时,可以通过引用SimpleSAMLphp的代码来实现,注意这里必须引用提供Web服务的SimpleSAMLphp代码,因为需要使用到配置文件。
//加载autoload require_once(SAML_ROOT . 'lib/_autoload.php'); //实例化认证对象,并指定使用的SP $as = new SimpleSAML_Auth_Simple($conf['sp']); //指定使用的Idp进行认证,如果没有认证会进行SAML的跳转 $as->requireAuth(array( 'saml:idp' => $conf['idp'], )); //如果已经认证成功则获取用户信息 $user_attributes = $as->getAttributes();
在最开始学习编程的时候,一般的书中都会提到代码的可移植性的问题,其中主要包括硬件平台的可移植性,操作系统的可移植性,运行环境的可移植性等。然而目前很多的代码都是在PC + Windows平台下开发和测试的,开发人员一般也不会考虑太多可移植性的问题。而在很多领域对于平台小型化的要求,不能够使用x86 + Windows的架构,而我们肯定不想为此独立维护两套代码,一般来说充分考虑代码的可移植性就能够解决这一问题。
以下简单说明一下代码在Windows系统和Linux系统下实现可移植的一些问题。
不同的操作系统对于文件名的处理方式不同,而比较大的一个差异在于是否区分文件名的英文大小写,Windows不区分,而Linux和BSD则区分,MacOS需要取决于使用的文件系统。虽然Windows系统下不会区分文件名的大小写,但当文件被复制到其他区分文件名大小写的操作系统时,则会以当前文件命名时的状态被复制。其中对于代码中的影响则主要在include代码文件时,因此在使用include语句时只要保证包含的文件名和文件命名时使用的字母大小写一致就可以做到不同系统的兼容。
/* 目录下的文件如下 Test.h main.c 这里需要和看到的文件名完全一致 */ #include "Test.h" int main() { return 0; }
Linux等操作系统使用的是单根节点的文件树,目录之间的分割为/符号,而Windows则使用了磁盘分区的概念,同时目录之间的分割符号为\符号。一般可以通过使用相对路径来避免处理根的问题,然后通过预编译条件定义使用的目录分割符号。
#include <string> #include <iostream> #ifdef _WIN32 #define DIRECTORY_SEPARATOR '\\' #else #define DIRECTORY_SEPARATOR '/' #endif int main() { std::string path = "usr"; path += DIRECTORY_SEPARATOR; path += "bin"; std::cout << path << std::endl; return 0; }
以上代码在不同的操作系统下会有不同的输出
Windows下输出为 usr\bin Linux下输出为 usr/bin
这里也给出了一种判断当前操作系统是否为Windows的方法,即利用预定义变量_WIN32
Windows的动态库导出和使用时相对Linux等操作系统要复杂一些,导出动态库时要在函数前声明_declspec(dllexport),导入声明时需要在函数前声明_declspec(dllimport)。而对于这一点,通常也可以使用预编译语句进行处理——定义两个宏,在Linux系统下其值为空,Windows系统下为对应的两个声明语句,即
#ifdef _WIN32 #define DLLExport _declspec(dllexport) #define DLLImport _declspec(dllimport) #else #define DLLExport #define DLLImport #endif
然后在函数声明时,使用定义的两个宏DLLExport和DLLImport,以达到兼容不同系统的作用。
在Windows中调用Fortran代码需要在声明时增加__stdcall声明,Linux中则不用,为此可以使用上一节中的方法进行处理,另外Linux下引入Fortran代码时,函数名最后要增加一个下划线,可以先声明带有下划线的函数名,然后在通过定义一个宏来实现和原来一致的函数名
例如以下的Fortran代码
real function F(X) implicit none real :: X F = X * X end function
导入到C语言中
#include <stdio.h> #ifdef _WIN32 float __stdcall f(float*); #else float f_(float*); #define f f_ #endif int main() { float x = 12.0; float y = f_(&x); printf("%f\n", y); return 0; }