目录

    完整代码已上传到github。

    web端体验地址:http://47.116.72.33/(只剩一个月有效期)

    apk下载地址:。

    用户名:非空即可,密码:123456,效果见文末,整体实现如有不妥之处,欢迎交流讨论

    实现部分参考。

    项目简介

    后端:springbootredis
    前端:vuevue routervuexaxiosvue-qrelemntui
    安卓:zxingxuiyhttp

    实现思路

    总体的扫码登录和oauth2.0的验证逻辑相似,如下所示:

    用户选择扫码登录可以看作是a:前端发授权请求,等待app扫码。
    用户使用app进行扫码可以看作是b:扫码进行授权,返回一个临时token供二次认证。
    用户在app进行确认登录可以看作是c:进行登录确认,授权用户在web端登录。
    后端在用户确认登录后返回一个正式token即可看作是步骤d
    后续前端根据正式token访问后台接口,正式在web端进行操作即可看作是ef

    二次认证的原因

    之所以在用户扫码之后还需要进行再一次的确认登录,而不是直接就登录的原因,则是为了用户安全考虑,避免用户扫了其他人需要登录的二维码,在未经确认就直接登录了,导致他人可能会在我们不知道的情况下访问我们的信息。

    实现步骤

    用户访问网页端,选择扫码登录

    用户在选择扫码登录时,会向后端发送一个二维码的生成请求,后端生成uuid,并保存到redis(固定有效时间),状态设置为unused(未使用)状态,如果redis缓存过期,则为expire(过期)状态,前端根据后端返回的内容生成二维码,并设置一个定时器,每隔一段时间根据二维码的内容中的uuid,向后端发送请求,获取二维码的状态,更新界面展示的内容。

    生成二维码后端接口:

    /**
     * 生成二维码内容
     *
     * @return 结果
     */
    @getmapping("/generate")
    public baseresult generate() {
        string code = idutil.simpleuuid();
        rediscache.setcacheobject(code, codeutils.getunusedcodeinfo(), 
                                  default_qr_expire_seconds, timeunit.seconds);
        return baseresult.success(generate_success, code);
    }
    

    前端获取内容,生成二维码:

    gettoken() {
        this.codestatus = 'empty'
        this.tip = '正在获取登录码,请稍等'
        // 有效时间 60 秒
        this.effectiveseconds = 60
        clearinterval(this.timer)
        request({
            method: 'get',
            url: '/code/generate'
        }).then((response) => {
            // 请求成功, 设置二维码内容, 并更新相关信息
            this.code = `${host}/code/scan?code=${response.data}`
            this.codestatus = 'unused'
            this.tip = '请使用手机扫码登录'
            this.timer = setinterval(this.gettokeninfo, 2000)
        }).catch(() => {
            this.gettoken()
        })
    }
    

    后端返回二维码状态信息的接口:

    /**
     * 获取二维码状态信息
     *
     * @param code 二维码
     * @return 结果
     */
    @getmapping("/info")
    public baseresult info(string code) {
        codevo codevo = rediscache.getcacheobject(code);
        if (codevo == null) {
            return baseresult.success(invalid_code, stringutils.empty);
        }
        return baseresult.success(get_success, codevo);
    }
    

    前端轮询获取二维码状态:

    gettokeninfo() {
        this.effectiveseconds--
        // 二维码过期
        if (this.effectiveseconds <= 0) {
            this.codestatus = 'expire'
            this.tip = '二维码已过期,请刷新'
            return
        }
        // 轮询查询二维码状态
        request({
            method: 'get',
            url: '/code/info',
            params: {
                code: this.code.substr(this.code.indexof('=') + 1)
            }
        }).then(response => {
            const codevo = response.data
            // 二维码过期
            if (!codevo || !codevo.codestatus) {
                this.codestatus = 'expire'
                this.tip = '二维码已过期,请刷新'
                return
            }
            // 二维码状态为为正在登录
            if (codevo.codestatus === 'confirming') {
                this.username = codevo.username
                this.avatar = codevo.avatar
                this.codestatus = 'confirming'
                this.tip = '扫码成功,请在手机上确认'
                return
            }
            // 二维码状态为确认登录
            if (codevo.codestatus === 'confirmed') {
                clearinterval(this.timer)
                const token = codevo.token
                store.commit('settoken', token)
                this.$router.push('/home')
                message.success('登录成功')
                return
            }
        })
    }
    

    使用手机扫码,二维码状态改变

    当用户使用手机扫码时(已登录并且为正确的app,否则扫码会跳转到自定义的宣传页),会更新二维码的状态为confirming(待确认)状态,并在redis缓存中新增用户名及头像信息的保存供前端使用展示,此外还会返回用户的登录信息(登录地址、浏览器、操作系统)给app展示,同时生成一个临时tokenapp(固定有效时间)。

    用户扫码时的后台处理:

    /**
     * 处理未使用状态的二维码
     *
     * @param code 二维码
     * @param token token
     * @return 结果
     */
    private baseresult handleunusedqr(string code, string token) {
        // 校验 app 端访问传递的 token
        boolean islegal = jwtutils.verify(token);
        if (!islegal) {
            return baseresult.error(authentication_failed);
        }
        // 保存用户名、头像信息, 供前端展示
        string username = jwtutils.getusername(token);
        codevo codevo = codeutils.getconfirmingcodeinfo(username, default_avatar_url);
        rediscache.setcacheobject(code, codevo, default_qr_expire_seconds, timeunit.seconds);
        // 返回登录地址、浏览器、操作系统以及一个临时 token 给 app
        string address = httputils.getrealaddressbyip();
        string browser = httputils.getbrowsername();
        string os = httputils.getosname();
        string tmptoken = jwtutils.sign(username);
        // 将临时 token 作为键, 用户名为内容存储在 redis 中
        rediscache.setcacheobject(tmptoken, username, default_temp_token_expire_minutes, timeunit.minutes);
        logininfovo logininfovo = new logininfovo(address, browser, os, tmptoken);
        return baseresult.success(scan_success, logininfovo);
    }
    

    手机确认登录

    当用户在app中点击确认登录时,就会携带生成的临时token发送更新状态的请求,二维码的状态会被更新为confirmed(已确认登录)状态,同时后端会生成一个正式token保存在redis中,前端在轮询更新状态时获取这个token,然后使用这个token进行登录。

    后端处理确认登录的代码:

    /**
     * 处理未待确认状态的二维码
     *
     * @param code 二维码
     * @param token token
     * @return 结果
     */
    private baseresult handleconfirmingqr(string code, string token) {
        // 使用临时 token 获取用户名, 并从 redis 中删除临时 token
        string username = rediscache.getcacheobject(token);
        if (stringutils.isblank(username)) {
            return baseresult.error(authentication_failed);
        }
        rediscache.deleteobject(token);
        // 根据用户名生成正式 token并保存在 redis 中供前端使用
        string formaltoken = jwtutils.sign(username);
        codevo codevo = codeutils.getconfirmedcodeinfo(username, default_avatar_url, formaltoken);
        rediscache.setcacheobject(code, codevo, default_qr_expire_seconds, timeunit.seconds);
        return baseresult.success(confirm_success);
    }
    

    效果演示

    以上就是java编程基于springboot框架实现扫码登录的详细内容,更多关于java编程springboot框架实现扫码登录的资料请关注www.887551.com其它相关文章!