1 @Component 2 public class Memory { 3 @Autowired 4 private Cache ehcache; // 注意这里引入的Cache是net.sf.ehcache.Cache 5 6 public void setValue(String key, String value) { 7 ehcache.put(new Element(key, value)); 8 } 9 10 public Object getValue(String key) { 11 Element element = ehcache.get(key); 12 return element != null ? element.getValue() : null; 13 } 14 }
缓存准备完毕,接下来就是模拟用户Session了,实现思路是这样的:
用户登录成功后,服务器端按照一定规则生成一个Token令牌,Token是可变的,也可以是固定的(后面会说明);
将Token作为key,用户信息作为value放到缓存中,设置有效时长(比如30分钟内没有访问就失效);
将Token返回给APP端,APP保存到本地存储中以便请求接口时带上此参数;
通过拦截器拦截所有涉及到用户隐私安全等方面的接口,验证请求中的Token参数合法性并检查缓存是否过期;
验证通过后,将Token值保存到线程存储中,以便当前线程的操作可以通过Token直接从缓存中索引当前登录的用户信息。
综上所述,APP端要做的事情就是登录并从服务器端获取Token存储起来,当访问用户隐私相关的接口时带上这个Token标识自己的身份。服务器端要做的就是拦截用户隐私相关的接口验证Token和登录信息,验证后将Token保存到线程变量里,之后可以在其它操作中取出这个Token并从缓存中获取当前用户信息。这样APP不需要知道用户ID,它拿到的只是一个身份标识,而且这个标识是可变的,服务器根据这个标识就可以知道要操作的是哪个用户。
对于Token是否可变,处理细节上有所不同,效果也不一样。
Token固定的情况:服务器端生成Token时将用户名和密码一起进行MD5加密,即MD5(username+password)。这样对于同一个用户而言,每次登录的Token是相同的,用户可以在多个客户端登录,共用一个Session,当用户密码变更时要求用户重新登录;
Token可变的情况:服务器端生成Token时将用户名、密码和当前时间戳一起MD5加密,即MD5(username+password+timestamp)。这样对于同一个用户而言,每次登录的Token都是不一样的,再清除上一次登录的缓存信息,即可实现唯一用户登录的效果。
为了保证同一个用户在缓存中只有一条登录信息,服务器端在生成Token后,可以再单独对用户名进行MD5作为Seed,即MD5(username)。再将Seed作为key,Token作为value保存到缓存中,这样即便Token是变化的,但每个用户的Seed是固定的,就可以通过Seed索引到Token,再通过Token清除上一次的登录信息,避免重复登录时缓存中保存过多无效的登录信息。
基于Token的Session控制部分代码如下:
1 @Component 2 public class Memory { 3 4 @Autowired 5 private Cache ehcache; 6 7 /** 8 * 关闭缓存管理器 9 */ 10 @PreDestroy 11 protected void shutdown() { 12 if (ehcache != null) { 13 ehcache.getCacheManager().shutdown(); 14 } 15 } 16 17 /** 18 * 保存当前登录用户信息 19 * 20 * @param loginUser 21 */ 22 public void saveLoginUser(LoginUser loginUser) { 23 // 生成seed和token值 24 String seed = MD5Util.getMD5Code(loginUser.getUsername()); 25 String token = TokenProcessor.getInstance().generateToken(seed, true); 26 // 保存token到登录用户中 27 loginUser.setToken(token); 28 // 清空之前的登录信息 29 clearLoginInfoBySeed(seed); 30 // 保存新的token和登录信息 31 String timeout = getSystemValue(SystemParam.TOKEN_TIMEOUT); 32 int ttiExpiry = NumberUtils.toInt(timeout) * 60; // 转换成秒 33 ehcache.put(new Element(seed, token, false, ttiExpiry, 0)); 34 ehcache.put(new Element(token, loginUser, false, ttiExpiry, 0)); 35 } 36 37 /** 38 * 获取当前线程中的用户信息 39 * 40 * @return 41 */ 42 public LoginUser currentLoginUser() { 43 Element element = ehcache.get(ThreadTokenHolder.getToken()); 44 return element == null ? null : (LoginUser) element.getValue(); 45 } 46 47 /** 48 * 根据token检查用户是否登录 49 * 50 * @param token 51 * @return 52 */ 53 public boolean checkLoginInfo(String token) { 54 Element element = ehcache.get(token); 55 return element != null && (LoginUser) element.getValue() != null; 56 } 57 58 /** 59 * 清空登录信息 60 */ 61 public void clearLoginInfo() { 62 LoginUser loginUser = currentLoginUser(); 63 if (loginUser != null) { 64 // 根据登录的用户名生成seed,然后清除登录信息 65 String seed = MD5Util.getMD5Code(loginUser.getUsername()); 66 clearLoginInfoBySeed(seed); 67 } 68 } 69 70 /** 71 * 根据seed清空登录信息 72 * 73 * @param seed 74 */ 75 public void clearLoginInfoBySeed(String seed) { 76 // 根据seed找到对应的token 77 Element element = ehcache.get(seed); 78 if (element != null) { 79 // 根据token清空之前的登录信息 80 ehcache.remove(seed); 81 ehcache.remove(element.getValue()); 82 } 83 } 84 }
Token拦截器部分代码如下: