iOS 移动端接口加密流程(包含单点登录和失效时间)

Posted 软件测试培训

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 移动端接口加密流程(包含单点登录和失效时间)相关的知识,希望对你有一定的参考价值。

顾翔老师作品《软件测试技术实战 设计、工具及管理》

http://detail.youzan.com/show/goods?alias=3erp1xpd7hmoh&from=wsc&kdtfrom=wsc&sf=wx_sm

店铺二维码:

啄木鸟软件测试培训网:www.3testing.com

本文来自:http://www.51testing.com


 ios 移动端接口加密流程(包含单点登录和失效时间)

  最近换了工作,新公司的一套面试题是关于移动端接口加密流程,当时根据面试题画出了大概的 UML 类图,但是还有很多考虑不周的情况。接触到代码后仔细研究看了一下数据请求逻辑,感觉还是有必要总结一下加深理解。

  ●为了数据安全,大多数没有使用 https 的公司都会选择 token 来提高数据请求的安全性,token 的生成方法也不尽相同,只要保证唯一性就行,最好是后台管理 token,由后台生成并返回前端保存。

  上送参数

  ●所有接口都需要携带当前的的版本号和平台

iOS 移动端接口加密流程(包含单点登录和失效时间)

  ●iOS端和安卓端分配不同的 app_id、private_key。每次通信是除了上述要上送的数据外,还要上送 timestamp、app_id、private_key、rand、sign。见下表:

iOS 移动端接口加密流程(包含单点登录和失效时间)

  如:iOS 端

  app_id=170ib799dd511e7b66000163e033322   

    private_key = 170ib799dd511e7b66000163e033322

  ●业务数据为 phoneNo=123456&password=123456

  ●不包含 sign 的上送数据(data) 为:

  app_id=170ib799dd511e7b66000163e033322&phoneNo=123456&password=123456&plat=iOS&rand=12dwfhdy487rSelsd&timestamp=2017-12-9-13:39:39:01&ver=1.0

  ●sign =MD5(data+ private_key), 就是对一下字符串做MD5运算

