【SpringCloud+Vue】生成微信二维码及扫码登录--OAuth2

news2024/11/20 11:35:36

OAuth2

微信登录流程

前端代码实现

后端代码实现

导入依赖

yml

实体类以及返回结果

工具类

微信配置信息

HTTP客户端连接池

JWT

控制层

业务层

持久层


OAuth2

        OAuth2是OAuth(Open Authorization,开放授权)协议的延续版本。用来授权第三方应用获取用户数据,是目前最流行的授权机制,它当前的版本是2.0。

一共定义了四种角色:

        1.资源所有者(Resource Owner):即代表用户本身

        2.资源服务器(Resource Server):存储受保护的账号信息

        3.授权服务器(Authorization Server):在成功验证用户身份,并获得授权后,给客户端派发访问资源令牌

        4.客户端(Client):即代表你访问的第三方应用

微信登录流程

        微信 OAuth2.0 授权登录让微信用户使用微信身份安全登录第三方应用或网站第三方可以获取到用户的接口调用凭证(access_token),通过凭证可以进行微信开放平台授权关系接口调用,从而可实现获取微信用户基本开放信息和帮助用户实现基础开放功能等。

        如果一个网站要使用微信登录,必然是要去微信公众号后台申请 appid 的,并且在申请的时候,还要填写一个获取 code的域名,而微信后台也会返回appsecret,appid,secret,code,域名。想要获取微信申请扫码所需可以看网址:neh微信s开放平台

第一步:微信用户扫描二维码请求登录第三方应用

第二步:第三方应用携带 appid 以及 redirect_uri 通过重定向的方式请求微信OAuth2.0授权登录(最常见的就是生成一个二维码给微信用户扫描),注意这一步并没有发送appsecret

  • 注意:此时微信开放平台是无法确定第三方应用身份的,因为这时微信开放平台只有一个appid,但没有任何手段来确认 第三方应用使用的是自己的 appid;
  • 用户授权后,微信会立即发送 code 和 state(自己设定的字段) 到 redirect_uri 中。

第三步:微信用户在微信开放平台上认证身份(扫码认证),并统一授权给 第三方应用;
第四步:微信用户允许授权第三方应用 后,微信 会 302 跳转到第三方网站 的 redirect_uri 上,并且带上授权临时票据 code(authorization code);

    按 OAuth2.0 的协议约定,该 code 通过浏览器的 302 重定向发送给第三方应用,这意味着 code 值从浏览器就能看到了,非常危险。

第五步:第三方应用 拿 code 以及 appid、appsecret 换取 accsess_token 和 openid;

    首先,这个过程是 第三方应用 后台 对 微信开放平台 后台 的,不依赖浏览器,所以access_token不会像 code 那样会暴露出去。
    其次,第三方应用 需要提供自己的 appsecret,这样就为 微信开放平台 提供了一种验证 第三方应用 的机制。

微信登录流程具体参考了这篇文章:微信OAuth2.0 登录流程以及安全性分析_一个小码农的进阶之旅的博客-CSDN博客_微信oauth2.0

前端代码实现

<template>
    <div class="wechat-wrapper" @click="weixinLogin()">
        <span class="iconfont icon"></span>
    </div>
</template>
<script>
export default{
    data(){
        return{}
    }
    mounted:{
        //初始化微信js
        const script = document.createElement('script')
        script.type = 'text/javascript'
        script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
        document.body.appendChild(script)
        //微信登录回调处理
        let self = this;
        window["loginCallback"] = (name,token, openid) => {
          self.loginCallback(name, token, openid);
        }
    },
    methods:{
        //使用微信扫码回调的方法
        loginCallback(name, token, openid) {
            this.setCookies(name, token);//方便后面的token拦截
        },
        weixinLogin() {
          //调用后端查询微信需要的参数
          axios.get("http://localhost:8160/wx/getLoginParam").then(response => {
              new WxLogin({
              self_redirect:true,
              id: 'weixinLogin', // 需要显示的容器id
              appid: response.data.appid, // 公众号appid wx*******
              scope: response.data.scope, // 网页默认即可
              redirect_uri: response.data.redirect_uri, // 授权成功后回调的url
              state: response.data.state, // 可设置为简单的随机数加session用来校验
              style: 'black', // 提供"black"、"white"可选。二维码的样式
              href: '' // 外部css文件url,需要https
            })
          })
        },
        //设置Cookie中的name与token
        setCookies(name, token) {
            cookie.set('token', token, { domain: 'localhost' })
            cookie.set('name', name, { domain: 'localhost' })
            window.location.reload()
        }
    }
}
</script>
<!--这里是回调方法到/weixin/callback.vue中的-->
<script>
export default {
  data() {
    return {
    }
  },
  mounted() {
    let token = this.$route.query.token
    let name = this.$route.query.name
    let openid = this.$route.query.openid
    // 调用父vue方法
    window.parent['loginCallback'](name, token, openid)
  }
}
</script>

