RESTful登录设计(基于Spring及Redis的Token鉴权)
什么是 REST
REST (Representational State Transfer) 是一种软件架构风格。它将服务端的信息和功能等所有事物统称为资源,客户端的请求实际就是对资源进行操作,它的主要特点有: - 每一个资源都会对应一个独一无二的 url - 客户端通过 HTTP 的 GET、POST、PUT、DELETE 请求方法对资源进行查询、创建、修改、删除操作 - 客户端与服务端的交互必须是无状态的
使用 Token 进行身份鉴权
网站应用一般使用 Session 进行登录用户信息的存储及验证,而在移动端使用 Token 则更加普遍。它们之间并没有太大区别,Token 比较像是一个更加精简的自定义的 Session。Session 的主要功能是保持会话信息,而 Token 则只用于登录用户的身份鉴权。所以在移动端使用 Token 会比使用 Session 更加简易并且有更高的安全性,同时也更加符合 RESTful 中无状态的定义。
交互流程
- 客户端通过登录请求提交用户名和密码,服务端验证通过后生成一个 Token 与该用户进行关联,并将Token 返回给客户端。
- 客户端在接下来的请求中都会携带 Token,服务端通过解析 Token 检查登录状态。
- 当用户退出登录、其他终端登录同一账号(被顶号)、长时间未进行操作时 Token 会失效,这时用户需要重新登录。
程序示例
服务端生成的 Token 一般为随机的非重复字符串,根据应用对安全性的不同要求,会将其添加时间戳(通过时间判断 Token 是否被盗用)或 url 签名(通过请求地址判断 Token 是否被盗用)后加密进行传输。在本文中为了演示方便,仅是将 User Id 与 Token 以”_”进行拼接。
/**
* Token 的 Model 类,可以增加字段提高安全性,例如时间戳、url 签名
* @author ScienJus
* @date 2019/12/7.
*/
public class TokenModel {
// 用户 id
private long userId;
// 随机生成的 uuid
private String token;
public TokenModel (long userId, String token) {
this.userId = userId;
this.token = token;
}
public long getUserId () {
return userId;
}
public void setUserId (long userId) {
this.userId = userId;
}
public String getToken () {
return token;
}
public void setToken (String token) {
this.token = token;
}
}
Redis 是一个 Key-Value 结构的内存数据库,用它维护 User Id 和 Token 的映射表会比传统数据库速度更快,这里使用 Spring-Data-Redis 封装的 TokenManager 对 Token 进行基础操作:
/**
* 对 token 进行操作的接口
* @author ScienJus
* @date 2015/7/31.
*/
public interface TokenManager {
/**
* 创建一个 token 关联上指定用户
* @param userId 指定用户的 id
* @return 生成的 token
*/
public TokenModel createToken (long userId);
/**
* 检查 token 是否有效
* @param model token
* @return 是否有效
*/
public boolean checkToken (TokenModel model);
/**
* 从字符串中解析 token
* @param authentication 加密后的字符串
* @return
*/
public TokenModel getToken (String authentication);
/**
* 清除 token
* @param userId 登录用户的 id
*/
public void deleteToken (long userId);
}
/**
* 通过 Redis 存储和验证 token 的实现类
* @author ScienJus
* @date 2015/7/31.
*/
@Component
public class RedisTokenManager implements TokenManager {
private RedisTemplate redis;
@Autowired
public void setRedis (RedisTemplate redis) {
this