前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >扫码登录的实现方案

扫码登录的实现方案

作者头像
每天学Java
发布2020-06-02 10:19:16
1.9K0
发布2020-06-02 10:19:16
举报
文章被收录于专栏:每天学Java

如何实现扫码登录

开发工具:MacOS、IDEA 技术栈:JDK1.8、SpringBoot、Thymeleaf、websocket、ZXing、jjwt

项目简介:

最近在想要打通各个子项目,于是搭建一个统一认证平台就成了任务的核心,对于企业级的CAS认证服务不在考虑范围内,轻量级任务框架如XXL-SSO我比较喜欢,经过一番研究,发现技术落地的核心是SpringBoot,Redis,拦截器。最终决定,自己搭建一个统一认证平台。这一篇文章对于单点登录不做描述,而是针对单点登录下的登录方式之一:扫码。

开发原理:

二维码生成技术使用谷歌开源的ZXing框架 前台采用Thymeleaf模版获取初始化数据 前后端通讯方式采用全双工通信的WebSocket

开发方案

第一步,连接到WebSocket上,获取到二维码。过程如下 前台打开登录界面,首先由SpringBoot的Controoler层分配一唯一UUID(分布式可采用雪花算法生成唯一ID,这里单机所以采用UUID),然后前端携带UUID连接到WebSocket服务中,与此同时异步请求携带UUID请求二维码接口,由接口输出二维码的流到页面上展示。 第二步,扫码,发送授权登录的请求,返回身份Token。过程如下 通过小程序/APP扫描二维码,取到二维码中的UUID,弹出是否授权登录弹窗,如果同意授权,则携带UUID和Token(小程序和APP已经登录过,所有具有身份信息)去请求确认登录的接口,接口通过UUID找到对应WebSocket连接的Session,然后传输Token给前端,如此便登录成功

实现效果

使用postman模拟扫码授权登录

核心代码

ViewController

代码语言:javascript
复制
@Controller
public class LoginController {
    @RequestMapping("login")
    public String login(HashMap<String, Object> map) {
        //UUID
        String code = generateUUID();
        map.put("code", code);
        map.put("url", "/zing/login/" + code);
        return "login";
    }
}

HTML(只展示核心)

代码语言:javascript
复制
<!--展示二维码-->
 <img th:src="${url}" 
style="width: 188px;height: 188px;border: 1px solid #E2E2E2;">
<!--webSocket连接-->
<script>
    var token;
    if (typeof (WebSocket) == "undefined") {
        alert("您的浏览器不支持WebSocket");
    } else {
        socket = new WebSocket("ws://127.0.0.1/websocket/".replace("http", "ws"));
        socket.onopen = function () {
            console.log("Socket 已打开");
            //发送Code
            socket.send("[[${code}]]")
        }
        socket.onmessage = function (msg) {
            token = JSON.parse(msg.data).data
            console.log("Token获取成功:" + token)
        }
        socket.onclose = function () {
            console.log("Socket已关闭");
        };
        //发生了错误事件
        socket.onerror = function () {
            alert("Socket发生了错误,请刷新");
        }
    }
</script>

Rest接口

代码语言:javascript
复制
    @RequestMapping("zing/login/{token}")
    @ResponseBody
    public void createQRCode(HttpServletResponse response, @PathVariable("token") String token) {
        //将带有Token的二维码返回到前端
        OutputStream oStream = null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            //生成二维码
            generate(token, baos);
            byte[] bytes = baos.toByteArray();
            oStream = response.getOutputStream();
            oStream.write(bytes);
        } catch (IOException e) {
            log.error("生成二维码出现错误", e);
            e.printStackTrace();
        } finally {
            //当创建对象成功时候,在执行close()方法。
            if (oStream != null) {
                try {
                    oStream.close();
                } catch (IOException e) {
                    try {
                        oStream.close();
                    } catch (IOException e1) {
                        log.error("生成二维码关闭出现错误", e);
                        e1.printStackTrace();
                    }
                    log.error("生成二维码关闭出现错误", e);
                    e.printStackTrace();
                }
            }
        }
    }
    private static void generate(String token, OutputStream stream) {
        try {
            MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
            FiveElements fiveElements = new FiveElements();
            fiveElements.setToken(token);
            fiveElements.setDate(new Date());
            String contents = JSON.toJSONString(fiveElements);
            HashMap<EncodeHintType, Object> hints = new HashMap<>();
            hints.put(EncodeHintType.CHARACTER_SET, CHARTSET);
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
            hints.put(EncodeHintType.MARGIN, 2);
            BitMatrix bitMatrix = multiFormatWriter.encode(contents, BarcodeFormat.QR_CODE, 400, 400, hints);
            MatrixToImageWriter.writeToStream(bitMatrix, "jpg", stream);
        } catch (Exception e) {
            System.out.println("二维码生成出错" + e.getMessage());
        }
    }

WebSocket

代码语言:javascript
复制
/**
 * @Auther: 陈龙
 * @Date: 2019-07-24 10:43
 * @Description:
 */

@ServerEndpoint("/websocket/")
@Component
public class WebSocketServer {

    static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
    //静态变量,用来记录当前在线连接数。
    private static AtomicInteger onlineCount = new AtomicInteger(0);
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    private String code;


    /**
     * 连接建立成功调用的方法
     */

    @OnOpen
    public void onOpen(Session session) {
        //UUID作为随机Token
        this.session = session;
        webSocketSet.add(this);//加入set中
        addOnlineCount();//在线数加1
        log.info("有新请求链接进入,当前在线人数为" + getOnlineCount());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    }


    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */

    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口的信息,Code为:" + message);
        //群发消息
        for (WebSocketServer item : webSocketSet) {
            if (item.session == item.session) {
                //存储code
                item.code = message;
                break;
            }
        }
    }


    /**
     * @param session
     * @param error
     */

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();

    }

    /**
     * 实现服务器主动推送
     */

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    /**
     * 群发自定义消息
     */

    public static void sendInfo(String message, String code) throws IOException {
        log.info("推送消息到窗口" + code + ",推送内容:" + message);
        for (WebSocketServer item : webSocketSet) {
            try {
                //这里可以设定只推送给这个sid的,为null则全部推送
                if (code == null) {
                    item.sendMessage(message);
                } else if (item.code.equals(code)) {
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }


    public static int getOnlineCount() {
        return onlineCount.get();
    }


    public static void addOnlineCount() {
        WebSocketServer.onlineCount.incrementAndGet();
    }


    public static void subOnlineCount() {
        WebSocketServer.onlineCount.decrementAndGet();
    }

}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何实现扫码登录
    • 项目简介:
      • 开发原理:
        • 开发方案
          • 实现效果
            • 核心代码
            相关产品与服务
            云开发 CloudBase
            云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档