后端代码实现

导入依赖

<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--利用JWT生成Token-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>
<!--使用PoolingHttpClientConnectionManager HTTP线程池-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<!-- 服务调用feign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--服务注册 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

yml

server:
  port: 8160    #必须使用这个端口号
spring:
  application:
    name: service-user
  profiles:
    active: dev
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/yygh_user?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
  #返回Json的全局时间格式
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  #Nacos服务地址
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml

wx:
  open:
    app_id: wxed9954c01bb89b47
    app_secret: a7482517235173ddb4083788de60b90e
    redirect_url: http://localhost:8160/wx/callback

#前端对应的网址
item:
  baseUrl: http://localhost:3000

实体类以及返回结果

@TableName("user_info")
public class UserInfo extends BaseEntity {

	private static final long serialVersionUID = 1L;
	
	@ApiModelProperty(value = "微信openid")
	@TableField("openid")
	private String openid;

	@ApiModelProperty(value = "微信昵称")
	@TableField("nick_name")
	private String nickName;

	@ApiModelProperty(value = "状态(0:锁定 1:正常)")
	@TableField("status")
	private Integer status;
}
/**
 * 全局统一返回结果类
 */
@Data
public class R<T> {
    private Integer code;//返回的状态码
    private String message;//返回的信息
    private T data;//返回的数据

    public static <T> R<T> ok(){
        return R.ok(null);
    }

    /**
     * 操作成功
     * @param data
     * @param <T>
     * @return
     */
    public static <T> R<T> ok(T data){
        R<T> result = new R<T>();
        result.setCode(200);
        result.setMessage("成功");
        if(data != null) result.setData(data);
        return result;
    }
}

工具类

微信配置信息

/**
 * 从配置文件中读取微信所需的配置信息
 */
@Component
public class ConstantWxPropertiesUtil implements InitializingBean {
    @Value("${wx.open.app_id}")
    private String appId;
    @Value("${wx.open.app_secret}")
    private String appSecret;
    @Value("${wx.open.redirect_url}")
    private String redirectUrl;
    @Value("${item.baseUrl}")
    private String BaseUrl;

    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;
    public static String ITEM_BASE_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
        WX_OPEN_APP_ID = this.appId;
        WX_OPEN_APP_SECRET = this.appSecret;
        WX_OPEN_REDIRECT_URL = this.redirectUrl;
        ITEM_BASE_URL = this.BaseUrl;
    }
}

HTTP客户端连接池

public class HttpClientUtil {

    public static final int connTimeout = 10000;
    public static final int readTimeout = 10000;
    public static final String charset = "UTF-8";
    private static HttpClient client = null;

    static {
        PoolingHttpClientConnectionManager cm 
                = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(128);
        cm.setDefaultMaxPerRoute(128);
        client = HttpClients.custom().setConnectionManager(cm).build();
    }

    public static String get(String url) throws Exception {
        return get(url, charset, null, null);
    }

    /**
     * 发送一个 GET 请求
     */
    public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
        throws ConnectTimeoutException,SocketTimeoutException, Exception {
        HttpClient client = null;
        HttpGet get = new HttpGet(url);
        String result = "";
        try {
            // 设置参数
            Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            get.setConfig(customReqConf.build());
            HttpResponse res = null;
            if (url.startsWith("https")) {
                // 执行 Https 请求.
                client = createSSLInsecureClient();
                res = client.execute(get);
            } else {
                // 执行 Http 请求.
                client = HttpClientUtil.client;
                res = client.execute(get);
            }
            result = IOUtils.toString(res.getEntity().getContent(), charset);
        } finally {
            get.releaseConnection();//释放连接
            if (client != null && url.startsWith("https") && client instanceof CloseableHttpClient) {
                ((CloseableHttpClient) client).close();
            }
        }
        return result;
    }
}