app_id=170ib799dd511e7b66000163e033322&phoneNo=123456&password=123456&private_key=170ib799dd511e7b66000163e033322&plat=iOS&rand=12dwfhdy487rSelsd&timestamp=2017-12-9-13:39:39:01&ver=1.0

  ●计算出来的结果sign最终为32位数据和字母组合8357heukf384r83gh3fi3dwks42i993(示例)

  最终上送data + sign 如:

  app_id=170ib799dd511e7b66000163e033322&phoneNo=123456&password=123456&plat=iOS&rand=12dwfhdy487rSelsd&timestamp=2017-12-9-13:39:39:01&ver=1.0&sign= 8357heukf384r83gh3fi3dwks42i993

  这是签名管理类.h代码

  #import <Foundation/Foundation.h>

  @interface TPBaseRequest : NSObject

  @property (nonatomic, strong) NSString *plat;

  @property (nonatomic, strong) NSString *ver;

  - (NSDictionary *)baseParameters;

  - (NSDictionary *)finalParametersFrom:(NSDictionary *)dic;

  @end

  这是签名管理类.m代码

  #import "TPBaseRequest.h"

  #import <CommonCrypto/CommonDigest.h>

  @interface TPBaseRequest()

  @property (nonatomic, strong)NSString *rand;

  @property (nonatomic, copy) NSString *timeStr;

  @end

  @implementation TPBaseRequest

  //最终上送为data(base + dic)+sign

  - (NSDictionary *)finalParametersFrom:(NSDictionary *)dic

  {

      self.rand = [self gainRand];

      NSMutableDictionary *finalDic = [NSMutableDictionary dictionaryWithDictionary:[self baseParameters]];

      [finalDic removeObjectForKey:@"private_key"];

      [finalDic addEntriesFromDictionary:dic];

      [finalDic setObject:[self gainSign:dic] forKey:@"sign"];

      [finalDic setObject:_timeStr forKey:@"timestamp"];

      return finalDic;

  }

  //基础参数

  - (NSDictionary *)baseParameters

  {

      //基础参数赋值

      NSMutableDictionary *para = [NSMutableDictionary dictionary];

      [para setObject:kApp_id forKey:@"app_id"];

      [para setObject:kPrivate_key forKey:@"private_key"];

      [para setObject:@"ios" forKey:@"plat"];

      [para setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"ver"];

      [para setObject:self.rand forKey:@"rand"];

      [para setObject:[self gainTimestamp] forKey:@"timestamp"];

      

      if([MDAccountStore isLogined]){

          if(![MDAccountManager sharedInstance].currentAccount){

              [MDAccountManager sharedInstance].currentAccount = [[MDAccountStore sharedInstance] readTheAccount];

          }

      }

      //判断是否有用户登录 如果有携带上 userId

      if ([MDAccountManager sharedInstance].currentAccount.userId) {

          long userId = [MDAccountManager sharedInstance].currentAccount.userId;

          [para setObject:[NSNumber numberWithLong:userId] forKey:@"userId"];

      }

      //判断本地存储是否有 token

      if ([MDAccountManager sharedInstance].currentAccount.token) {

          [para setObject:[MDAccountManager sharedInstance].currentAccount.token forKey:@"token"];

      }

      

      return [NSDictionary dictionaryWithDictionary:para];

  }

  //获取随机数

  - (NSString *)gainRand

  {

      NSString *str = [NSString string];

      //16位

      for (int i = 0; i < 16; i++)

      {

          //随机出现字母、数字

          switch(arc4random() % 3) {

              case 0:

                  str = [str stringByAppendingString:[NSString stringWithFormat:@"%d", arc4random() % 10]];

                  break;

              case 1:

                  str = [str stringByAppendingString:[NSString stringWithFormat:@"%c",  (arc4random() % 26) + 97]];

                  break;

              case 2:

                  str = [str stringByAppendingString:[NSString stringWithFormat:@"%c", (arc4random() % 26) + 65]];

                  break;

          }

      }

      return str;

  }

  //获取时间字符串

  - (NSString *)gainTimestamp

  {

      NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];

      dateFormatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss";

      NSString *timeStr = [dateFormatter stringFromDate:[NSDate date]];

      //    NSString *stamp = [NSString stringWithFormat:@"%ld", (long)[[NSDate date] timeIntervalSince1970]];

      return timeStr;

  }

  //获取sign sign=md5(data+private_key)

  - (NSString *)gainSign:(NSDictionary *)dic

  {

      NSString *sign = [self getMd5_32Bit_String:[self gainData:dic]];

      return sign;

  }

  //获取data+private_key (private_key包含在base中)

  - (NSString *)gainData:(NSDictionary *)dic

  {

      //data = base + dic

      NSMutableDictionary *basePara = [NSMutableDictionary dictionaryWithDictionary:[self baseParameters]];

      _timeStr = [self gainTimestamp];

      [basePara addEntriesFromDictionary:dic];

      

      NSArray *arr = [basePara allKeys];

      arr = [arr sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2){

          NSComparisonResult result = [obj1 compare:obj2];

          return result == NSOrderedDescending;

      }];

      

      NSString *dataStr = [NSString string];

      for (int i = 0; i < arr.count; i++) {

          dataStr = [dataStr stringByAppendingString:arr[i]];

          dataStr = [dataStr stringByAppendingString:@"="];

          dataStr = [dataStr stringByAppendingString:[NSString stringWithFormat:@"%@",[basePara objectOrNilForKey:arr[i]]]];

          dataStr = [dataStr stringByAppendingString:@"&"];

      }

      dataStr = [dataStr substringToIndex:dataStr.length - 1];

      return dataStr;

  }

  //md5加密

  - (NSString *)getMd5_32Bit_String:(NSString *)srcString

  {

      const char *cStr = [srcString UTF8String];

      unsigned char digest[CC_MD5_DIGEST_LENGTH];

      CC_MD5( cStr, strlen(cStr), digest );

      NSMutableString *result = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];

      for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)

          [result appendFormat:@"%02x", digest[i]];

      

      return result;

  }

  @end

  后台在接收到数据后首先会做验签操作,验证签名是否正确和签名是否失效。

  ●单点登录:如果账号被别的客户端登录,上送的 token 会发生改变,之前的 token 就会过期,会在本客户端发出数据请求时弹出登录框。

  ●失效时间: 后台验证token时间,超过预设时间会返回-100

  单点登录和失效时间返回 json 数据如下:

  {

    "err":{

        "code":-100,

        "msg": 登录已过期,请重新登录,

        "eventId":"xxx-xxx-xxx-xxx-xxx"

    }

  }

  ●这是有一个建议,我们可以在根视图设置一个通知监听,当我们在处理数据时收到 code= -100 是发出登录的通知,模态出登陆页面

  ●数据请求方法

  + (void)postRequestWihtUrl:(NSString *)url params:(NSDictionary *)params success:(void (^)(id))successInfo failure:(TPErrorInfo)errorInfo {

      AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

      manager.requestSerializer = [AFHTTPRequestSerializer serializer];

      manager.responseSerializer.acceptableContentTypes = [NSSet setWithArray:@[@"text/html", @"application/json", @"application/x-www-form-urlencoded"]];

      

      /**

       数据请求

       获取基础上送参数

       [[[TPBaseRequest alloc] init] finalParametersFrom:params]

       */

      [manager POST:url parameters:[[[TPBaseRequest alloc] init] finalParametersFrom:params] progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

          TPErrorHandle *err = [[TPErrorHandle alloc] initWithDic:[responseObject objectForKey:@"err"]];

          if (err.isError) {

              errorInfo(err);

              return;

          }

          successInfo(responseObject);

      } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

          NSLog(@"请求失败信息:%@",error);

          NSDictionary *errDic=error.userInfo;

          NSHTTPURLResponse *response=[errDic objectForKey:@"com.alamofire.serialization.response.error.response"];

          NSInteger errCode=response.statusCode;

          MDErrorInfo *err = [[MDErrorInfo alloc] initWithDefault];

          err.code = errCode;

          if (error.code == -1009) {

              err.msg = @"网络超时";

          }

          else if (err.code == 404){

              err.msg = @"网络页面不存在";

          }

     //我发出通知处理错误的方法在TPErrorHandle 文件里

          errorInfo([[TPErrorHandle alloc] initWithMDErrorInfo:err]);

      }];

  }

  ●这里有一个eventId字段,我之前说了app_id尽量每个版本更新时候换一个,这里就用了

  在 APP 更新一个星期后,我们会在后台把之前版本使用的 app_id 删除,用户如果在继续使用老版本进行数据请求是会出现以下报错:

  {

    "err":{

        "code":-99,

        "msg": "有新版本,请及时更新",

        "eventId":"返回的更新链接"

    }

  }

  根视图上添加的更新通知监听就会pop 出更新页面,这样极大地提高了新版本的更新率。

  这一套流程使用时移动端几乎不需要控制是否需要去登陆,所有的判断是否登录都抛给后台去判断,这种做法会浪费一部分服务器资源,但是只要前后台封装好一整套流程,开发速度会有很大提高的。

  接口的加密大概就这么些东西了,没有任何加密是无法破解的,我们要做的只是去增加破解的难度。这种加密方式肯定会有弊端,现阶段的我可能技术太烂,只能这么去总结一下,如果写的不好请见谅。

  最后想感慨一下关于接手别人代码的事,最近换了工作,公司自己的一个产品需要我接手开发迭代,这份代码从15年到现在经手过5个人,除了代码没有留下任何文档,每个人写代码的风格迥异。熟悉别人代码的过程是痛苦的,但是你在熟悉别人代码的过程中进步也是特别快的,不管别人代码写的好还是坏,每个人的代码中你总能找到亮点,找到你没有用过的技能。最近说的最多的话就是:我靠,还可以这样搞!!!我靠,又在搞事情!!!


