Java二维码登录流程实现(包含短地址生成,含部分代码)

2023-11-07

近年来,二维码的使用越来越风生水起,笔者最近手头也遇到了一个需要使用二维码扫码登录网站的活,所以研究了一下这一套机制,并用代码实现了整个流程,接下来就和大家聊聊二维码登录及的那些事儿。

二维码原理

二维码是微信搞起来的,当年微信扫码二维码登录网页微信的时候,感觉很神奇,然而,我们了解了它的原理,也就没那么神奇了。二维码实际上就是通过黑白的点阵包含了一个url请求信息。端上扫码,请求url,做对应的操作。

一般性扫码操作的原理

微信登录、支付宝扫码支付都是这个原理:

二维码原理图

如图所示:

1. 请求二维码

桌面端向服务器发起请求一个二维码的。

2. 生成包含唯一id的二维码

桌面端会随机生成一个id,id唯一标识这个二维码,以便后续操作。

3. 端上扫码

移动端扫码二维码,解chu出二维码中的url请求。

4. 移动端发送请求到服务器

移动端向服务器发送url请求,请求中包含两个信息,唯一id标识扫的是哪个码,端上浏览器中特定的cookie或者header参数等会标识由哪个用户来进行扫码的。

5. 服务器端通知扫码成功

服务器端收到二维码中信息的url请求时,通知端上已经扫码成功,并添加必要的登录Cookie等信息。这里的通知方式一般有几种:websocket、轮训hold住请求直到超时、隔几秒轮训。

二维码中url的艺术
如何实现自有客户端和其他客户端扫码(如微信)表现的不同

比如,在业务中,你可能想要这样的操作,如果是你公司的二维码被其他app(如微信)所扫描,想要跳转一个提示页,提示页上可以有一个app的下载链接;而当被你自己的app所扫描时,直接进行对应的请求。

这种情况下,可以这样来做,所有二维码中的链接都进行一层加密,然后统一用另一个链接来处理。

如:www.test.com/qr?p=xxxxxx,p参数中包含服务器与客户端约定的加解密算法(可以是对称的也可以是非对称的),端上扫码到这种特定路径的时候,直接用解密算法解p参数,得到www.testqr.com/qrcode?key=s1arV,这样就可以向服务器发起请求了,而其他客户端因为不知道这个规则,只能直接去请求www.test.com/qr?p=xxxxxx,这个请求返回提示页。

如何让二维码更简单

很多时候,又要马儿跑,又要马儿不吃草。想要二维码中带有很多参数,但是又不想要二维码太复杂,难以被扫码出来。这时候,就需要考虑如何在不影响业务的情况下让二维码变的简单。

  • 与端上约定规则:比如定义编码信息中i参数为1,2,3表示不同的uri,端上来匹配遇到不同的i参数时请求哪个接口

  • 简化一切能简化的地方:简化uri,简化参数中的key、value。如www.a.com/q?k=s1arV就比www.abc.def.adfg.edu.com.cn/qrcode/scan?k=77179574e98a7c860007df62a5dbd98b 要简化很多,生成的二维码要好扫很多。

  • 简化唯一id参数:上一条中前一个请求中参数值只有5位,后一个请求中参数值为生成的32位md5值。生成一个端的key至关重要。

示例代码

需要导入jar包:zxingcore-2.0.jar

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

public class QrCodeUtil {
    private static final int BLACK = Color.black.getRGB();
    private static final int WHITE = Color.WHITE.getRGB();
    private static final int DEFAULT_QR_SIZE = 183;
    private static final String DEFAULT_QR_FORMAT = "png";
    private static final byte[] EMPTY_BYTES = new byte[0];
    
    public static byte[] createQrCode(String content, int size, String extension) {
        return createQrCode(content, size, extension, null);
    }

    /**
     * 生成带图片的二维码
     * @param content  二维码中要包含的信息
     * @param size  大小
     * @param extension  文件格式扩展
     * @param insertImg  中间的logo图片
     * @return
     */
    public static byte[] createQrCode(String content, int size, String extension, Image insertImg) {
        if (size <= 0) {
            throw new IllegalArgumentException("size (" + size + ")  cannot be <= 0");
        }
        ByteArrayOutputStream baos = null;
        try {
            Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);

            //使用信息生成指定大小的点阵
            BitMatrix m = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, size, size, hints);
            
            //去掉白边
            m = updateBit(m, 0);
            
