ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android APP登录设备云FlexManager平台API接口的完整实践指南

Android APP登录设备云FlexManager平台API接口的完整实践指南 1. 项目起因为什么要把Android APP接到FlexManager设备云先说下背景。我手头有个物联网项目设备端用的是某品牌的工业网关数据上行的终点就是FlexManager设备云平台。这个平台主要做设备接入、数据存储和远程管理云端API的开放程度还不错但官方文档偏硬件侧面向“外部APP调用登录接口”的说明写得比较简略尤其Android这边的接入示例几乎没有。项目推进到一半客户提了个需求希望有一款专用的Android端管理APP登录验证走FlexManager的账号体系不做本地注册、不做第三方授权直接用设备云平台的账号密码换Token再拿着Token去做后续的设备状态查询和指令下发。标题里写的是“登录设备云FlexManager平台API接口”但实际要解决的是三件事APP端怎么调登录接口、Token怎么安全地存下来、后续业务请求怎么带着身份信息去访问。这一篇就把我们踩过的坑、最终的接口对接方案和Android端的落地写法一起整理出来。适合正在做设备云平台接入、或者准备给自己的APP对接统一登录服务的开发者参考。不管是自己搭云平台的API还是对接第三方设备云登录认证这块的套路基本都是相通的。标题里那堆热搜词我扫了一遍和这个项目真正相关的就是“API接口”“Android Studio开发APP”“免费API接口”“java开发API接口以供外部调用”这几个其他基本是干扰项。下面写的所有内容都以Android APP对接FlexManager平台登录API为主线不跑题。2. FlexManager平台登录API的设计逻辑与调用规则2.1 平台侧的认证机制AppId、AppSecret与AccessToken开始写代码之前建议先把FlexManager平台的API认证机制摸清楚。这平台走的不是传统的SessionCookie而是标准的Token认证体系。大体流程是这样开发者在FlexManager开放平台后台创建应用拿到一组AppId和AppSecret。APP端拿着AppId、AppSecret加账号密码请求登录接口换取AccessToken。后续所有业务接口都在请求头里带AccessToken平台根据Token识别用户身份。Token有有效期过期后需要用RefreshToken刷新避免用户频繁重新输密码。这里的AppId等同于你的应用身份证号AppSecret等同于密码这两个东西泄露了等于把整个应用的控制权交出去。所以AppSecret绝对不能硬编码在Android客户端里哪怕是混淆过的代码也可能被扒出来。后面我会单独讲怎么处理这个安全性的问题先说下API交互本身的细节。2.2 登录接口的请求格式与签名规则我们联调时用到的登录接口如下基于平台公开接口文档的通常设计具体字段名你可能需要按自己平台的实际文档调整POST https://api.flexmanager.cn/v1/auth/login Content-Type: application/json请求体{ appId: your_app_id, username: user_phone_or_name, password: md5(password) }这里有个细节密码不要明文传输。我们项目里服务端要求的是先对密码做MD5再传MD5值过去。如果你接的平台没有这条要求也建议至少做一次Hash再出网别让密码裸奔。除了登录参数本身Header里一般还要带时间戳防重放。平台侧会校验请求时间与服务器时间差超过5分钟直接拒绝。响应报文长这样{ code: 0, message: success, data: { accessToken: eyJhbGciOiJIUzI1NiIs..., refreshToken: refresh_token_value, expiresIn: 7200, tokenType: Bearer } }字段含义字段含义备注accessToken访问令牌请求业务接口时放入Authorization头refreshToken刷新令牌令牌过期时用于换取新的accessTokenexpiresIn有效期秒一般7200秒2小时tokenType令牌类型通常为Bearer拼到请求头里就是Bearer xxx2.3 签名校验到底是干什么的FlexManager平台比一般的登录接口多了一步签名校验。不是光拿到账号密码就行还要对请求参数做签名。当时没仔细看文档第一版直接把参数Post过去被平台连续拒绝了三次排查半天才发现少了签名逻辑。签名规则通常是将请求参数appId、username、password、timestamp按key值ASCII码升序排列拼接成 key1value1key2value2 格式用AppSecret作为密钥对拼接字符串做HMAC-SHA256运算生成的签名转十六进制字符串放到Header的 X-App-Sign 字段里。这段逻辑用Java写就是private static String generateSign(MapString, String params, String appSecret) { // 1. 按key排序 TreeMapString, String sortedParams new TreeMap(params); StringBuilder sb new StringBuilder(); for (Map.EntryString, String entry : sortedParams.entrySet()) { if (sb.length() 0) { sb.append(); } sb.append(entry.getKey()).append().append(entry.getValue()); } // 2. HMAC-SHA256 try { Mac mac Mac.getInstance(HmacSHA256); SecretKeySpec keySpec new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), HmacSHA256); mac.init(keySpec); byte[] raw mac.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8)); StringBuilder hexSb new StringBuilder(); for (byte b : raw) { String hex Integer.toHexString(0xFF b); if (hex.length() 1) { hexSb.append(0); } hexSb.append(hex); } return hexSb.toString(); } catch (Exception e) { e.printStackTrace(); return null; } }当时卡了很久的一个点是排序。我一直以为按字符串长度排结果是按ASCII码升序排。另外拼接时value要使用URL解码后的原始值如果value里边有中文或者特殊字符先做URLEncode再做签名两边结果一致才行。这是出bug概率最高的位置后面排查章节我会专门讲一个真实案例。3. Android端登录功能的技术选型与网络层搭建3.1 网络框架选型Retrofit还是OkHttp直接撸Android端我用的OkHttp Retrofit的组合。有人说就一个登录接口还用Retrofit是不是小题大做但我后面还要调设备列表、状态上报、远程指令下发这些接口提前把网络层框架搭好后面加接口只需要加一个Service方法的事没有必要每个接口都从Request写到Callback。依赖配置implementation com.squareup.okhttp3:okhttp:4.12.0 implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 implementation com.squareup.okhttp3:logging-interceptor:4.12.0特别提一下logging-interceptor联调阶段一定加上这是看接口报文最快的路径。我们在测试环境开着日志每个请求的URL、Header、Body、响应全打到Logcat里定位问题效率能提升不少。3.2 登录接口的Retrofit定义与调用封装接口定义public interface AuthApiService { POST(/v1/auth/login) CallLoginResponse login(Body LoginRequest request); }这里有个经验点如果你把签名参数放在Header里Retrofit的注解要用Header如果签名参数放在Body里就保持Body不动。我建议签名必须放在Header因为Body里的签名容易在日志里被打印出来。你自己调试一下就知道平台下发请求日志的时候Header里的信息不会主动展示出来相对安全。LoginRequest封装public class LoginRequest { private String appId; private String username; private String password; private long timestamp; public LoginRequest(String appId, String username, String password) { this.appId appId; this.username username; this.password MD5Util.md5(password); this.timestamp System.currentTimeMillis() / 1000; } }注意这里的timestamp在签名和Body里都要用到所以构建签名时不能重新取时间直接取对象里这个值。顺序错了或时间对不上签名就校验不过去。OkHttpClient的构建要加拦截器统一把签名头加到所有请求上。不要每个接口单独写一遍签名逻辑后面接口多了你会疯掉的。OkHttpClient client new OkHttpClient.Builder() .addInterceptor(new SignInterceptor(appId, appSecret)) .addInterceptor(new HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BODY)) .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .build();SignInterceptor的核心逻辑是拿到Request读取已有参数和当前时间计算签名并写入Header。public class SignInterceptor implements Interceptor { private final String appId; private final String appSecret; Override public Response intercept(Chain chain) throws IOException { Request original chain.request(); long timestamp System.currentTimeMillis() / 1000; MapString, String params new HashMap(); params.put(appId, appId); params.put(timestamp, String.valueOf(timestamp)); String sign SignUtil.generateSign(params, appSecret); Request signedRequest original.newBuilder() .header(X-App-Id, appId) .header(X-App-Sign, sign) .header(X-Timestamp, String.valueOf(timestamp)) .build(); return chain.proceed(signedRequest); } }3.3 AppSecret不落客户端的处理思路刚才说了AppSecret不能放客户端那签名拦截器里怎么拿AppSecret常规做法是让服务端加一个“临时签名凭证”接口APP启动时请求自己的后端服务后端带着AppSecret去FlexManager换一个短期有效的签名TokenAPP拿签名Token存内存里过期前再去换登录接口的签名用这个签名Token生成而不是用原始AppSecret。这样做以后客户端里永远只存在短期凭证即使被逆向泄露的也只是短时效的Token不至于把整个应用的AppSecret暴露掉。代价是多了一次后端交互多了一个接口要联调但对于商业项目来说这笔账是划算的。4. 登录与Token管理从换取令牌到业务接口对接4.1 登录成功后的Token存储策略登录成功拿到AccessToken后怎么存是个值得纠结的问题。SharedPreferences存取方便但明文存储等于把令牌送到逆向者嘴边。Android的Keystore/KeyStore可以做加密但KeyStore的使用有一个坑设备重启后有些机型上KeyStore的解密会有延迟或失败需要重新初始化。这就导致Token明明存在数据库里却解不开用户被强制重新登录。我们最终用的方案是三层Token的内存缓存APP运行期间只从内存读不每次冷启动都去翻存储持久化用SharedPreferences AES加密存储密钥放在Android Keystore里加密密钥每次启动时从KeyStore取首次创建后不重建除非密钥失效。加密工具封装public class TokenStorage { private SharedPreferences prefs; private static final String KEY_ALIAS flexmanager_token_key; public void saveToken(String accessToken, String refreshToken) { String encryptedToken AESUtil.encrypt(accessToken, getOrCreateKey()); String encryptedRefresh AESUtil.encrypt(refreshToken, getOrCreateKey()); prefs.edit() .putString(access_token, encryptedToken) .putString(refresh_token, encryptedRefresh) .putLong(token_expire_at, System.currentTimeMillis() 7200 * 1000) .apply(); } public String getAccessToken() { String encrypted prefs.getString(access_token, null); if (encrypted null) return null; return AESUtil.decrypt(encrypted, getOrCreateKey()); } }getOrCreateKey()这个方法内部逻辑是去KeyStore里查别名是否存在存在就取对应的SecretKey不存在就生成一个再存进去。代码不复杂但有一个细节要注意Keystore生成的SecretKey不一定支持AES建议用AES/GCM/NoPadding的加密模式记得把IV初始化向量存下来解密的时候要用。GCM模式在API 23以上是官方推荐我之前用CBC模式出现过部分机型上解密乱码的问题换成GCM后稳定很多。IV不保密直接放在加密结果前边拼成一个文件存起来就行。4.2 业务接口如何携带Token访问登录完成后设备列表接口请求头里要带AuthorizationAuthorization: Bearer eyJhbGciOiJIUzI1NiIs...Retrofit里的写法public interface DeviceApiService { GET(/v1/device/list) CallDeviceListResponse getDeviceList(Header(Authorization) String authorization); }调用处String token TokenStorage.getInstance().getAccessToken(); String authHeader Bearer token; deviceApiService.getDeviceList(authHeader).enqueue(...);这个写法直白但也有点繁琐。可以在OkHttp的Interceptor里统一给所有请求加Authorization头登录接口单独排除。这样做的好处是后续业务方法不用每个都传Header参数。统一加Header的Interceptorpublic class AuthInterceptor implements Interceptor { Override public Response intercept(Chain chain) throws IOException { Request original chain.request(); // 登录接口不需要带Authorization if (original.url().encodedPath().contains(/auth/login)) { return chain.proceed(original); } String token TokenStorage.getInstance().getAccessToken(); Request.Builder builder original.newBuilder(); if (token ! null) { builder.header(Authorization, Bearer token); } return chain.proceed(builder.build()); } }这个方案在接口多了以后优势很明显。新加的业务接口只需要定义注解和响应对象不用管鉴权拦截器自动把身份信息带过去。4.3 Token过期与RefreshToken刷新逻辑AccessToken有效期一般是2小时过期后如果再拿旧Token去调业务接口会收到401。这时候正确的做法是用RefreshToken去刷新而不是让用户重新登录。刷新接口一般长这样POST(/v1/auth/refresh) CallLoginResponse refresh(Body RefreshRequest request);RefreshRequest里带上refreshToken和应用标识即可。在我们项目里刷新逻辑是用一个Interceptor统一处理的业务请求发出响应码401拦截器捕获401暂停当前请求调刷新接口换新Token刷新成功保存新Token重放原请求刷新失败踢回登录页提示用户重新登录。这个逻辑说起来简单实现时要特别注意并发问题。如果同时有5个业务请求都收到401然后5个请求同时去刷新Token平台可能处理不过来甚至让你短时间内重复刷新、导致RefreshToken失效。所以要加一个信号量锁同一时间只允许一个刷新任务在执行其他等待的请求在刷新完成后直接复用新Token。public class TokenRefresher { private Semaphore refreshLock new Semaphore(1); private volatile boolean isRefreshing false; public synchronized void refreshIfNeeded() { if (isRefreshing) return; isRefreshing true; try { // 调刷新接口 LoginResponse resp authApiService.refresh(...).execute().body(); if (resp ! null resp.getCode() 0) { TokenStorage.getInstance().saveToken( resp.getData().getAccessToken(), resp.getData().getRefreshToken() ); } } finally { isRefreshing false; refreshLock.release(); } } }做了这个之后并发刷新的问题基本没有了。顺便说一句RefreshToken的有效期一般比AccessToken长很多有些平台是7天有些是30天。RefreshToken一旦失效就只能重新走用户名密码登录。5. 联调实测中的坑完整排查链路与解决方案5.1 签名一直校验失败时间偏差惹的祸第一轮联调签名逻辑照着文档写了一遍自测没毛病一到真机就报签名失败。把日志拉出来看请求头里的timestamp和服务端时间差了将近40秒。查了下发现是手机时间不准手动校时后立刻就好了但这问题不能指望用户手动校时最后统一改成APP每次启动时先调一下平台的“时间同步接口”拿服务器时间戳作为后续签名的基准客户端本地时间只是用来计算差值签名用的时间戳全部基于服务器时间偏移量。那段时间偏移量会在请求时加回去private long serverTimeOffset 0; // 启动时调时间接口后设置 public void setServerTimeOffset(long serverTs, long localTs) { serverTimeOffset serverTs - localTs; } public long getSignTimestamp() { return System.currentTimeMillis() / 1000 serverTimeOffset; }别小看这个小改动它把问题从“每次重启要校时”降到了“只要成功同步过一次时间就基本不再出错”。后来我们线上环境跑了一个多月没有再出现过因为时间偏差导致的签名失败。5.2 请求参数顺序不同也能导致签名对不上另一个坑比较隐蔽。服务端要求的排序是“参数名按ASCII码升序”但我们有个字段叫userId有个字段叫userName排序的时候userId一定排在userName前面因为I的ASCII码是73N是78。但我们的C服务端同事习惯性地按字典序的“自然顺序”排——他用的std::map std::string,string 默认是按字典序排理论上ASCII序也是这个顺序但问题出在他把sign本身也加进了排序集合。服务端校验签名的时候把sign字段也拿来参与拼接然后比对自己算出来的sign结果永远对不上。排查了一整天最后是两边把参与签名的字段清单逐字对比才发现多了sign自己。解决方案很简单参与签名的参数列表显式指定只包含appId、timestamp和业务参数sign值本身永不参与计算。这也是一个经验签名算法里用的字段集合必须是封闭的、明确写死在代码里的后续加参数时要同步审视签名规则。5.3 Android高版本明文流量被拦Android 9API 28以后默认禁止明文HTTP流量。FlexManager平台正式环境是HTTPS但测试环境用的HTTP域名一调就抛异常CLEARTEXT communication to api.test.flexmanager.cn not permitted by network security policy解决方案有三条路调试用的测试包单独加networkSecurityConfig允许特定域名走明文用debug-overrides只允许debug包类型用明文直接把所有测试环境的请求代理到PC上的Charles做抓包手机不直连测试环境。我们用的是方案1具体做法是在AndroidManifest里给application加networkSecurityConfig属性然后新建network_security_config.xmlnetwork-security-config domain-config cleartextTrafficPermittedtrue domain includeSubdomainstrueapi.test.flexmanager.cn/domain /domain-config /network-security-config这个配置只对测试域名放开明文线上HTTPS域名不受影响。发布的时候记得确认release包不引用这个测试配置或者直接在release的res目录下放一个不允许明文流量的同名配置覆盖掉避免漏改。5.4 登录成功后快速跳转导致Token未落地有个偶现问题登录请求成功后立刻点击“进入设备列表”偶尔会出现列表页带过去的是空Token提示未授权。定位后发现是异步回调的问题。登录接口的onResponse在子线程我们在回调里先saveToken再跳转但跳转动作被写在了UI线程的某个后续事件里时序上出现了“还没save完就开始拿Token”的窗口期。修法很简单saveToken完成后再回调UI线程跳转不搞竞态。如果token保存失败直接提示“状态异常请重试”不要带着半路状态进页面。这个看起来是小问题但在弱网环境下出现的频率不低后台统计里“登录成功但秒退/报401”的会话基本都是这个原因导致的。5.5 弱网环境下的超时与重试FlexManager平台API的服务端平均响应时间还行但我们跑过一段室外弱网测试4G信号波动的时候经常出现SocketTimeoutException。一开始设置的connectTimeout10秒readTimeout30秒实际弱网下根本不够用后来调到connectTimeout20秒、readTimeout60秒才算稳定。超时时间调到这么大也有副作用如果API挂了用户就一直转圈。所以还需要加一个失败重试策略ConnectException和SocketTimeoutException可以做一次重试但401、403、业务错误code不等于0的情况不做重试。重试间隔用500ms最多重试2次。这些逻辑放在OkHttp的RetryInterceptor里统一处理不污染业务代码。6. Android端登录界面的状态管理与异常兜底6.1 登录页的整体交互流程设计一个完整的登录页不只是“用户名、密码、登录按钮”三件套。我们经历过测试阶段反复的反馈逐步把流程补成了下面这个样子进入登录页先检查本地是否已有Token有Token且未过期直接跳转主页面没有Token或Token已过期但RefreshToken还有效静默刷新刷新成功进主页失败留在登录页用户手动输入账号密码点击登录按钮置灰显示加载状态登录成功Token落地跳转设备列表页登录失败根据错误码给出对应提示文案而不是笼统的“登录失败”。这些流程说起来简单代码里涉及异步状态很多建议用一个LoginViewModel统一管理UI状态不要直接在Activity里写业务逻辑。等测试阶段出问题时就会感谢当时的这个决定——你能很方便地复现和定位问题是发生在网络层、存储层还是界面刷新逻辑上。6.2 状态机与错误提示映射FlexManager登录接口的错误码不算多但每个错误码对应的用户提示不能含糊。我们整理了这样一张映射表错误码含义用户提示1001账号不存在该账号未注册请检查输入1002密码错误密码错误请重新输入1003账号被锁定账号已被锁定请联系管理员1004AppId无效应用不存在或已停用请联系开发者1005签名校验失败请求校验失败请稍后重试1006请求过期请求已过期请重新尝试2001服务端内部错误服务繁忙请稍后重试这里特别强调一下1005和1006。这两个错误本质上是客户端加密逻辑和时间戳的问题提示给用户“请求校验失败”其实没有帮助用户根本不知道你在说什么。但我们没有把真正的签名细节暴露给用户而是统一提示“请求校验失败请稍后重试”同时埋点上报到后台让开发侧去查。面向用户的文案可以掩盖技术细节但一定要准确、不误导。6.3 登录后的页面跳转与账号切换登录完成后跳转设备列表页这个逻辑大家都懂但有一个容易被忽略的点账号切换时的数据清理。我们的APP里有“退出登录”按钮退出时除了清掉Token还要清空本地缓存的所有设备数据、用户配置项、搜索历史。不然同一个人换成另一个账号登录看到上一任账号的设备列表客户的隐私安全就出问题了。清数据我建议用这种写法public class LogoutManager { public static void logout(Context context) { // 1. 清Token TokenStorage.getInstance().clear(); // 2. 清本地数据库 DeviceDatabase.getInstance(context).clearAllTables(); // 3. 清SharedPreferences中的业务缓存保留基础配置 SharedPreferences sp context.getSharedPreferences(flex_cache, Context.MODE_PRIVATE); sp.edit().clear().apply(); // 4. 跳回登录页清空Activity栈 Intent intent new Intent(context, LoginActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent); } }如果平台侧的RefreshToken在退出时没失效那退出后用抓包软件把旧Token掏出来仍然能调业务接口。有条件的项目应该在退出时调一下FlexManager的“注销令牌”接口主动把RefreshToken吊销而不是只清本地。我们上线前补了这个逻辑运营那边反馈非常好——他们在公网环境下做过一次模拟泄露测试老Token在退出后立刻被服务端拒绝这给了客户很大信心。7. 从功能上线到稳定运行经验总结与后续演进7.1 线上运行后的监控指标上线后的前两周我一直在盯几个关键指标登录接口成功率目标99%以上低于95%就要查是不是接口挂了或者签名逻辑有改动Token刷新成功率低于90%说明RefreshToken的分发或存储可能有问题例如设备上时间偏差过大导致刷新请求也被拒401频率某时间段出现401集中可能是AccessToken过期时间设置过短或者RefreshToken没生效直接让用户断登崩溃率与ANR登录页和Token的读写涉及IO与加密性能差的机型上容易出现卡顿。当时有一次登录成功率突然降到91%查了半天发现是服务端凌晨更新时重启了一批实例部分请求落在了还没完全启动的节点上返回了503而不是正常的错误码。客户端把503当成网络异常做了重试重试还是503用户就看到了“登录失败”。后来我在RetryInterceptor里加了规则503做一次延迟重试再失败就提示“服务升级中请稍后再试”而不是干巴巴的失败提示。7.2 后续功能扩展思路单点登录与多端互踢FlexManager平台的登录API在基础的账号密码登录之外还支持部分扩展能力。我们在项目规划阶段留了些扩展位虽然没有全部落地但值得记录一下思路单点登录SSO扩展让FlexManager作为统一身份源APP、Web管理端、大屏看板共用一套账号体系。实现方式是在Token中嵌入用户角色和设备权限信息业务侧通过解析Token判断用户是否有权操作某台设备。多端互踢如果同一账号在两个设备上登录服务端可选择后登录的踢掉先登录的或者先登录的保持在线、新登录的弹个提示。这个一般通过处理Token生成的序号完成服务端看到同账号更新鲜的Token会主动吊销旧Token。做这个之前要先想清楚业务上是允许“一人在多端同时在线”还是“只能一端在线”不要随意踢人。多因子认证FlexManager登录接口里如果要支持多因子认证一般做法是登录时传递一个otpCode字段服务端校验通过后才发Token。我们当时因为项目排期没有上这个能力但如果你接的行业客户对安全等级有硬性要求这个扩展点应该考虑进去。7.3 开发效率与工程质量的两点体会整个对接过程走下来我个人的两个体会特别深第一接口文档一定要先读透再动手。一开始登录接口我们只看了请求和响应字段没注意签名校验和时间戳的要求结果联调阶段整整多花了两天去调试签名失败。建议拿到文档后先把鉴权流程、签名算法、错误码表这三个部分完整阅读一遍再开始写网络层代码。很多平台的文档都会给一个Postman示例集合可以直接导入先用Postman把登录流程跑通再回Android端写代码这个顺序是最稳的。第二Token存储和刷新逻辑值得多花时间做扎实。这部分虽然不直接面向用户但一旦出了问题表现是间歇性的登录跳闪、列表页数据加载不出来、用户反复被踢回登录页。这类问题的定位成本比开发成本高得多。我们的Token存储从明文改成Keystore加密、刷新逻辑从写死在Activity里改成拦截器统一处理之后稳定性明显上了一个台阶。7.4 针对后来者的一份快速核对清单如果你也要接FlexManager平台的登录API以下几点建议直接做成checklist确认AppId与AppSecret的来源开启服务端权限时注意别把密钥分享到代码托管平台写一个独立的后端代理或中转服务来存AppSecret客户端只拿短期签名凭证签名算法里的参数集合必须和平台文档一致sign本身不参与签名timestamp用服务器时间密码出网前至少做一次MD5有更高安全要求的项目建议加盐后做SHA256Token存储用Keystore加密禁止明文放在SharedPreferences或数据库里统一用拦截器处理Authorization头、签名、401刷新不要在业务代码里到处写弱网环境超时时间放宽到20秒以上配合一次重试机制退出登录时主动调用平台注销接口吊销RefreshToken而不只是清本地这份清单是拿实际踩坑换来的按着走能省掉至少两天的联调时间和大半的线上异常排查时间。登录对接本身不算复杂但每一步都有细节坑核心原则就一句话和平台约定好的东西严格照做客户端多做一层兜底错误处理统一收口。用这套思路去接基本不会出大岔子。我在实际项目中最后的体会是接口对接的价值不只是把“登录”这个功能做完而是通过梳理鉴权流程、Token生命周期和异常处理把客户端整个网络层和安全底子打结实了。这一套东西后续接任何新的业务接口都会特别顺手。希望这篇文字能帮你少走几步弯路。
返回列表