专业的编程技术博客社区

网站首页 > 博客文章 正文

如何实现扫码登录(扫码登录怎么实现)

baijin 2024-08-12 14:00:05 博客文章 9 ℃ 0 评论

开发工具: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

@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(只展示核心)

<!--展示二维码-->
 <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接口

 @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

/**
 * @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();
 }
}

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表