            int width = m.getWidth();
            int height = m.getHeight();
            
            //将BitMatrix中的信息设置到BufferdImage中,形成黑白图片
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    image.setRGB(i, j, m.get(i, j) ? BLACK : WHITE);
                }
            }
            if (insertImg != null) {
                // 插入中间的logo图片
                insertImage(image, insertImg, m.getWidth());
            }
            //将因为去白边而变小的图片再放大
            image = zoomInImage(image, size, size);
            baos = new ByteArrayOutputStream();
            ImageIO.write(image, extension, baos);
            
            return baos.toByteArray();
        } catch (Exception e) {
        } finally {
            if(baos != null)
                try {
                    baos.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }
        return EMPTY_BYTES;
    }
    
    /**
     * 自定义二维码白边宽度
     * @param matrix
     * @param margin
     * @return
     */
    private static BitMatrix updateBit(BitMatrix matrix, int margin) {
        int tempM = margin * 2;
        int[] rec = matrix.getEnclosingRectangle(); // 获取二维码图案的属性
        int resWidth = rec[2] + tempM;
        int resHeight = rec[3] + tempM;
        BitMatrix resMatrix = new BitMatrix(resWidth, resHeight); // 按照自定义边框生成新的BitMatrix
        resMatrix.clear();
        for (int i = margin; i < resWidth - margin; i++) { // 循环,将二维码图案绘制到新的bitMatrix中
            for (int j = margin; j < resHeight - margin; j++) {
                if (matrix.get(i - margin + rec[0], j - margin + rec[1])) {
                    resMatrix.set(i, j);
                }
            }
        }
        return resMatrix;
    }
    
    // 图片放大缩小
    public static BufferedImage zoomInImage(BufferedImage originalImage, int width, int height) {
        BufferedImage newImage = new BufferedImage(width, height, originalImage.getType());
        Graphics g = newImage.getGraphics();
        g.drawImage(originalImage, 0, 0, width, height, null);
        g.dispose();
        return newImage;
    }
    
    private static void insertImage(BufferedImage source, Image insertImg, int size) {
        try {
            int width = insertImg.getWidth(null);
            int height = insertImg.getHeight(null);
            width = width > size / 6 ? size / 6 : width;  // logo设为二维码的六分之一大小
            height = height > size / 6 ? size / 6 : height;
            Graphics2D graph = source.createGraphics();
            int x = (size - width) / 2;
            int y = (size - height) / 2;
            graph.drawImage(insertImg, x, y, width, height, null);
            Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
            graph.setStroke(new BasicStroke(3f));
            graph.draw(shape);
            graph.dispose();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static byte[] createQrCode(String content) {
        return createQrCode(content, DEFAULT_QR_SIZE, DEFAULT_QR_FORMAT);
    }

    public static void main(String[] args){
        try {
            FileOutputStream fos = new FileOutputStream("ab.png");
            fos.write(createQrCode("test"));
            fos.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    
}
生成短链接

基本思路:

短网址映射算法的理论:

  1. 将长网址加随机数用用md5算法生成32位签名串,分为4段,每段8个字符

  2. 对这4段循环处理,取每段的8个字符, 将他看成16进制字符串与0x3fffffff(30位1)的位与操作,超过30位的忽略处理

  3. 将每段得到的这30位又分成6段,每5位的数字作为字母表的索引取得特定字符,依次进行获得6位字符串;

  4. 这样一个md5字符串可以获得4个6位串,取里面的任意一个就可作为这个长url的短url地址。

  5. 最好是用一个key-value数据库存储,万一发生碰撞换一个,如果四个都发生碰撞,重新生成md5(因为有随机数,会生成不一样的md5)

public class ShortUrlUtil {

    /**
     * 传入32位md5值
     * @param md5
     * @return
     */
    public static String[] shortUrl(String md5) {
        // 要使用生成 URL 的字符
        String[] chars = new String[] { "a", "b", "c", "d", "e", "f", "g", "h",
                "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
                "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
                "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
                "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
                "U", "V", "W", "X", "Y", "Z"

        };
        
        String[] resUrl = new String[4];  
        
        for (int i = 0; i < 4; i++) {

            // 把加密字符按照 8 位一组 16 进制与 0x3FFFFFFF 进行位与运算,超过30位的忽略
            String sTempSubString = md5.substring(i * 8, i * 8 + 8);

            // 这里需要使用 long 型来转换,因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用 long ,则会越界
            long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16);
            String outChars = "";
            for (int j = 0; j < 6; j++) {
                // 把得到的值与 0x0000003D 进行位与运算,取得字符数组 chars 索引
                long index = 0x0000003D & lHexLong;
                // 把取得的字符相加
                outChars += chars[(int) index];
                // 每次循环按位右移 5 位
                lHexLong = lHexLong >> 5;
            }
            // 把字符串存入对应索引的输出数组
            resUrl[i] = outChars;
        }
        return resUrl;
    }
    
    public static void main(String [] args){
        String[] test = shortUrl("fdf8d941f23680be79af83f921b107ac");
        for (String string : test) {
            System.out.println(string);
        }
    }
    
}

说明:核心代码非原创,借鉴了他人的代码,感谢!

转载于:https://www.cnblogs.com/shenpengyan/p/6120257.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java二维码登录流程实现(包含短地址生成,含部分代码) 的相关文章

  • 在文本文件中写入多行(java)

    下面的代码是运行命令cmd并使用命令行的输出生成一个文本文件 下面的代码在 Eclipse 的输出窗口中显示了正确的信息 但在文本文件中只打印了最后一行 谁能帮我这个 import java io public class TextFile
  • 如何在android上的python kivy中关闭应用程序后使服务继续工作

    我希望我的服务在关闭应用程序后继续工作 但我做不到 我听说我应该使用startForeground 但如何在Python中做到这一点呢 应用程序代码 from kivy app import App from kivy uix floatl
  • 如何创建一个显示 Spinners 的 x 和 y 值的表格?

    我想创建一个位于图表右侧的表格 其中显示 2 列 x 和 y 值已输入到xSpin and ySpin旋转器 我已经画了一张我想要桌子放置的位置的图 我尝试过在网格窗格布局中使用文本框来创建表格并将值直接输入到文本框网格中 但是我无法将它们
  • 是什么决定了从 lambda 创建哪个函数式接口?

    请考虑这个例子 import java util function Consumer public class Example public static void main String args Example example new
  • Java:如何从转义的 URL 获取文件?

    我收到了一个定位本地文件的 URL 事实上我收到的 URL 不在我的控制范围内 URL 按照 RFC2396 中的定义进行有效转义 如何将其转换为 Java File 对象 有趣的是 URL getFile 方法返回一个字符串 而不是文件
  • OpenCV 中的 Gabor 内核参数

    我必须在我的应用程序中使用 Gabor 过滤器 但我不知道这个 OpenCV 方法参数值 我想对虹膜进行编码 启动 Gabor 过滤器并获取特征 我想对 12 组 Gabor 参数值执行此操作 然后我想计算 Hamming Dystans
  • Java AES 128 加密方式与 openssl 不同

    我们遇到了一种奇怪的情况 即我们在 Java 中使用的加密方法会向 openssl 生成不同的输出 尽管它们在配置上看起来相同 使用相同的键和 IV 文本 敏捷的棕色狐狸跳过了懒狗 加密为 Base64 字符串 openssl A8cMRI
  • 比较两个文本文件的最快方法是什么,不将移动的行视为不同

    我有两个文件非常大 每个文件有 50000 行 我需要比较这两个文件并识别更改 然而 问题是如果一条线出现在不同的位置 它不应该显示为不同的 例如 考虑这个文件A txt xxxxx yyyyy zzzzz 文件B txt zzzzz xx
  • java中如何连接字符串

    这是我的字符串连接代码 StringSecret java public class StringSecret public static void main String args String s new String abc s co
  • 如何在不超过最大值的情况下增加变量?

    我正在为学校开发一个简单的视频游戏程序 我创建了一个方法 如果调用该方法 玩家将获得 15 点生命值 我必须将生命值保持在最大值 100 并且由于我目前的编程能力有限 我正在做这样的事情 public void getHealed if h
  • Hazelcast 分布式锁与 iMap

    我们目前使用 Hazelcast 3 1 5 我有一个简单的分布式锁定机制 应该可以跨多个 JVM 节点提供线程安全性 代码非常简单 private static HazelcastInstance hInst getHazelcastIn
  • 匿名类上的 NotSerializedException

    我有一个用于过滤项目的界面 public interface KeyValFilter extends Serializable public static final long serialVersionUID 7069537470113
  • 普罗米修斯指标 - 未找到

    我有 Spring Boot 应用程序 并且正在使用 vertx 我想监控服务和 jvm 为此我选择了 Prometheus 这是我的监控配置类 Configuration public class MonitoringConfig Bea
  • 如何在 Java 中测试一个类是否正确实现了 Serialized(不仅仅是 Serialized 的实例)

    我正在实现一个可序列化的类 因此它是一个与 RMI 一起使用的值对象 但我需要测试一下 有没有办法轻松做到这一点 澄清 我正在实现该类 因此在类定义中添加 Serialized 很简单 我需要手动序列化 反序列化它以查看它是否有效 我找到了
  • IntelliJ - 调试模式 - 在程序内存中搜索文本

    我正在与无证的第三方库合作 我知道有一定的String存储在库深处的某个字段中的某处 我可以预测的动态值 但我想从库的 API 中获取它 有没有一种方法可以通过以下方式进行搜索 类似于全文搜索 full程序内存处于调试模式并在某个断点处停止
  • 有没有一种快速方法可以从 Jar/war 中删除文件,而无需提取 jar 并重新创建它?

    所以我需要从 jar war 文件中删除一个文件 我希望有类似 jar d myjar jar file I donot need txt 的内容 但现在我能看到从 Linux 命令行执行此操作的唯一方法 不使用 WinRAR Winzip
  • Netty:阻止调用以获取连接的服务器通道?

    呼吁ServerBootstrap bind 返回一个Channel但这不是在Connected状态 因此不能用于写入客户端 Netty 文档中的所有示例都显示写入Channel从它的ChannelHandler的事件如channelCon
  • spring中如何使用jackson代替JdkSerializationRedisSerializer

    我在我的一个 Java 应用程序中使用 Redis 并且正在序列化要存储在 Redis 中的对象列表 但是 我注意到使用 RedisTemplate 会使用 JdkSerializationRedisSerializer 相反 我想使用 J
  • 在 RESTful Web 服务中实现注销

    我正在开发一个需要注销服务的移动应用程序 登录服务是通过数据库验证来完成的 现在我陷入了注销状态 退一步 您没有提供有关如何在应用程序中执行身份验证的详细信息 并且很难猜测您在做什么 但是 需要注意的是 在 REST 应用程序中 不能有会话
  • GUI Java 程序 - 绘图程序

    我一直试图找出我的代码有什么问题 这个想法是创建一个小的 Paint 程序并具有红色 绿色 蓝色和透明按钮 我拥有我能想到的让它工作的一切 但无法弄清楚代码有什么问题 该程序打开 然后立即关闭 import java awt import

随机推荐

  • 在Power Designer生成的类图中同时显示name和code

    在PowerDesigner生成的类图中同时显示name和code 问题 由显示name改为显示code 同时显示name和Code 问题 Power Designer是一款非常强大的设计工具 缺省的类图只显示name 一般地我们在name
  • 牛顿法(Newton’s method)

    牛顿法通常都是用来寻找一个根 同时也可以理解为最大化目标函数的局部二次近似 设我们的目标函数为f x 那么一个关于x0的二次近似就有 我们用f进行匹配 可以得到 如果b lt 0 g的最大值为a 得到更新规则 这是牛顿法在最优化方面的表述
  • 使用wps转换文本中001、002章节名为第001章、第002章

    1 首先ctrl f打开查找和替换 2 点击弹窗中高级搜索 选中使用通配符 3 可先输入 lt 0 9 3 尝试是否可以查找出文本内章节名 可以找到的话进行下一步 4 切换到替换tab 替换为输入框内输入第 1章 之后点击底部全部替换 即可
  • 万向区块链肖风:元宇宙的十大经济规则

    本文为万向区块链董事长兼总经理肖风为华泰证券研究所科技及电子行业首席分析师黄乐平 万向区块链首席经济学家邹传伟联合撰写的 元宇宙经济学 所作序言 元宇宙是什么 按照我的理解 元宇宙是一个由分布式网络技术 分布式账本和分布式社会 商业构成的三
  • 【Linux网络编程笔记】TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇

    上篇笔记主要介绍了与TIME WAIT相关的基础知识 本文则从实践出发 说明如何解决文章标题提出的问题 1 查看系统网络配置和当前TCP状态 在定位并处理应用程序出现的网络问题时 了解系统默认网络配置是非常必要的 以x86 64平台Linu
  • Python raise用法(超级详细,看了无师自通)

    当程序出现错误时 系统会自动引发异常 除此之外 Python 也允许程序自行引发异常 自行引发异常使用 raise 语句来完成 异常是一种很 主观 的说法 以下雨为例 假设大家约好明天去爬山郊游 如果第二天下雨了 这种情况会打破既定计划 就
  • 软件测试测试环境搭建很难?一天学会这份测试环境搭建教程

    如何搭建测试环境 这既是一道高频面试题 又是困扰很多小伙伴的难题 因为你在网上找到的大多数教程 乃至在一些培训机构的课程 都不会有详细的说明 你能找到的大多数项目 是在本机电脑环境搭建环境 或是别人已经搭建好的环境 你很难上手体验在服务器上
  • IntersectionObserver实现滚动加载

    加载模板及样式 template div class lazy more div
  • WebSocket :用WebSocket实现推送你必须考虑的几个问题

    目录 目录 WebSocket简介 项目背景硬件环境及客户端支持 本文研究内容 基于javaxwebsocket服务端代码源码后续补充git连接 客户端代码 问题探索 8月3日补充 中间线路断网情况 如何做到支持几千个client同时在线人
  • WebSocket集群解决方案,不用MQ

    首先不了解WebSocket的可以先看看这篇文章 以及传统的WebSocket方案是怎么做的 https www cnblogs com jeremylai7 p 16875115 html 这是用MQ解决的版本 那么这种方案存在什么问题呢
  • 使用nginx配置二级域名

    最近想把三个项目配在一个服务器上 于是想使用nginx配置二级域名实现 1 域名添加解析 我的是阿里云的域名 所以首先给自己的域名添加解析 打算使用 www codeliu com test1 codeliu com test2 codel
  • elementUI表格行的点击事件,点击表格,拿到当前行的数据

    1 绑定事件 2 定义事件 3 点击表格某行的时候 拿到数据 转载于 https www cnblogs com wuhefeng p 11316215 html
  • STM32 PID调节输出电压

    一 简介 关于PID调节的这里不做详解 就简单说下 其实就是先设定好一个期望 通过反馈系统返回输出值 然后判断这个输出实际输出的值 和我们的期望值的误差 然后PID算法根据这个误差 去调整我们的输出值 直到输出达到我们的期望值 那么我们为啥
  • 为什么程序员做外包会被瞧不起?

    二哥 有个事想询问下您的意见 您觉得应届生值得去外包吗 公司虽然挺大的 中xx 但待遇感觉挺低 马上要报到 挺纠结的 以上是读者小 K 给我发的私信 除此之外 还有个读者 down 也问我关于外包的事情 担心外包学不到技术 但很不幸的是年前
  • 11种概率分布,你了解几个?

    点击上方 小白学视觉 选择加 星标 或 置顶 重磅干货 第一时间送达 本文转自 视学算法 了解常见的概率分布十分必要 它是概率统计的基石 这是昨天推送的 从概率统计到深度学习 四大技术路线图谱 都在这里 文章中的第一大技术路线图谱如下所示
  • 【mega-nerf】调包失败&pip install失败解决方案

    Problem 1 调包失败 在这样的层级架构里调包 输出无法找到 mega nerf 直接用 sys path append 没有作用 import sys print sys path sys path append home pape
  • Java实现Excel文件生成和下载功能

    7 行代码优雅地实现 Excel 文件生成 下载功能 欢迎关注博主公众号 小哈学Java 专注于分享Java领域干货文章 关注回复 资源 免费领取全网最热的Java架构师学习PDF 转载请注明出处 https www exception s
  • stm32F103C8T6控制DHT11

    stm32F103C8T6控制DHT11串口打印 stm32F103C8T6控制DHT11串口打印学习经验总结 本人借鉴了许多大佬们的资料 这是个人学习的见解 如发现错误之处 麻烦指导指导 借鉴链接 https blog csdn net
  • 移动端-表头固定的表格组件

    UI原型 HTML代码 div class scroll table box div class scroll table head table class tb1 thead tr th style width 3em 序号 th th
  • Java二维码登录流程实现(包含短地址生成,含部分代码)

    近年来 二维码的使用越来越风生水起 笔者最近手头也遇到了一个需要使用二维码扫码登录网站的活 所以研究了一下这一套机制 并用代码实现了整个流程 接下来就和大家聊聊二维码登录及的那些事儿 二维码原理 二维码是微信搞起来的 当年微信扫码二维码登录