Android APP自动登录功能实现(以Retrofit+okHttp3请求框架)为例

前言

        在我们日常使用的各类软件中,自动登录是一个非常常见的功能,因为有许多功能是必须用户登录后(或者说需要用户信息)才能使用的(例如,收藏功能,查看个人信息功能等)。

实现原理

简而言之,通过持久化存储用户登录或注册成功后服务器端返回的用户名密码Cookie,并在下次访问需用户信息的接口时,拦截网络请求并将本地保存的用户账号密码cookie添加上去后再进行访问即可。

        我们先来了解一下注册功能的逻辑,首先用户在客户端输入用户名密码,进行注册,此时通过post请求向服务器端提交用户注册数据,若服务器端校验完毕无误,则会将用户名密码存储于服务器端,以便下次登录时进行验证,同时也会在cookie中返回账号密码告诉客户端注册成功。

        其次我们再来了解一下需要登录注册才能使用的功能的实现逻辑,以收藏功能为例,当用户点击收藏按钮后,app需要做的事情有:保存当前收藏的条目id作为参数,进行带用户账号密码Cookie的网络请求,通知服务器端当前用户要收藏此item。可以简单理解为,服务器端需要知道用户id和用户密码以及用户要收藏的数据id,才能将当前收藏的数据id挂在用户id下,标识此用户收藏了此数据。

        了解完上述操作的逻辑,实现自动登录功能的逻辑就显而易见了,在用户注册/登录成功后,通过Cookie拦截器拦截服务器端返回的cookie,解析并在本地进行持久化存储。每次进行网络请求时,通过头拦截器拦截请求的url,判断当前url是否需要用户信息cookie,若需要,则为其添加后再进行网络请求,则可实现自动登录功能了。

具体代码实现

创建OkHttpClient客户端对象并为其添加自定义的Cookie拦截器和Hearder拦截器

/**
 * 网络请求接口
 */
interface ApiInterface {
    companion object {
        private const val BASE_URL = "https://www.xxx.com"

        //使用懒加载
        val api: WanAndroidApiInterface by lazy { createApi() }

        /**
         * 通过Retrofit的动态代理生成ApiInterface实现类
         * @return WanAndroidApiInterface
         */
        private fun createApi(): WanAndroidApiInterface {
            val build = OkHttpClient.Builder()
            build.connectTimeout(Constant.CONNECT_TIME_OUT,TimeUnit.SECONDS)
            build.readTimeout(Constant.CONNECT_TIME_OUT,TimeUnit.SECONDS)
            build.writeTimeout(Constant.CONNECT_TIME_OUT,TimeUnit.SECONDS)
            build.retryOnConnectionFailure(true)
            //为OkHttp添加拦截器,以实现自动拦截存储Cookie和为需要Cookie的接口拦截添加Cookie
            build.addInterceptor(CookiesInterceptor())
            build.addInterceptor(HeaderInterceptor())
            val loggingInterceptor = HttpLoggingInterceptor { message: String ->
                LogUtils.i(
                    this,
                    message
                )
            }//日志拦截器
            build.addInterceptor(loggingInterceptor)
            val client = build.build()
            val retrofit = Retrofit.Builder().baseUrl(BASE_URL).client(client)
                .addConverterFactory(GsonConverterFactory.create()).build()
            return retrofit.create(WanAndroidApiInterface::class.java)
        }
    }


    /**
     * 收藏站内文章
     * @param collectId Int
     */
    @POST("/lg/collect/{id}/json")
    suspend fun collectArticle(@Path("id") collectId: Int): BaseResult?


    
}

Cookie拦截器:此拦截器的具体拦截规则需依据请求接口返回的Cookie自行添加拦截条件,因为有的接口即使登录失败也会返回默认的Cookie,即需要根据实际情况判断拦截存储下来的Cookie是否为用户信息Cookie!


class CookiesInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val newBuilder = request.newBuilder()

        val response = chain.proceed(newBuilder.build())
        val url = request.url.toString()
        //如果当前请求URL为登录或注册url,则拦截返回的Cookie
        if ((url.contains(USER_LOGIN_URL) || url.contains(USER_REGISTER_URL))) {
            val cookies = response.headers(SET_COOKIE)
            val cookiesStr = CookiesManager.encodeCookie(cookies)//解析Cookie
            CookiesManager.saveCookies(cookiesStr)//保存Cookie
            LogUtils.e(this@CookiesInterceptor,"CookiesInterceptor:cookies:$cookies")
        }
        //拦截退出登录请求
        if(url.contains(USER_LOGOUT_URL) && response.headers(SET_COOKIE).isNotEmpty()){
            //清除本地Cookie
            CookiesManager.clearCookies()
        }
        return response
    }
}

Header拦截器

class HeaderInterceptor:Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val newBuilder = request.newBuilder()
        newBuilder.addHeader("Content-type", "application/json; charset=utf-8")

        val host = request.url.host
        val url = request.url.toString()

        //给需要登录才能访问的接口添加Cookies
        if (host.isNotEmpty() && url.contains("/lg/collect")){
            val cookies = CookiesManager.getCookies()//获取之前保存的用户信息Cookie
            LogUtils.e(this,"HeaderInterceptor:cookies:$cookies")
            if (!cookies.isNullOrEmpty()) {
                newBuilder.addHeader(KEY_COOKIE, cookies)//添加Cookie
            }
        }
        return chain.proceed(newBuilder.build())
    }
}

Cookie管理类

/**
 * @description: Cookies管理类
 * @author YL Chen
 * @date 2025/2/25 18:23
 * @version 1.0
 */
object CookiesManager {

    /**
     * 解析Cookies
     * @param cookies
     */
    fun encodeCookie(cookies: List?): String {
        val sb = StringBuilder()
        val set = HashSet()
        cookies
            ?.map { cookie ->
                cookie.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            }
            ?.forEach { it ->
                it.filterNot { set.contains(it) }.forEach { set.add(it) }
            }
        LogUtils.e(this,"cookiesList:$cookies")
        val ite = set.iterator()
        while (ite.hasNext()) {
            val cookie = ite.next()
            sb.append(cookie).append(";")
        }
        val last = sb.lastIndexOf(";")
        if (sb.length - 1 == last) {
            sb.deleteCharAt(last)
        }
        return sb.toString()
    }
    /**
     * 保存Cookies
     * @param cookies
     */
    fun saveCookies(cookies: String) {
        val mmkv = MMKV.defaultMMKV()
        mmkv.encode(HTTP_COOKIES_INFO, cookies)
    }

    /**
     * 获取Cookies
     * @return cookies
     */
    fun getCookies(): String? {
        val mmkv = MMKV.defaultMMKV()
        LogUtils.d(this,"getCookies-->${mmkv.decodeString(HTTP_COOKIES_INFO, "")}")
        return mmkv.decodeString(HTTP_COOKIES_INFO, "")
    }


    /**
     * 清除Cookies
     */
    fun clearCookies() {
        saveCookies("")
    }
}

你可能感兴趣的:(android,kotlin,retrofit,okhttp)