【从 0 开始开发一款直播 APP】16 利用 Cookie、Token、加密保证用户安全

本文为菜鸟窝作者蒋志碧的连载。“从 0 开始开发一款直播 APP ”系列来聊聊时下最火的直播 APP,如何完整的实现一个类"腾讯直播"的商业化项目

App 在开发过程中要保证账户安全,那么 Cookie,Token,加密如何保证安全呢?

Cookie和 Token 在实际使用中有什么区别?

Cookie 最初是解决 Http 连接无状态问题的产物,用于客户端和服务器共同维护一些状态数据,Cookie 会被附加到 Http 请求中,这不需要开发者做额外的支持,Cookie 存在一个的最大长度(4KB)问题,不能无限制的存储 Cookie 文件。

Token 通常作为验证后的凭证,免除在一定时间内重复验证。Token 的存储和参数传递都需要开发者来处理。

使用 Cookie 来维持登录状态,在实现过程中实际上是在 Cookie 中添加一个 Token 来维持登录状态。

客户端如何对账户密码进行加密?

客户端登录请求参数:account,md5(password + salt),加密也可使用 AES。

服务端验证后:返回 result,token。

在传输过程中,就算别人不知道密码,看到加密后的加密文,也可以请求服务器并登录成功。为抵御重放攻击,那么就需要在请求中加入时间戳,对整个请求体做签名,服务器验证签名后检查时间戳以及 account,来决定是否响应

一、Cookie

Cookie 是 web 服务器存放在用户硬盘上的一段文本,Cookie 允许一个 web 在站点在用户的机器上存放一些文本信息,并可以在以后重新获取它,这个基于文本的信息存储着一些 「键-值」对,不包含在任何可执行代码。大多数需要登录的网址在用户验证成功之后都会设置一个 Cookie,只要这个 Cookie 存在并有效,用户就可以自由浏览这个网站的任意页面,再次说明,Cookie 只包含数据,其本身而言并不有害。

Cookie 属性

Domain:域,表示当前cookie所属于哪个域或子域下面。

如果一个 Cookie不设置对应的 Domain,那么在 CookieContainer.Add(cookies) 的时候,会死掉。对于服务器返回的 Set-Cookie 中,如果没有指定 Domain 的值,那么其 Domain 的值是默认为当前所提交的 Http 的请求所对应的主域名的。比如访问 ,返回一个 Cookie,没有指名 Domain 值,那么其为值为默认的 。如 http: //live.demo.cniao5.com/Api/User ,返回一个 Cookie ,没有指定 Domain 值,默认值为 live.demo.cniao5.com。

Path:表示 Cookie 所属路径。

Expire time/Max-age:表示了 Cookie 的有效期。expire 的值,是一个时间,过了这个时间,该 Cookie 就失效了。或者是用 max-age 指定当前cookie是在多长时间之后而失效。

secure:表示该cookie只能用https传输。

一般用于包含认证信息的cookie,要求传输此cookie的时候,必须用https传输。

httponly:表示此cookie必须用于http或https传输。

这意味着,浏览器脚本,比如 javascript 中,是不允许访问操作此 cookie 的。


Cookie 持久化存储

CookieJarImpl,读者可以自己查看

//泓洋大神的 Cookie 管理类
public class CookieJarImpl implements CookieJar
{
    private CookieStore cookieStore;
    public CookieJarImpl(CookieStore cookieStore)
    {
        if (cookieStore == null) Exceptions.illegalArgument("cookieStore can not be null.");
        this.cookieStore = cookieStore;
    }

    @Override
    public synchronized void saveFromResponse(HttpUrl url, List<Cookie> cookies)
    {
      //添加一条符合规范的 cookie
        cookieStore.add(url, cookies);
    }

    @Override
    public synchronized List<Cookie> loadForRequest(HttpUrl url)
    {
      //根据 URL 读取 URL 下的所有 cookie
        return cookieStore.get(url);
    }

    public CookieStore getCookieStore()
    {
        return cookieStore;
    }
}

在直播中对 Cookie 实现持久化存储和获取。这样在请求网络的时候就会携带 Cookie 信息。

public class AsyncHttp {
   private static AsyncHttp mInstance;
    //初始化 Cookie 实现类,PersistentCookieStore 该类实现 Cookie 持久化
   private CookieJarImpl mCookieJar = new CookieJarImpl(new PersistentCookieStore(LiveApplication.getInstance()));

   //初始化操作,设置超时时间
   private OkHttpClient okHttpClient = new OkHttpClient.Builder()
         .connectTimeout(20 * 1000, TimeUnit.MILLISECONDS)
         .readTimeout(20 * 1000, TimeUnit.MILLISECONDS)
         .cookieJar(mCookieJar)
         .build();
}

