您的位置:首页 > 其它

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

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有效性检测时也会检测一次是否过期。

使用的过程就是前面的思路了。

就到这吧,基本都讲完了,有的实现是把最近一次操作的时间更新和过期性检测放在客户端,觉得没必要,这样可以减少网络交互次数,也方便。

有更好的实现和想法,欢迎一起来讨论下啦,觉得有问题的欢迎指正~~~~~~~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  服务器 sso Token