O2OA的SSO与单点认证

SSO与单点认证

与其他系统实现单点登入

1.1 URL传递加密参数方式

这种方式是比较通用简单的实现方式,应急门户将用户登录信息(用户ID)以URL参数方式传递给被集成系统,被集成系统通过接收参数获取登陆用户的信息,实现单点登陆。

1.1.1 集成前提

1、被集成系统为B/S架构。

2、被集成系统可以二次开发接收参数。

3、被集成系统需要使用相同的加密解密算法。

1.1.2 集成方式 URL传加密参数方式优点在于通用性较强。为了系统安全,传递参数的值都通过加密算法(DES2)加密,并通过其它方法来保证安全性。这种方式对被集成系统要求比较低,针对被集成系统现状可以灵活转变。

1.1.3 场景

用户登录到OA系统后,点击对方系统链接,系统会自动跳转到OA系统的中间页面,中间页面将登陆用户的ID(uid:邮箱账号前缀)和系统当前时间(current)加密后设置到表单域然后通过post方式提交对方系统指定url,对方系统在接受到参数后首先通过对应解密算法解密参数值,得到uid和current两个参数的值,然后验证uid合法性,验证URL时间合法性(规则见2.1.5)。如果以上两项中有不符合要求,跳转到系统登录页面。如果两项都通过,初始化系统后进入系统。

1.1.4 加密算法

本次uid和current加密解密都是通过DES2加解密算法来实现。算法中key由双方商定。

算法调用如下: 返回值 方法名 说明 String createEncryptor(String str) 加密字符串 String byteToString(byte[] str) 转换byte数组为字符串 byte[] stringToByte(String str) 转换字符串为byte[]型数据 String createDecryptor(String str) 解密字符串

代码片断: DES2 des = new DES2(key); String str = “tempstr”; String tempstr = des. createEncryptor(str);//加密字符串 String strtmp = des. createDecryptor(tempstr);//解密字符串

1.1.5 URL合法性验证规则

此次为防止盗用链接,设置时间校验。系统接收到current后,用系统当前时间和current比较,如果时间之差在10分钟之内,则视为有效的URL,否则视为无效的URL。时间验证需要同步物理主机的时间。

current验证规则代码片断: String current = request.getParemter(“current”);//获得current参数 DES2 des = new DES2(key); current = des.createDecryptor (current);//解密字符串 long oldTime = Long.parseLong(s); Calendar cal = Calendar.getInstance(); long time = cal.getTimeInMillis(); if(time-oldtime<600000){ url有效 ...... }else{ 跳转到登录页面 }

 

 

 

SSO配置

先正常完成O2OA系统启动,使用xadmin登录系统,在左上角系统菜单中点击控制面板-系统设置

O2OA的SSO与单点认证

 

注意:xadmin 帐号不能用于单点登入

 

O2OA的SSO与单点认证

 

约定**单点认证配置

O2OA提供多种单点认证配置,如约定**的单点认证配置,OAuth2客户端与服务端配置支持。本文主要讲解如果使用约定**实现外部系统与O2OA的单点认证。

O2OA的SSO与单点认证

O2OA的SSO与单点认证

关于用户账号同步

通过单点认证的系统中拥有统一的用户登录账号,或者进行某种账号映射是单点认证登录设置的基础。

 

单点认证配置入口

在系统菜单-控制面板-系统设置中定制您的系统。点击系统菜单,找到“系统SSO配置置”,点击后打开设计中心。

 

 

打开系统主菜单

 

打开控件面板

O2OA的SSO与单点认证

约定**的单点认证

在控制面板 - 系统设置 - 基础配置 - 系统SSO配置界面添加一个SSO配置:

 

当然也可以在服务器配置文件:o2server/config/token.json中进行配置,如果config目录中没有该配置文件,可以从o2server/configSample目录中COPY文件token.json到o2server/config目录后再进行配置文件修改

 

"ssos": [

{

"enable": true,

"client": "ERP",

"key": "password"

}

],

参数配置:

 

"enable": "是否启用",

"client": "名称",

"key": "**"

 

数据加密工具类(Java)

Crypto.java

 

import java.io.IOException;

import java.net.URLDecoder;

import java.net.URLEncoder;

import java.security.SecureRandom;

import javax.crypto.Cipher;

import javax.crypto.SecretKey;

import javax.crypto.SecretKeyFactory;

import javax.crypto.spec.DESKeySpec;

import org.apache.commons.codec.binary.Base64;

import org.apache.commons.lang3.StringUtils;

/**

* encrypt and decrypt utils

* @author O2OA

*

*/