PersistentCookieStore 类对 Cookie 进行了管理

public class PersistentCookieStore implements CookieStore
{
  /**
   * Construct a persistent cookie store.
   * @param context Context to attach cookie store to
   */
  public PersistentCookieStore(Context context)
  {
      cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
      cookies = new HashMap<String, ConcurrentHashMap<String, Cookie>>();

      // Load any previously stored cookies into the store
    //读取cookie文件中所有的cookie数据,遍历cookie的map集合
      Map<String, ?> prefsMap = cookiePrefs.getAll();
      for (Map.Entry<String, ?> entry : prefsMap.entrySet())
      {
          if (((String) entry.getValue()) != null && !((String) entry.getValue()).startsWith(COOKIE_NAME_PREFIX))
          {
              String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
              for (String name : cookieNames)
              {
                  String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
                  if (encodedCookie != null)
                  {
                      Cookie decodedCookie = decodeCookie(encodedCookie);
                      if (decodedCookie != null)
                      {
                          if (!cookies.containsKey(entry.getKey()))
                              cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>());
                            //在这里获取 key 和 value
                          cookies.get(entry.getKey()).put(name, decodedCookie);
                      }
                  }
              }

          }
      }
  }
  //其他是重写的一些get,add,remove等方法,读者自己查看
  //........
}

在之前的登录文章中有对用户信息进行缓存处理,没讲解用户类哪里来的,这里说明一下,请看图。之前登陆是用的测试账号,现在使用菜鸟窝账号和密码进行登录,如果没有菜鸟窝账号进行登录,是无法发起直播的,而发起直播的条件就是「购买直播课程」,否则无法发起直播。在响应体中返回了 cookie 和 token。



二、Token

基于 Token 的身份验证是无状态的,我们不将用户信息存在服务器或 Session中。NoSession 意味着你的程序可以根据需要去增减机器而不用担心用户是否登录。

Token 身份验证过程

1、客户端使用用户名和密码请求登录

2、服务端收到请求,去验证用户名和密码

3、验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端

4、客户端收到 Token 之后将 token 存储起来,如放在 Cookie 或 缓存

5、客户端每次向服务端请求资源的时候就需要带着服务端签发的 Token

6、服务端收到请求,然后去验证客户端请求里面带着的 Token 是否一致,如果验证成功,就向客户端返回请求数据

在返回体中可以看到 token 信息和 cookie 信息。在代码中会进行验证,保证账户安全,并且对密码进行加密。



在 IRequest 网络请求基类中添加 token 验证。

public abstract class IRequest extends IDontObfuscate {
   protected RequestParams mParams = new RequestParams();
   public RequestParams getParams() {
      String token = UserInfoCache.getToken(LiveApplication.getInstance());
      if (token != null){
         mParams.put("token",token);
      }
      return mParams;
   }
}

在 UserInfoCache 用户缓存类中存储 token,并从缓存中读取 token。

public class UserInfoCache extends IDontObfuscate{
    public static void saveCache(Context context, UserInfo info){
        ACache.get(context).put("token",info.getToken());
    }
    public static String getToken(Context context){
        return ACache.get(context).getAsString("token");
    }    
}

将 LoginRequest 中请求参数替换成利用菜鸟窝账号登录的相关参数,并对密码进行加密。

public class LoginRequest extends IRequest {

   public LoginRequest(int requestId, String userName, String password) {
      mRequestId = requestId;
//    mParams.put("action", "login");//普通账号登录
      mParams.put("action", "loginCniaow");//发起直播需要调用这个接口,使用菜鸟窝账号并且购买了直播课程
      mParams.put("userName", userName);
      mParams.put("password", password);
      if (mParams.getUrlParams("action").equals("loginCniaow")) {
         mParams.put("password", CipherUtil.getAESInfo(password));
      } else {
         mParams.put("password", password);
      }
   }

   @Override
   public String getUrl() {
      return getHost() + "User";
   }

   @Override
   public Type getParserType() {
      return new TypeToken<Response<UserInfo>>() {
      }.getType();
   }
}

三、AES 加密

在 LoginRequest 中对密码进行了 AES 加密,返回的是一串字符串。AES 是一个分组密码,属于对称密码,AES 算法的模块在对称密码领域特别是分组密码领域常有使用。

详情查看 AES 算法详解

运行程序可以看到控制台打印出来的用户信息和 token,这里只打印了头像信息和 token。


详情转至 Github

撸这个项目的一半,你就是大神 , 戳http://mp.weixin.qq.com/s/ZagocTlDfxZpC2IjUSFhHg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容