概述
客户端中有些数据是需要登录后才能查看的,这时候就需要在接口中加入凭证,如Cookie 或Token。我们这里主要说下Token。更多关于身份验证的详细情况请见参考链接。
身份验证的几种方案
典型的用户身份验证标准(方案):
HTTP BASIC Authentication
HTTP Digest Authentication
Form-based Authentication
Token Based Authentication
X.509 Certificate Authentication
Token/Session/Cookie的区别
其中Token身份验证如下:
1)客户端使用账号密码请求登录
2)服务端收到请求,验证账号密码
3)验证通过,服务端签发一个token给客户端
4)客户端收到token存储起来(例:存在cookie)
5)客户端每次请求服务端时带着服务端签发的token
6)服务端收到请求,验证token。验证成功则返回数据给客户端。
具体实践
认证逻辑
- 客户端在登录页面执行登录请求,服务端通过后返回Token
- 客户端本地存储Token,并且在需要身份认证的接口中添加Token(一般放在header里)
- 服务端对每个Token赋予一个有效期,过了有效期之后,返回给客户端一个特殊的错误码,让其重新登录或者其他的方式刷新Token(如通过RefreshToken刷新Token)
注意: 以下代码使用了Retrofit和Rxjava框架
登录接口
- 执行加盐请求。利用盐加密密码,盐和密码拼接后使用SHA512加密。
- 执行登录请求。
1 2 3 4 5 6 7 8 9 10 11 12 13
| fun loginWithSalt(mobile: String, password: String): Flowable<ApiResponse<TokenModel?>> { return BossService.api() .getSalt() .flatMap { response -> val enPassword = PasswordEncryptHelper.getWhenLogin(password, response.data!!.salt) val map = hashMapOf( Pair("mobile", mobile), Pair("saltKey", response.data.saltkey), Pair("password", enPassword)) return@flatMap BossService.api().login(map) } .compose(apply()) }
|
登录操作及更新Token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| private fun attemptLogin(mobile:String, password:String) { NetApi.loginWithSalt(mobile, password) .subscribe({ data -> loginSuccess(data.data!!, mobile) }, { t -> loginFail(t, mobile) }) }
private fun loginSuccess(any: TokenModel, mobile: String) { DataManager.updateCurrentUser(mobile, UserModel(mobile)) DataManager.currentUserSettings()!!.setToken(any.token) DataManager.currentUserSettings()!!.setRefreshToken(any.refreshToken) }
class TokenModel(val token: String, val refreshToken: String) : BaseModel()
|
需要身份认证的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @GET("users/v1/baseInfo") fun getUserBaseInfo(@Header("X-Authorization") token: String ): Flowable<ApiResponse<UserModel>>
fun getUserBaseInfo(): Flowable<ApiResponse<UserModel?>> { return BossService.api() .getUserBaseInfo(token()) .compose(apply()) }
private fun token(): String { val currentUser = DataManager.currentUserSettings() val token = currentUser?.getToken() ?: "" return "Bearer ".plus(token) }
|
Token过期处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| private fun fetchRemoteData(): Disposable { val loading = LoadingHelper.create(context!!) return NetApi.getUserBaseInfo() .compose(RxTransformers.applyLoading(loading)) .subscribe({ user -> }, { t -> if (!NetErrorHelper.handleAuth(t, activity!!)) { Util.toast().showShort("fail:${t.message}") } }) }
object NetErrorHelper {
fun handleAuth(t: Throwable, activity: Activity): Boolean { if (t !is ApiException) { return false } when (t.code) { ApiCode.ERROR_JWT_TOKEN_EXPIRED.code -> { Util.dialog().warn(activity) .content(t.message ?: ApiCode.ERROR_AUTHENTICATION_FAILED.description) .positiveText(R.string.common_confirm) .onPositive { _, _ -> ARouter.getInstance().build(RouteTable.LOGIN).navigation() activity.finish() } .cancelable(false) .show() return true } else -> { } } return false } }
|
参考
身份验证的几种方案
Token/Session/Cookie的区别