public class Crypto {

private static final String utf8 = "UTF-8";

private final static String DES = "DES";

private final static String cipher_init = "DES";

public static String encrypt(String data, String key) throws Exception {

byte[] bt = encrypt(data.getBytes(), key.getBytes());

String str = Base64.encodeBase64URLSafeString(bt);

return URLEncoder.encode( str, utf8 );

}

public static byte[] encrypt(byte[] data, byte[] key) throws Exception {

// 生成一个可信任的随机数源

SecureRandom sr = new SecureRandom();

// 从原始**数据创建DESKeySpec对象

DESKeySpec dks = new DESKeySpec(key);

// 创建一个**工厂,然后用它把DESKeySpec转换成SecretKey对象

SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);

SecretKey securekey = keyFactory.generateSecret(dks);

// Cipher对象实际完成加密操作

Cipher cipher = Cipher.getInstance(cipher_init);

// 用**初始化Cipher对象

cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);

return cipher.doFinal(data);

}

public static String decrypt(String data, String key) throws IOException, Exception {

if (StringUtils.isEmpty(data)) {

return null;

}

String str = URLDecoder.decode(data, utf8);

byte[] buf = Base64.decodeBase64(str);

byte[] bt = decrypt(buf, key.getBytes());

return new String(bt);

}

public static byte[] decrypt(byte[] data, byte[] key) throws Exception {

// 生成一个可信任的随机数源

SecureRandom sr = new SecureRandom();

// 从原始**数据创建DESKeySpec对象

DESKeySpec dks = new DESKeySpec(key);

// 创建一个**工厂,然后用它把DESKeySpec转换成SecretKey对象

SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);

SecretKey securekey = keyFactory.generateSecret(dks);

// Cipher对象实际完成解密操作

Cipher cipher = Cipher.getInstance(cipher_init);

// 用**初始化Cipher对象

cipher.init(Cipher.DECRYPT_MODE, securekey, sr);

return cipher.doFinal(data);

}

}

 

从第三方系统登录O2OA

 

第三方系统使用数据加密登录O2OA

 

/**

* 从系统中获取已经登录第三方系统的用户账号,并且进行账号和信息加密,送到到O2OA进行登录

*/

//sso名称和密码是在O2OA平台中约定的配置项

String sso_client_name = "ERP";

String sso_key = "password";

//sso_url在O2OA平台中准备好的单点登录页面模板

//文件路径:o2server/servers/webserver/x_desktop/sso.html

String sso_url = "http://o2oa_hostip:port/x_desktop/sso.html";

//login_uid为在第三方系统中识别的登录账号名,这里理解为双方系统账号是统一的账号

String login_uid = "user1";

//获取当前时间

long time = new Date().getTime();

//将用户账号和登录时间一起使用sso_key进行信息加密

String xtoken = null;

try {

xtoken = Crypto.encrypt( login_uid + "#" + time, sso_key );

response.sendRedirect( sso_url + "?client=" + sso_client_name + "&xtoken=" + xtoken );

} catch (Exception e) {

e.printStackTrace();

}

信息加密后,将用户的登录账号(O2OA可以识别的账号)以及登录时间一起使用约定的密码进行加密,然后发送到O2OA的sso.html

 

O2OA相关修改(sso.html)

文件路径:o2server/servers/webServer/x_desktop/sso.html 修改登录相关的代码

 

......

COMMON.setContentPath("/x_desktop");

COMMON.AjaxModule.load("mwf", function(){

MWF.getJSON("res/config/config.json", function(config){

getServiceAddress(config, function(address){

var uri = new URI(window.location.toString());

var xtoken = uri.getData("xtoken");

var client = uri.getData("client");

if (xtoken){

var res = new Request.JSON({

url: address+"/jaxrs/sso",

secure: false,

method: "POST",

noCache: true,

withCredentials: true,

onSuccess: function(responseJSON, responseText){

window.location = "/index.html" ;

}.bind(this),

onFailure: function(xhr){

window.location = "/index.html";

}.bind(this),

onError: function(text, error){

window.location = "/index.html";

}.bind(this)

});

res.setHeader("Content-Type", "application/json; charset=utf-8");

var json = {"token": xtoken, "client": client};

res.send(JSON.encode(json));

}else{

window.location = "/index.html";

}

});

});

系统登录成功,可以指定到单独的门户页面,或者打开指定应用界面。登录失败后,也可以指定到单独的错误处理页面

SSO客户端程序

get方式:

java加密方式:

String login_uid = "test";

long time = new Date().getTime();

String sso_key = "12345678";

String xtoken = Crypto.encrypt( login_uid + "#" + time, sso_key );

js脚本打开url,如下:

var client = "unicom";

var xtoken = "5no3eDzdxrU1-cV_7F7e1gZq-RKMrJ-X" ;

var redirect= "http://127.0.0.1/x_desktop/app.html?app=process.TaskCenter" ;

var url = "http://127.0.0.1/x_desktop/sso.html" + "?client=" + client + "&xtoken=" + xtoken + "&redirect=" + redirect ;

window.open(url);

post方式:

O2OA的SSO与单点认证

 

sso.html文件所在路径

O2OA的SSO与单点认证

 

SSO服务端程序

war包部署路径:

O2OA的SSO与单点认证

在application服务器启动时,会读取这个目录下所有war文件,自动地部署到jetty服务器。

O2OA的SSO与单点认证

 

 

O2OA的SSO与单点认证

应用服务器端口20020

 

O2OA的SSO与单点认证