天天看点

webapi鉴权使用token令牌

一为什么使用Token验证:

在app开放接口api的设计中,避免不了的就是安全性问题,因为大多数接口涉及到用户的个人信息以及一些敏感的数据,所以对这些接口需要进行身份的认
证,那么这就需要用户提供一些信息,比如用户名密码等,但是为了安全起见让用户暴露的明文密码次数越少越好,我们一般在web项目中,大多数采用保
存的session中,然后在存一份到cookie中,来保持用户的会话有效性,但是会在客户端关闭时丢失,还有过期问题。但是app更像一个离线客户端, 因为
它的用户数据保存在本地,可以实现免登陆等,只在它拿数据的时候 提交认证信息就行了 。      

二什么场景使用Token验证:

现在很多基于restful的api接口都有个登录的设计,也就是在发起正式的请求之前先通过一个登录的请求接口,申请一个叫做token的东西。申请成功后,后面其他的请求都要带上这个token,服务端通过这个token验证请求的合法性。这个token通常都有一个有效期,一般就是几个小时。

三Token验证有什么特点

1.无状态、可扩展

2.支持移动设备

3.跨程序调用

4.安全

四Token验证的原理

基于Token的身份验证是无状态的,我们不将用户信息存在服务器或Session中。用户登录后向服务器提供用户认证信息(如账户和密码),服务器认证完后给客户端返回一个Token令牌,用户再次获取信息时,带上此令牌,如果令牌正取,则返回数据。对于获取Token信息后,访问用户相关接口,客户端请求的url需要带上如下参数:

   时间戳:timestamp

   Token令牌:token

然后将所有用户请求的参数按照字母排序(包括timestamp,token),然后更具MD5加密(可以加点盐),全部大写,生成sign签名,这就是所说的url签名算法。然后登陆后每次调用用户信息时,带上sign,timestamp,token参数。

五Token如何生成

token实例

/**
 * Token 的 Model 类,可以增加字段提高安全性,例如时间戳、url 签名
 * @author ScienJus
 * @date 2015/7/31.
 */
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;
    }
}
           
/**
 * 对 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.redis = redis;
        // 泛型设置成 Long 后必须更改对应的序列化方案
        redis.setKeySerializer (new JdkSerializationRedisSerializer ());
    }

    public TokenModel createToken (long userId) {
        // 使用 uuid 作为源 token
        String token = UUID.randomUUID ().toString ().replace ("-", "");
        TokenModel model = new TokenModel (userId, token);
        // 存储到 redis 并设置过期时间
        redis.boundValueOps (userId).set (token, Constants.TOKEN_EXPIRES_HOUR, TimeUnit.HOURS);
        return model;
    }

    public TokenModel getToken (String authentication) {
        if (authentication == null || authentication.length () == 0) {
            return null;
        }
        String [] param = authentication.split ("_");
        if (param.length != 2) {
            return null;
        }
        // 使用 userId 和源 token 简单拼接成的 token,可以增加加密措施
        long userId = Long.parseLong (param [0]);
        String token = param [1];
        return new TokenModel (userId, token);
    }

    public boolean checkToken (TokenModel model) {
        if (model == null) {
            return false;
        }
        String token = redis.boundValueOps (model.getUserId ()).get ();
        if (token == null || !token.equals (model.getToken ())) {
            return false;
        }
        // 如果验证成功,说明此用户进行了一次有效操作,延长 token 的过期时间
        redis.boundValueOps (model.getUserId ()).expire (Constants.TOKEN_EXPIRES_HOUR, TimeUnit.HOURS);
        return true;
    }

    public void deleteToken (long userId) {
        redis.delete (userId);
    }
}