扫码登录的简单实现-服务器端

服务器端使用springboot框架。

一、需要添加的pom依赖

本实例没有什么特殊的依赖。

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

二、yml配置文件。

只是端口设置。

server:
  port: 7021

三、跨域设置文件CorsConfig.java

package com.chris.sl.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * 跨域访问设置
 */
@Configuration
public class CorsConfig {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*"); // 1允许任何域名使用
        corsConfiguration.addAllowedHeader("*"); // 2允许任何头
        corsConfiguration.addAllowedMethod("*"); // 3允许任何方法(post、get等) 
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig()); // 4  
        return new CorsFilter(source);
    }
} 

四、需要用到的实例类

1. 用于传输登录信息的LoginUser.java

package com.chris.sl.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 11:31
 * use for: 登录用户
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser {
    private String username;
    private String password;
}

2. 用于存放扫码登录信息的LoginInfo.java

package com.chris.sl.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 11:34
 * use for: 登录信息
 * 包括登录用户名和识别码过期时间
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginInfo {
    private String username;
    private long expire;
    //是否已被占用
    private boolean occ;
}

3. 用于给前端包装返回识别码的ScanCode.java

package com.chris.sl.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 13:13
 * use for:
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ScanCode {
    private String code;
}

五、数据管理器DataManager.java,类似于数据库的管理。本文使用内存操作。实际业务可能需要操作redis等数据库,改变一下各个方法的具体实现即可。

package com.chris.sl.manager;

import com.chris.sl.model.LoginInfo;
import com.chris.sl.model.LoginUser;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 11:28
 * use for: 数据管理器
 * 因为目的只是为了走通逻辑,为方便移植,此处不用外部数据库,用内存数据模拟数据库
 * 如果改用mysql、redis,只需要改变方法的实现
 */
public class DataManager {
    //用户信息
    public static Map<String, LoginUser> userMap = new HashMap<>(16);
    //登录识别码池
    public static Map<String, LoginInfo> loginInfoMap = new HashMap<>(16);

    /**
     * 初始化
     */
    public static void init() {
        userMap.put("kaly", new LoginUser("kaly", "123456"));
        userMap.put("chris", new LoginUser("chris", "123456"));
        userMap.put("will", new LoginUser("will", "123456"));
        userMap.put("chenfabao", new LoginUser("chenfabao", "123456"));
    }

    /**
     * 添加一个登录信息
     *
     * @param code
     */
    public static void addLoginInfo(String code) {
        //过期时间设置为5分钟
        long exp = LocalDateTime.now().plusMinutes(5).toInstant(ZoneOffset.of("+8")).toEpochMilli();
        loginInfoMap.put(code, new LoginInfo(null, exp, false));
    }

    /**
     * 占用登录信息
     *
     * @param code
     * @param username
     */
    public static void occLoginInfo(String code, String username) {
        LoginInfo loginInfo = loginInfoMap.get(code);
        if (null == loginInfo) {
            return;
        }
        loginInfo.setUsername(username);
        loginInfo.setOcc(true);
    }

    /**
     * 验证用户
     *
     * @param username
     * @param password
     * @param code
     * @return
     */
    public static boolean verification(String username, String password, String code) {
        LoginUser loginUser = userMap.get(username);
        //检查用户是否存在
        if (loginUser == null) {
            return false;
        }
        //检查密码是否正确
        if (!password.equalsIgnoreCase(loginUser.getPassword())) {
            return false;
        }
        //检查二维码识别码是否存在或过期
        LoginInfo loginInfo = loginInfoMap.get(code);
        long currentTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        if (null == loginInfo || currentTime > loginInfo.getExpire()) {
            return false;
        }
        return true;
    }

    /**
     * 检查占用
     *
     * @param code
     * @return
     */
    public static boolean checkOcc(String code) {
        refreshLoginInfo();//整理
        LoginInfo loginInfo = loginInfoMap.get(code);
        if (null == loginInfo) {
            return false;
        }
        return loginInfo.isOcc();
    }

