SSO-Token,服务器端实现
2015-06-15 10:31
330 查看
最近在项目中遇到了一个单点登录的问题,就做了一个研究了下并做了实验,并分享给大家,有意见请指正~~~~
关于SSO的定义等一些相关的问题请自行百度,这里只说实现~~~~~~
首先呢,我做的SSO的实现在验证等操作全部是在服务器端实现的:
思路如下:
1、用户登录后,生成一个16位长度的Token字符串
2、将Token发回到客户端,在服务器端,将生成的Token和用户信息放到一个全局唯一的存储结构中
3、设置Token的过期时间和最后一次操作时间
4、每次用户进行操作时,验证Token是否过期,如过期要求重新登录,否则直接操作,并更新最近一次操作时间
5、后台线程定期检测Token的存储结构,将过期的Token移除
行了,直接来看实现吧:
我们将过期时间间隔,以及Token的字符串长度的设置都在配置文件中:如下SystemComfig.properties
解析配置文件的类:SystemConfigConstants.java
下面就是重点了:
我们需要去生成我们想要的长度的Token字符串,我这里使用的是使用是UUID,因为这个可以保证生成的字符串的不会重复的,但是这个太长了128bit呢,所有觉得太长了
所有我又使用了Base64算法对UUID字符串进行处理,至于UUID是啥,请Google, 所依赖的包:commons-codec-1.10.jar
下面看下具体的实现吧:TokenGenerateUtit.java
public class TokenGenerateUtil {
//用于外部调用生成Token字符串的静态方法
public static String getToken(){
UUID uuid = UUID.randomUUID();
String token = compressedUUID(uuid);
//验证是否有相同的Token,为true重新生成
if(TokenManager.DATA_MAP.containsKey(token)){
uuid = UUID.randomUUID();
token = compressedUUID(uuid);
}
return token;
}
//对UUID进行处理,形成想要的Token长度
private static String compressedUUID(UUID uuid) {
byte[] byUuid = new byte[SystemConfigConstants.TOKEN_BYTE_LEN];
long least = uuid.getLeastSignificantBits();
long most = uuid.getMostSignificantBits();
long2bytes(most, byUuid, 0);
long2bytes(least, byUuid, SystemConfigConstants.TOKEN_BYTE_LEN/2);
String compressUUID = Base64.encodeBase64URLSafeString(byUuid);
return compressUUID;
}
//长度处理
private static void long2bytes(long value, byte[] bytes, int offset) {
for (int i = SystemConfigConstants.TOKEN_BYTE_LEN/2-1; i > -1; i--) {
bytes[offset++] = (byte) ((value >> 8 * i) & 0xFF);
}
}
}虽然呢UUID可以保证的是唯一的,但是经过Base64处理后,为了不出现重复的情况(在存储结构中,token字符串是作为map的key的,所以不能重复),我在
生成时加了一个检测过程。
下面就是对Token的管理了:先看代码:TokenManager.java
public class TokenManager {
//计时器,间隔一定的时间执行一次,设置为守护线程设true
private static final Timer timer = new Timer(true);
//构造函数私有化防止静态类被实例化 使用这种方式保障全局唯一性
private TokenManager() {
}
//复合结构体,含登录的User和过期时间expried两个成员以及最后一次操作的时间
private static class Token {
private String userName;//登录用户名
private Date expired; //过期时间
private Date lastOperate; // 最近一次操作的时间
}
//令牌存储结构 ConcurrentHashMap:支持同步的HashMap
public static final Map<String, Token> DATA_MAP = new ConcurrentHashMap<String, Token>();
/**
* 令牌有效性验证
*/
public static boolean validate(String vt) {
System.out.println("验证Token是否有效.......");
boolean isValid = true;
if(DATA_MAP.containsKey(vt)){
Date expired = DATA_MAP.get(vt).expired;
Date now = new Date();
if(now.compareTo(expired) > 0){//已过期
isValid = false;
DATA_MAP.remove(vt);//移除
}
}else{
isValid = false;
}
return isValid;
}
/**
* 用户授权成功后存入授权信息
*/
public static void addToken(String vt, String userName) {
Token token = new Token();
token.userName = userName;
token.lastOperate = new Date();
token.expired = new Date(token.lastOperate.getTime() + SystemConfigConstants.TOKENTIMEOUT * 60 * 1000);
DATA_MAP.put(vt, token);
}
/**
* 更新最近一次操作的时间
*/
public static void updateLastOperate(String vt) {
Token token = DATA_MAP.get(vt);
token.lastOperate = new Date(new Date().getTime());//更新最近时间
token.expired = new Date(token.lastOperate.getTime() + SystemConfigConstants.TOKENTIMEOUT * 60 * 1000); //更新过期时间
}
/**
* 计时器实现
*/
static {
timer.schedule(new TimerTask() {
@Override
public void run() {
for (Entry<String, Token> entiy : DATA_MAP.entrySet()) {
String vt = entiy.getKey();
Token token = entiy.getValue();
Date expired = token.expired;
Date now = new Date();
//当前时间大于过期时间
//当前时间大于过期时间
//标识已经过期时间
//将token移除
if (now.compareTo(expired) > 0) {
//已过期,清除
DATA_MAP.remove(vt);
}
}
}
//第一个为线程启动后间隔多久启动第一次执行,第二个是两次执行的间隔时间
}, 0 * 1000, 5 * 1000);
}
/**
* 在系统启动时启动管理工具
*/
public static void init(){ }
}可以看到有一个Timer以守护线程的方式,运行,对存储结构进行维护,对已经过期的Token移除。
当每次操作的时候,对最近一次操作的时间进行更新,因为对Token过期的检测的线程是有时间间隔的,所以为了保证Token的有效性,每次操作前的Token有效性检测时也会检测一次是否过期。
使用的过程就是前面的思路了。
就到这吧,基本都讲完了,有的实现是把最近一次操作的时间更新和过期性检测放在客户端,觉得没必要,这样可以减少网络交互次数,也方便。
有更好的实现和想法,欢迎一起来讨论下啦,觉得有问题的欢迎指正~~~~~~~~
关于SSO的定义等一些相关的问题请自行百度,这里只说实现~~~~~~
首先呢,我做的SSO的实现在验证等操作全部是在服务器端实现的:
思路如下:
1、用户登录后,生成一个16位长度的Token字符串
2、将Token发回到客户端,在服务器端,将生成的Token和用户信息放到一个全局唯一的存储结构中
3、设置Token的过期时间和最后一次操作时间
4、每次用户进行操作时,验证Token是否过期,如过期要求重新登录,否则直接操作,并更新最近一次操作时间
5、后台线程定期检测Token的存储结构,将过期的Token移除
行了,直接来看实现吧:
我们将过期时间间隔,以及Token的字符串长度的设置都在配置文件中:如下SystemComfig.properties
TOKENTIMEOUT=1 设置的Token的过期时间,这里用的1分钟,方便实验,根据系统自行设定即可 TOKEN_BYTE_LEN=12 8--->10; 10--->14; 12--->16 具体的在实验的时候看下就可以了
解析配置文件的类:SystemConfigConstants.java
public class SystemConfigConstants { //Token过期时间 public static final int TOKENTIMEOUT; //用于生成的Token长度 public static final int TOKEN_BYTE_LEN; /* * 取值过程 */ static{ //配置文件读取 InputStream stream = null; //读取工具 Properties properties = null; try { stream = SystemConfigConstants.class.getResourceAsStream("SystemComfig.properties"); properties = new Properties(); properties.load(stream); } catch (IOException e) { e.printStackTrace(); } TOKENTIMEOUT = Integer.parseInt(properties.getProperty("TOKENTIMEOUT")); TOKEN_BYTE_LEN = Integer.parseInt(properties.getProperty("TOKEN_BYTE_LEN")); } }
下面就是重点了:
我们需要去生成我们想要的长度的Token字符串,我这里使用的是使用是UUID,因为这个可以保证生成的字符串的不会重复的,但是这个太长了128bit呢,所有觉得太长了
所有我又使用了Base64算法对UUID字符串进行处理,至于UUID是啥,请Google, 所依赖的包:commons-codec-1.10.jar
下面看下具体的实现吧:TokenGenerateUtit.java
public class TokenGenerateUtil {
//用于外部调用生成Token字符串的静态方法
public static String getToken(){
UUID uuid = UUID.randomUUID();
String token = compressedUUID(uuid);
//验证是否有相同的Token,为true重新生成
if(TokenManager.DATA_MAP.containsKey(token)){
uuid = UUID.randomUUID();
token = compressedUUID(uuid);
}
return token;
}
//对UUID进行处理,形成想要的Token长度
private static String compressedUUID(UUID uuid) {
byte[] byUuid = new byte[SystemConfigConstants.TOKEN_BYTE_LEN];
long least = uuid.getLeastSignificantBits();
long most = uuid.getMostSignificantBits();
long2bytes(most, byUuid, 0);
long2bytes(least, byUuid, SystemConfigConstants.TOKEN_BYTE_LEN/2);
String compressUUID = Base64.encodeBase64URLSafeString(byUuid);
return compressUUID;
}
//长度处理
private static void long2bytes(long value, byte[] bytes, int offset) {
for (int i = SystemConfigConstants.TOKEN_BYTE_LEN/2-1; i > -1; i--) {
bytes[offset++] = (byte) ((value >> 8 * i) & 0xFF);
}
}
}虽然呢UUID可以保证的是唯一的,但是经过Base64处理后,为了不出现重复的情况(在存储结构中,token字符串是作为map的key的,所以不能重复),我在
生成时加了一个检测过程。
下面就是对Token的管理了:先看代码:TokenManager.java
public class TokenManager {
//计时器,间隔一定的时间执行一次,设置为守护线程设true
private static final Timer timer = new Timer(true);
//构造函数私有化防止静态类被实例化 使用这种方式保障全局唯一性
private TokenManager() {
}
//复合结构体,含登录的User和过期时间expried两个成员以及最后一次操作的时间
private static class Token {
private String userName;//登录用户名
private Date expired; //过期时间
private Date lastOperate; // 最近一次操作的时间
}
//令牌存储结构 ConcurrentHashMap:支持同步的HashMap
public static final Map<String, Token> DATA_MAP = new ConcurrentHashMap<String, Token>();
/**
* 令牌有效性验证
*/
public static boolean validate(String vt) {
System.out.println("验证Token是否有效.......");
boolean isValid = true;
if(DATA_MAP.containsKey(vt)){
Date expired = DATA_MAP.get(vt).expired;
Date now = new Date();
if(now.compareTo(expired) > 0){//已过期
isValid = false;
DATA_MAP.remove(vt);//移除
}
}else{
isValid = false;
}
return isValid;
}
/**
* 用户授权成功后存入授权信息
*/
public static void addToken(String vt, String userName) {
Token token = new Token();
token.userName = userName;
token.lastOperate = new Date();
token.expired = new Date(token.lastOperate.getTime() + SystemConfigConstants.TOKENTIMEOUT * 60 * 1000);
DATA_MAP.put(vt, token);
}
/**
* 更新最近一次操作的时间
*/
public static void updateLastOperate(String vt) {
Token token = DATA_MAP.get(vt);
token.lastOperate = new Date(new Date().getTime());//更新最近时间
token.expired = new Date(token.lastOperate.getTime() + SystemConfigConstants.TOKENTIMEOUT * 60 * 1000); //更新过期时间
}
/**
* 计时器实现
*/
static {
timer.schedule(new TimerTask() {
@Override
public void run() {
for (Entry<String, Token> entiy : DATA_MAP.entrySet()) {
String vt = entiy.getKey();
Token token = entiy.getValue();
Date expired = token.expired;
Date now = new Date();
//当前时间大于过期时间
//当前时间大于过期时间
//标识已经过期时间
//将token移除
if (now.compareTo(expired) > 0) {
//已过期,清除
DATA_MAP.remove(vt);
}
}
}
//第一个为线程启动后间隔多久启动第一次执行,第二个是两次执行的间隔时间
}, 0 * 1000, 5 * 1000);
}
/**
* 在系统启动时启动管理工具
*/
public static void init(){ }
}可以看到有一个Timer以守护线程的方式,运行,对存储结构进行维护,对已经过期的Token移除。
当每次操作的时候,对最近一次操作的时间进行更新,因为对Token过期的检测的线程是有时间间隔的,所以为了保证Token的有效性,每次操作前的Token有效性检测时也会检测一次是否过期。
使用的过程就是前面的思路了。
就到这吧,基本都讲完了,有的实现是把最近一次操作的时间更新和过期性检测放在客户端,觉得没必要,这样可以减少网络交互次数,也方便。
有更好的实现和想法,欢迎一起来讨论下啦,觉得有问题的欢迎指正~~~~~~~~
相关文章推荐
- 小心服务器内存居高不下的元凶--WebAPI服务
- 运维入门
- Linux5.9无人值守安装
- 数据中心和云未来的十二大趋势
- 用vsftp快速搭建ftp服务器
- Linux快速构建apache web服务器
- 服务器监控策略浅谈
- 如何降低服务器采购成本 原理分析
- 建议的服务器分区办法
- 服务器托管六大优势分析
- Erlang实现的一个Web服务器代码实例
- 服务器技术全面解析
- 保护DNS服务器的几点方法小结
- 我国成为全球第二大服务器消费国
- 服务器 安全检查要点[星外提供]
- 服务器应用自动重新启动IIS批处理[原创]_DOS/BAT_脚本之家
- FTP 服务器关于权限的问题
- 批处理设置windows服务器的代码ThecSafe1.9.4第1/3页
- Apache服务器配置全攻略