preface

This article will introduce the general idea of logging in to Demo by scanning code based on SpringBoot + Vue + Android. The complete code has been uploaded to GitHub. Web side experience address: http://47.116.72.33/ (only one month validity), APK download address: github.com/zhangjiwei1… . User name: not empty, password: 123456, the effect is at the end of the article, if there is any problem with the overall implementation, welcome to exchange and discuss, the realization of part of the two-dimensional code scan code login is what principle.

Project introduction

Back end: SpringBoot, Redis.

Front-end: Vue, Vue Router, VueX, Axios, VUE-QR, ElemntUI.

Android: ZXing, XUI, YHttp.

Implementation approach

The overall sweep login and OAuth2.0 authentication logic are similar as follows:

When the user selects code sweep for login, it can be regarded as A: the front end sends authorization request and waits for the APP to scan code.

The scanning of the app by the user can be regarded as B: scanning for authorization and returning a temporary Token for secondary authentication.

User login confirmation in APP can be regarded as C: login confirmation, and authorized user login in Web.

Step D is when the backend returns an official Token after the user confirms login.

Subsequently, the front-end accesses the background interface based on the formal Token, and the formal operations on the Web end can be regarded as E and F.

Reasons for secondary authentication

The reason after users scan code also need to confirm the login again rather than just log in reason, it is for user safety consideration, avoid the user swept others need to log in to the qr code, without confirmation will directly logged in, may lead to others in situations where we don’t know to visit our information.

Implementation steps

  1. When a user accesses the web page, he or she selects scan code to log in

    The user will send a QR code generation request to the back end when scanning for login. The back end generates a UUID and saves it to Redis (fixed validity time). The STATUS is UNUSED. And set a timer, every once in a while according to the UUID in the content of the TWO-DIMENSIONAL code, send a request to the back end, obtain the status of the two-dimensional code, update the content displayed on the interface.

    Generate the back-end interface of two-dimensional code:

    /** * Generate the content of the QR code **@returnResults the * /
    @GetMapping("/generate")
    public BaseResult generate(a) {
        String code = IdUtil.simpleUUID();
        redisCache.setCacheObject(code, CodeUtils.getUnusedCodeInfo(), 
                                  DEFAULT_QR_EXPIRE_SECONDS, TimeUnit.SECONDS);
        return BaseResult.success(GENERATE_SUCCESS, code);
    }
    Copy the code

    The front-end obtains the content and generates the TWO-DIMENSIONAL code:

    getToken() {
        this.codeStatus = 'EMPTY'
        this.tip = 'Obtaining login code, please wait.'
        // Valid time is 60 seconds
        this.effectiveSeconds = 60
        clearInterval(this.timer)
        request({
            method: 'get'.url: '/code/generate'
        }).then((response) = > {
            // The request is successful. Set the content of the QR code and update the relevant information
            this.code = `${HOST}/code/scan? code=${response.data}`
            this.codeStatus = 'UNUSED'
            this.tip = 'Please use mobile phone scan to log in'
            this.timer = setInterval(this.getTokenInfo, 2000)
        }).catch(() = > {
            this.getToken()
        })
    }
    Copy the code

    Back-end interface for returning status information of two-dimensional code:

    /** * Obtain the status information of the QR code **@paramCode:@returnResults the * /
    @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);
    }
    Copy the code

    Front-end polling to obtain two-dimensional code status:

    getTokenInfo() {
        this.effectiveSeconds--
        // The qr code has expired
        if (this.effectiveSeconds <= 0) {
            this.codeStatus = 'EXPIRE'
            this.tip = 'Qr code has expired, please refresh'
            return
        }
        // Query the status of the QR code
        request({
            method: 'get'.url: '/code/info'.params: {
                code: this.code.substr(this.code.indexOf('=') + 1)
            }
        }).then(response= > {
            const codeVO = response.data
            // The qr code has expired
            if(! codeVO || ! codeVO.codeStatus) {this.codeStatus = 'EXPIRE'
                this.tip = 'Qr code has expired, please refresh'
                return
            }
            // The status of the QR code is login
            if (codeVO.codeStatus === 'CONFIRMING') {
                this.username = codeVO.username
                this.avatar = codeVO.avatar
                this.codeStatus = 'CONFIRMING'
                this.tip = 'Scan code successfully, please confirm on your mobile phone'
                return
            }
            // The status of the QR code is confirm login
            if (codeVO.codeStatus === 'CONFIRMED') {
                clearInterval(this.timer)
                const token = codeVO.token
                store.commit('setToken', token)
                this.$router.push('/home')
                Message.success('Login successful')
                return}})}Copy the code
  2. Scanning code with mobile phone changes the status of the TWO-DIMENSIONAL code

    When a user uses a mobile phone code scan (the user has logged in to the correct APP; otherwise, the scan will jump to the customized promotional page), it will update the status of the TWO-DIMENSIONAL code to CONFIRMING (to be confirmed), and add the user name and profile picture information to the Redis cache for front-end display. In addition, it will return the user’s login information (login address, browser, operating system) to the APP for display, and generate a temporary Token to the APP (fixed validity period).

    Background processing when users scan codes:

    /** * Process the qr code in the unused state **@paramCode:@param token token
     * @returnResults the * /
    private BaseResult handleUnusedQr(String code, String token) {
        // Verify the token passed through app access
        boolean isLegal = JwtUtils.verify(token);
        if(! isLegal) {return BaseResult.error(AUTHENTICATION_FAILED);
        }
        // Save the user name and profile picture for display
        String username = JwtUtils.getUsername(token);
        CodeVO codeVO = CodeUtils.getConfirmingCodeInfo(username, DEFAULT_AVATAR_URL);
        redisCache.setCacheObject(code, codeVO, DEFAULT_QR_EXPIRE_SECONDS, TimeUnit.SECONDS);
        // Return the login address, browser, operating system, and a temporary token to app
        String address = HttpUtils.getRealAddressByIp();
        String browser = HttpUtils.getBrowserName();
        String os = HttpUtils.getOsName();
        String tmpToken = JwtUtils.sign(username);
        // Store the user name content in Redis with the temporary token as the key
        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);
    }
    Copy the code
  3. Confirm login by mobile phone

    When the user clicks “confirm login” in the APP, he/she will send a request to update the status with the generated temporary Token. The status of the TWO-DIMENSIONAL code will be updated to “CONFIRMED login”. At the same time, the back-end will generate a formal Token and store it in Redis. Then use this Token to log in.

    The backend processing confirms the login code:

    /** * Process the qr code with unconfirmed status **@paramCode:@param token token
     * @returnResults the * /
    private BaseResult handleConfirmingQr(String code, String token) {
        // Get the user name using the temporary token and delete the temporary token from redis
        String username = redisCache.getCacheObject(token);
        if (StringUtils.isBlank(username)) {
            return BaseResult.error(AUTHENTICATION_FAILED);
        }
        redisCache.deleteObject(token);
        // Generate a formal token based on the user name and save it in Redis for use by the front end
        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);
    }
    Copy the code

Results demonstrate