JWT

public class JwtUtil {
    //过期时间24小时
    private static long tokenExpiration = 24 * 60 * 60 * 1000;
    //签名秘钥
    private static String tokenSignKey = "123456";

    /**
     * 根据参数生成token
     * @param userId 用户Id
     * @param userName 用户名称
     * @return
     */
    public static String createToken(Long userId, String userName) {
        String token = Jwts.builder()
                .setSubject("USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .claim("userId", userId)
                .claim("userName", userName)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }
}

控制层

@Controller
@RequestMapping("/wx")
public class WeiXinController {
    @Autowired
    private UserInfoService userInfoService;

    /**
     * 微信生成二维码返回需要的参数
     * @return
     */
    @GetMapping("/getLoginParam")
    @ResponseBody //为了返回数据
    public R getQrConnect(){
        try {
            Map<String,Object> map = new HashMap<>();
            map.put("appid", ConstantWxPropertiesUtil.WX_OPEN_APP_ID);
            map.put("scope", "snsapi_login");
            String wxOpenRedirectUrl = ConstantWxPropertiesUtil.WX_OPEN_REDIRECT_URL;
            wxOpenRedirectUrl = URLEncoder.encode(wxOpenRedirectUrl,"utf-8");
            map.put("redirect_uri",wxOpenRedirectUrl);
            map.put("state",System.currentTimeMillis() + "");
            return R.ok(map);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 微信扫码后回调的方法
     * @param code 临时票据
     * @param state 
     * @return
     */
    @GetMapping("/callback")
    public String callback(String code,String state){
        //将临时票据(code)和微信id及密钥 请求微信固定地址
        //https://api.weixin.qq.com/sns/oauth2/access_token?appid=??&secret=??&code=??&grant_type=authorization_code
        StringBuffer stringBuffer = new StringBuffer()
                .append("https://api.weixin.qq.com/sns/oauth2/access_token")
                .append("?appid=%s")
                .append("&secret=%s")
                .append("&code=%s")
                .append("&grant_type=authorization_code");
        String accessTokenUrl = String.format(stringBuffer.toString(),
                ConstantWxPropertiesUtil.WX_OPEN_APP_ID,
                ConstantWxPropertiesUtil.WX_OPEN_APP_SECRET,
                code);
        try {
            //通过HttpClient请求这个地址
            String accessTokenInfo = HttpClientUtil.get(accessTokenUrl);
            //从返回的这个字符串获取openid和access_token
            JSONObject object = JSONObject.parseObject(accessTokenInfo);
            String openid = object.getString("openid");
            String accessToken = object.getString("access_token");
            //根据openid判断数据库中是否存有扫码人信息
            UserInfo wxInfo = userInfoService.selectWxInfoOpenId(openid);
            if(wxInfo == null) {
                //请求微信地址 得到扫码人信息
                String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                        "?access_token=%s" + "&openid=%s";
                String userInfoUrl = String.format(baseUserInfoUrl,accessToken,openid);
                String resultInfo = HttpClientUtil.get(userInfoUrl);//扫码人信息
                JSONObject jsonObject = JSONObject.parseObject(resultInfo);
                String nickname = jsonObject.getString("nickname");//获得扫码人的昵称
                //将扫码人信息(昵称、微信id、状态)添加进数据库
                wxInfo = new UserInfo();
                wxInfo.setNickName(nickname);
                wxInfo.setOpenid(openid);
                wxInfo.setStatus(1);
                userInfoService.save(wxInfo);
            }
            //返回name和token字符串
            Map<String,Object> map = new HashMap<>();
            map.put("name",wxInfo.getNickName());
            String token = JwtUtil.createToken(wxInfo.getId(), name);
            map.put("token",token);
            //跳转到前端页面
            return "redirect:" + ConstantWxPropertiesUtil.ITEM_BASE_URL +
                    "/weixin/callback?token=" + map.get("token") + "&openid=" +
                    map.get("openid") + "&name=" + URLEncoder.encode((String) map.get("name"),"utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

业务层

@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper,UserInfo> implements UserInfoService {

    /**
     * 根据openid判断数据库中是否存有扫码人信息
     * @param openid
     * @return
     */
    @Override
    public UserInfo selectWxInfoOpenId(String openid) {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("openid",openid);
        UserInfo userInfo = baseMapper.selectOne(wrapper);
        return userInfo;
    }
}

持久层

public interface UserInfoMapper extends BaseMapper<UserInfo> {
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/352361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hinge Loss 和 Zero-One Loss

文章目录Hinge Loss 和 Zero-One LossHinge LossZero-One LossHinge Loss 和 Zero-One Loss 维基百科&#xff1a;https://en.wikipedia.org/wiki/Hinge_loss 图表说明&#xff1a; 纵轴表示固定 t1t1t1 的 Hinge loss&#xff08;蓝色&#xff09;和 Zero-One Loss&#xff…

字节5年测试经验,12月无情被辞,划水的兄弟别再这样了····

前言 先简单交代一下背景吧&#xff0c;某不知名 985 的本硕&#xff0c;17 年毕业加入字节&#xff0c;以“人员优化”的名义无情被裁员&#xff0c;之后跳槽到了有赞&#xff0c;一直从事软件测试的工作。之前没有实习经历&#xff0c;算是5年的工作经验吧。 这5年之间完成…

实现一个小程序分享图 wxml2canvas

我们经常会遇上动态生成海报的需求&#xff0c;而在小程序中&#xff0c;生成图片非Canvas莫属。但是在实际工作当中&#xff0c;为了追求效率&#xff0c;我们会不可避免地去使用一些JS插件&#xff0c;而 wxml-to-canvas 就是一款官方推荐且非常优秀的插件&#xff0c;它可以…

图文详解Ansible中的变量及加密

文章目录一、变量命名二、变量级别三、.变量设定和使用方式1.在playbook中直接定义变量2.在文件中定义变量3.使用变量4.设定主机变量和清单变量5.目录设定变量6.用命令覆盖变量7.使用数组设定变量8.注册变量9.事实变量10.魔法变量四、JINJA2模板五、 Ansible的加密控制练习1.用…

I2C总线应用测试程序

参考链接&#xff1a;I2c协议 Linux I2C应用编程开发 问题背景 在工作中需要测试I2C总线的传输稳定性&#xff0c;需写一个测试程序通过读写从设备寄存器的值来验证数据传输稳定性。 站在cpu的角度来看&#xff0c;操作I2C外设实际上就是通过控制cpu中挂载该I2C外设的I2C控制…

yunUI组件库解析:图片上传与排序组件yImgPro

yunUI是笔者开源的微信小程序功能库。目前其中包含了一些复杂的功能组件。方便使用。未来它将分为组件、样式、js三者合为一体&#xff0c;但分别提供。 本文所用代码皆来源于组件库中的yImgPro组件。详细代码可至github查看。地址&#xff1a; yunUI 。 npm地址&#xff1a;yu…

Bing+ChatGPT 对传统搜索引擎的降维打击

早些时候申请了新版 Bing 的内测资格&#xff0c;终于收到了通过的邮件。 一天的体验之后&#xff0c;我的感受是&#xff1a;当新版 Bing 具备了 ChatGPT 的聊天能力之后&#xff0c;它的能力不论是对传统搜索引擎&#xff0c;还是 ChatGPT 自身&#xff0c;都将是降维打击。 …

LeetCode 237. 删除链表中的节点

原题链接 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 有一个单链表的 headheadhead&#xff0c;我们想删除它其中的一个节点 nodenodenode。 给你一个需要删除的节点 nodenodenode 。你将 无法访问 第一个节点 headheadhead。 链表的所有值都是 唯一的&…

IoT 边缘集群基于 Kubernetes Events 的告警通知实现(二):进一步配置

上一篇文章 IoT 边缘集群基于 Kubernetes Events 的告警通知实现 目标 告警恢复通知 - 经过评估无法实现原因: 告警和恢复是单独完全不相关的事件, 告警是 Warning 级别, 恢复是 Normal 级别, 要开启恢复, 就会导致所有 Normal Events 都会被发送, 这个数量是很恐怖的; 而且…

【重排重绘】从输入url到浏览器展示页面发生了什么?

目录步骤如下&#xff1a;一、用户在浏览器搜索栏中输入url地址二、浏览器解析域名得到服务器ip地址浏览器解析域名得到服务器ip地址有哪些过程&#xff1f;三、TCP三次握手建立客户端和服务器的连接四、客户端发送HTTP请求获取服务器端的静态资源五、服务器发送HTTP响应报文给…

程序员深度体验一周ChatGPT发现竟然....

程序员深度体验一周ChatGPT发现竟然… 周一打卡上班&#xff0c;老板凑到我跟前&#xff1a;“小李啊&#xff0c;这周有个新需求交给你做一下&#xff0c;给我们的API管理平台新增一个智能Mock的功能…”。我条件反射般的差点脱口而出&#xff1a;“这个需求做不了…”。不过…

【软件测试】资深测试总结的几个自动化测试点,提升跨越一大步......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 自动化的软件测试与…

PostgreSQL查询引擎——SELECT STATEMENTS SelectStmt

SelectStmt: select_no_parens %prec UMINUS| select_with_parens %prec UMINUS select_with_parens:( select_no_parens ) { $$ $2; }| ( select_with_parens ) { $$ $2; } 该规则返回单个SelectStmt节点或它们的树&#xff0c;表示集合操作树(set-operation tree…

JAVA线程池的使用

一、池化思想和JAVA线程池 池化是很重要的思想&#xff1b;池化的好处是提供缓冲和统一的管理。这个笔者在本人的数据库连接池的博客中已经提到过了&#xff08;JAVA常用数据库连接池_王者之路001的博客-CSDN博客 &#xff09;。 线程池是另一种池化思想的运用&#xff0c;把…

MySQL 派生表产生关联索引auto_key0导致SQL非常的慢

相同的SQL在maridb运行0.5秒&#xff0c;在MySQL8.0.26中运行要19秒 官方MySQL在处理子查时&#xff0c;优化器有个优化参数derived_merge&#xff0c;MySQL7开启添加&#xff0c;默认on.很多情况可以自动优化派生表&#xff0c;避免创建临时索引auto_key0和生成临时表数据做…

C++入门:函数重载

目录 一. 函数重载的概念和分类 1.1 什么是函数重载 1.2 函数重载的分类 1.3 关于函数重载的几点注意事项 二. C实现函数重载的底层逻辑&#xff08;为什么C可以实现函数重载而C语言不能&#xff09; 2.1 编译器编译程序的过程 2.2 为什么C可以实现函数重载而C语言不能 …

内网安装管家婆软件如何实现外网访问?内网穿透的几种方案教程

管家婆软件从网络架构上分两种版本&#xff1a;web&#xff08;浏览器http端口&#xff09;访问的版本和客户端&#xff08;211固定端口sqlserver数据库&#xff09;访问的版本。公司库管经常用仓库登录管家婆&#xff0c;一旦需要在公司外部登陆访问管家婆客户端&#xff0c;就…

微信中如何接入机器人才比较正常

大家好,我是雄雄,欢迎关注微信公众号:雄雄的小课堂。 前言 为什么会有这个话题?大家都知道最近有个AI机器人很火,那就是AI机器人,关于它的介绍,大家可以自行百度去,我这边就不多介绍了。 好多人嫌网页版玩的不过瘾,就把这个机器人接入到了QQ上,接入到了钉钉上,TG …

Go语言基础知识学习笔记

环境准备 下载安装Golang&#xff1a;https://golang.google.cn/dl/ 因为国外下载速度较慢&#xff0c;我们需要配置国内代理 # 开启包管理工具 go env -w GO111MODULEon # 设置代理 go env -w GOPROXYhttps://goproxy.cn,direct # 设置不走 proxy 的私有仓库&#xff0c;多…

Ajax?阿贾克斯?

一、Ajax简介 AJAX Asynchronous JavaScript and XML&#xff08;异步的 JavaScript 和 XML&#xff09;。 AJAX 不是新的编程语言&#xff0c;而是一种使用现有标准的创新方法。 AJAX 最大的优点是在不重新加载整个页面的情况下&#xff0c;可以与服务器交换数据并更新部分网…