顾翔凡言:

不懂人工智能的IT人员在五年后不可想象,python的作用是伟大的。

啄木鸟软件测试培训中心,2017年主打课:

各企业可进行裁剪

自动化软件测试课程(企业内训 24,000,公开课 2,000/人)

软件性能测试课程(企业内训 18,000,公开课 1,500/人)

WEB软件用户体验式测试课程(企业内训 12,000,公开课 1,000/人)

安卓APP自动化软件测试课程(企业内训 24,000,公开课 2,000/人)

问题引导的用户验收测试(UAT)课程(企业内训 12,000,公开课 1,000/人)

嵌入式软件测试培训课程(企业内训 18,000,公开课 1,500/人)

探索式软件测试课程(企业内训 12,000,公开课 1,000/人)

APP软件专项测试课程(企业内训 12,000,公开课 1,000/人)

WEB软件安全性测试课程(企业内训 15,000,公开课 1,200/人)

WEB软件测试课程(企业内训 12,000,公开课 1,000/人)

以项目为导向的敏捷课程方案(

两天课企业内训:¥12,000 公开课:¥1,000/

三天课企业内训:¥18,000公开课:¥1,500/

一周课企业内训:¥29,000公开课:¥5,000/

四周可企业内训:¥100,000公开课:¥1,0000/


以上是关于iOS 移动端接口加密流程(包含单点登录和失效时间)的主要内容,如果未能解决你的问题,请参考以下文章

移动端接口碰到了服务器无法解析ios端传回?

单点登陆TOKEN的处理

微信小程序登陆,后端接口实现 - springboot

api服务端接口安全性解析针对

PHP给移动端接口 [] 和 {} 的区别

使用Jmeter开发app端接口自动化案例实战