单点登录(SSO)是目前比较流行的企业业务整合的解决方案之一,它的机制是在企业网络用户访问企业网站时作一次身份认证,随后就可以对所有被授权的网络资源进行无缝的访问,而不需要多次输入自己的认证信息.Web服务具有松散耦合、语言中立、平台无关性、开放性的特性,通过对集中单点登录模型的分析和比较,提出了基于Web服务和token-id的单点登录管理框架.介绍了该系统的体系机构及关键部分的工作原理.
本项目主要是采用spring boot +mysql 实现。重点描述如何重代码结构上实现 Web服务统一身份认证协议设计与实现功能。
主要功能如下
- token生成与管理
- 系统接入token 实现sso 单点登录
- 统一身份token 认证与校验
- 分布式系统接入sso模式
项目整体结构
一:sso 管理
原理,利用http 头部信息保存token 匹配服务器的session信息,进行校验。当用户首次登录系统,注册sso 信息
(1):注册sso token
public ResultPo register(String sessionId, String token, long uid) {
String session = getSession(uid, token);
if (session != null && session.equals(sessionId)) {
ResultPo result = ResultPo.create(200, "sso token exist");
SSoPo po = redisService.getPo(SSoPo.class, new MongoPrimaryKey("session_id", session));
if (po != null) {
po.setMtime(TimeUtils.Now());
redisService.updatePo(po);
result.appendData("sso", po);
return result;
}
} else if (session != null) {
this.removeSession(session, token);
}
SSoPo sso = new SSoPo();
sso.setUid(uid);
sso.setSessionId(sessionId);
sso.setToken(token);
sso.setMtime(TimeUtils.Now());
SSoPo po = redisService.getPo(SSoPo.class, sso.getPrimaryKeys());
if (po != null) {
po.setMtime(TimeUtils.Now());
redisService.updatePo(po);
ResultPo result = ResultPo.create(200, "sso token exist");
result.appendData("sso", sso);
return result;
}
redisService.savePo(sso);
ResultPo result = ResultPo.create(200, "sso register successful");
result.appendData("sso", sso);
redisService.hsetString(getSessionPool(token), uid + "", sessionId, sso.getClass().getAnnotation(Cache.class).timestamp());
return result;
}
/**
* token 生成器
*
* @return
*/
public String generateTokeCode() {
String value = System.currentTimeMillis() + new Random().nextInt() + "";
System.out.println(value);
long currentTime = System.currentTimeMillis();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy年-MM月dd日-HH时mm分ss秒");
Date date = new Date(currentTime);
System.out.println(formatter.format(date));
//获取数据指纹,指纹是唯一的
try {
MessageDigest md = MessageDigest.getInstance("md5");
byte[] b = md.digest(value.getBytes());//产生数据的指纹
//Base64编码
BASE64Encoder be = new BASE64Encoder();
be.encode(b);
System.out.println(be.encode(b));
return be.encode(b);//制定一个编码
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
把信息保存到中心服务中,把用户uid sessionid token 三者关系建立起来。
(2):校验sso
public ResultPo validateSession(String sessionId, String token) {
SSoPo sso = redisService.getPo(SSoPo.class, new MongoPrimaryKey("session_id", sessionId));
if (sso == null) {
return ResultPo.create(ErrorCode.SESSION_ERROR, "session not found");
}
//过期
boolean expr = TimeUtils.Now() > (sso.getMtime() + sso.getClass().getAnnotation(Cache.class).timestamp());
if (expr) {
removeSession(sso.getSessionId(), token);
return ResultPo.create(ErrorCode.SESSION_ERROR, "session long old");
}
ResultPo po = ResultPo.create(200, "session validate successful");
po.appendData("sso", sso);
return po;
}
用户在分布式系统中请求都携带此token 中,不用在此进行登录或者用户信息的校验,只需要通过token 和application所颁发的token 进行校验。
/**
* 验证web 应用是否合法
*
* @param serverId
* @param token
* @return
*/
public ResultPo validateApp(long serverId, String token) {
HashMap<String, Object> params = new HashMap<>();
params.put("serverId", serverId);
params.put("token", token);
String json = HttpUtil.getInstance(token).post("", this.url + "/sso/appValidate", params);
return BaseUtils.getResultPo(json);
}
(3):token 销毁
@Override
public ResultPo removeSession(String sessionId, String token) {
SSoPo sso = redisService.getPo(SSoPo.class, new MongoPrimaryKey("session_id", sessionId));
if (sso == null) {
return ResultPo.create(200, "");
}
redisService.deletePo(SSoPo.class, new MongoPrimaryKey("session_id", sessionId));
redisService.deleteHField(getSessionPool(token), sso.getUid() + "");
return ResultPo.create(200, "successful");
}
二:服务器节点接入
服务节点的介入,任何系统授信信息都需要通过中心服务器颁发的token 与appid 进行注册校验,提供application的注册信息。颁发给每个系统。
(1):系统节点接入
应用节点定义:
@Document(collection = "server_node")
@Cache(cache = true)
public class ServerNode implements IMongoPo {
@Field("server_id")
@PrimaryKey(name = "server_id")
@Indexed(name = "server_id", unique = true) //索引 name为索引名称,unique=true,唯一索引
private int serverId;
@Field("type")
private String type;
@Field("name")
private String name;
@Field("url")
private String url;
@Field("token")
private String token;
@Field("port")
private int port;
@Field("weight")
private int weight;
@Field("status")
private int status;
应用接入:
@Override
public ResultPo register(ServerNode serverNode) {
boolean successful = serverDao.add(serverNode);
if (successful) {
return ResultPo.create();
}
return ResultPo.create(500, "create app error");
}
分系统接入
定义一个服务的主要信息
"sso": [
{
"id": 1,
"url": "http://127.0.0.1",
"port": 8524,
"weight": 1,
"token": "123456"
}
],
应用校验
应用系统进行网络通讯的时候,首先会进行application校验。
@RequestMapping("appValidate")
public ResultPo appValidate(HttpServletRequest request, long serverId, String token) {
ResultPo resultpo = serverService.get(serverId);
Object obj = resultpo.get("data");
if (obj == null) {
resultpo.setCode(500);
resultpo.setMessage("application validate error!");
return resultpo;
}
ServerNode node = (ServerNode) obj;
if (!node.getToken().equals(token)) {
resultpo.setCode(500);
resultpo.setMessage("application validate error! token not march");
return resultpo;
}
resultpo.setCode(200);
resultpo.setMessage("application validate successful");
return resultpo;
}
package com.graduation.online.proxy;
import com.graduation.online.base.BaseUtils;
import com.graduation.online.base.enums.ServerType;
import com.graduation.online.base.model.ResultPo;
import com.graduation.online.base.service.ServerNodeService;
import com.graduation.online.base.utils.HttpUtil;
import java.util.HashMap;
public class SSoAgentServer extends BaseAgent {
public SSoAgentServer() {
super(ServerNodeService.getInstance().getServerNode(ServerType.SSO));
}
public ResultPo register(String sessionId, long uid) {
HashMap<String, Object> params = new HashMap<>();
params.put("sessionId", sessionId);
params.put("token", token);
params.put("uid", uid);
String json = HttpUtil.getInstance(token).post(sessionId, this.url + "/sso/register", params);
return BaseUtils.getResultPo(json);
}
/**
* 验证系统用户session 同一身份
*
* @param sessionId
* @return
*/
public ResultPo validateSession(String sessionId) {
HashMap<String, Object> params = new HashMap<>();
params.put("sessionId", sessionId);
params.put("token", token);
String json = HttpUtil.getInstance(token).post(sessionId, this.url + "/sso/validate", params);
return BaseUtils.getResultPo(json);
}
/**
* 验证web 应用是否合法
*
* @param serverId
* @param token
* @return
*/
public ResultPo validateApp(long serverId, String token) {
HashMap<String, Object> params = new HashMap<>();
params.put("serverId", serverId);
params.put("token", token);
String json = HttpUtil.getInstance(token).post("", this.url + "/sso/appValidate", params);
return BaseUtils.getResultPo(json);
}
public long getSSoUId(String sessionId) {
HashMap<String, Object> params = new HashMap<>();
params.put("sessionId", sessionId);
params.put("token", token);
String json = HttpUtil.getInstance(token).post(sessionId, this.url + "/sso/uid", params);
ResultPo result = BaseUtils.getResultPo(json);
if (result.get("uid") == null) {
return -1;
}
return Long.parseLong(result.get("uid").toString());
}
public String getSession(long uid) {
HashMap<String, Object> params = new HashMap<>();
params.put("uid", uid);
params.put("token", token);
String json = HttpUtil.getInstance(token).post(null, this.url + "/sso/getSession", params);
ResultPo result = BaseUtils.getResultPo(json);
if (result.get("sessionId") == null) {
return "";
}
return result.get("sessionId").toString();
}
public ResultPo removeSession(String sessionId) {
HashMap<String, Object> params = new HashMap<>();
params.put("sessionId", sessionId);
params.put("token", token);
String json = HttpUtil.getInstance(token).post(sessionId, this.url + "/sso/remove", params);
return BaseUtils.getResultPo(json);
}
}
分系统用户进行登录时候,只需要在登录授权一次,既可以在任何系统进行数据读取,
三:简单演示
当用户登录系统后
后台sso 系统接收到用户认证信息
用户授权后,进入到系统
系统为应用授权
总结:本系统采用web 的sso 统一身份认知,利用一个简单的,实现web 认证管理,加密认证,分布式系统应用授权认证。
代码地址:git@github.com:twjitm/graduation-online-server-code.git