    /**
     * 获取登录用户名
     *
     * @param code
     * @return
     */
    public static LoginUser getUserByCode(String code) {
        LoginInfo loginInfo = loginInfoMap.get(code);
        if (null == loginInfo) {
            return null;
        }
        return userMap.get(loginInfo.getUsername());
    }

    /**
     * 整理登录信息
     * 主要是把已经过期的部分全部删除,否则数据会很大
     */
    public static void refreshLoginInfo() {
        //收集过期的code
        Set<String> codeSet = new HashSet<>(16);
        //收集
        long currentTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        for (Map.Entry<String, LoginInfo> entry : loginInfoMap.entrySet()) {
            if (currentTime > entry.getValue().getExpire()) {
                codeSet.add(entry.getKey());
            }
        }
        //删除
        for (String code : codeSet) {
            loginInfoMap.remove(code);
        }
    }
}

六、接口层UserApi.java,主要就是三个接口

package com.chris.sl.api;

import com.chris.sl.manager.DataManager;
import com.chris.sl.model.LoginUser;
import com.chris.sl.model.ScanCode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 11:11
 * use for: 接口
 */
@RestController
@RequestMapping("/api/user")
public class UserApi {
    /**
     * 登录 App调用
     * App扫描二维码之后,获得登录识别码,然后携带自己的帐号密码一起调用登录
     * 服务器在后台对三个信息进行验证,验证成功后把帐号放进登录呼叫池,等待前端下一次轮询
     *
     * @param username
     * @param password
     * @param code
     * @return
     */
    @GetMapping("/login")
    public Boolean login(String username, String password, String code) {
        //验证
        if (DataManager.verification(username, password, code)) {
            //占用
            DataManager.occLoginInfo(code, username);
            return true;
        }
        System.out.println("验证失败");
        return false;
    }

    /**
     * 获取扫描识别码 Web前端调用
     * 用于实现一个二维码
     * 同时服务器将这个二维码保存在数据库,同时设置好过期时间
     *
     * @return
     */
    @GetMapping("/getScanCode")
    public ScanCode getScanCode() {
        String code = UUID.randomUUID().toString();
        //添加到数据库
        DataManager.addLoginInfo(code);
        return new ScanCode(code);
    }

    /**
     * 询问登录 Web前端轮询
     * 1秒一次,后端根据code查看数据库,是否由用户绑定此code且通过了验证,等待登录
     * 如果成功,将用户名返回给前端
     *
     * @param code
     * @return
     */
    @GetMapping("/askLogin")
    public LoginUser askLogin(String code) {
        System.out.println(code);
        if (DataManager.checkOcc(code)) {
            return DataManager.getUserByCode(code);
        }
        return null;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现微信登录的前后分离流程如下: 1. 前生成登录二维码:前页面加载时,向后发送请求获取登录二维码的参数信息,包括appid和redirect_uri等,后根据这些参数生成登录二维码的URL,并返回给前。 2. 前展示二维码:前使用第三方库(如qrcode.js)将生成的登录二维码展示给用户。 3. 用户码确认登录:用户使用微信描前展示的二维码,微信客户会将用户的微信账号与该二维码关联,并向后发送确认登录的请求。 4. 后验证登录状态:后接收到微信客户发送的确认登录请求后,根据请求中的参数进行验证,包括校验appid、redirect_uri、code等信息的有效性。 5. 后获取用户信息:验证通过后,后使用code参数向微信服务器发送请求,获取用户的access_token和openid等信息。 6. 后生成登录凭证:后根据获取到的用户信息生成自己的登录凭证(如JWT),并将该凭证返回给前。 7. 前保存登录状态:前接收到后返回的登录凭证后,可以将该凭证保存在本地(如localStorage或cookie)用于后续的请求验证和会话管理。 8. 后续请求的验证:前后分离后,后续的请求需要在请求头中携带登录凭证进行验证,后根据凭证的有效性判断用户的登录状态。 这就是前后分离实现微信登录的大致流程,通过这种方式可以实现用户使用微信账号进行快速登录和注册。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值