草庐IT

如何实现微信扫码登录--OAuth2

何苏三月 2023-08-25 原文

第三方登录

用户不想去注册你的网站,觉得输入这些信息麻烦。更愿意像直接扫码进行登录这样,简单。

扫码人的信息自动注册,加入到你的数据库中。

微信登录是一个固定的流程,是腾讯规定的固定流程去做。

微信登录讲解之前,先讲解OAuth2

OAuth2是什么?

OAuth2是针对特定问题的一种解决方案。

主要可以解决两个问题:

   1. 开放系统间授权   2.分布式访问问题

开放系统间授权:

lucy照了很多照片,她把它们都存到了自己的百度网盘上去了。但是百度网盘没法打印照片啊,某公司自己研发了一款系统,专门用于这种云存储的打印服务。

但是,默认情况下,这款系统默认是不能操作lucy存到她百度网盘上的东西的。没有权限。

但是百度网盘提供了授权服务的相关方法。

这个时候,只要lucy授权给这款打印系统,那么就可以实现打印服务。

具体怎么做到的,这就是OAuth2要做的事情。也是百度网盘要规定的流程。

分布式访问

我们只需要把token相关的代码抽取出来,生成一个公共的类。

这样,即使是不同的模块之间,他们的token创建和解析的加密方式也都是一样的,也就能解析到cookie中传过来的token信息,进行登录。

当然了,我们可以使用redis存储token以及对应用户信息,这样不同模块就都能拿到用户信息。否则的话,我们只知道是当前用户,当前用户的用户信息,也只是token解析出来的信息。比如用户id。当然,因为用户id具有唯一性嘛,所以通过用户id关联自己模块的业务也是可以的。比如:某人登录了视频服务,在视频服务会存储一个视频记录的数据表。这个时候,只要数据表关联用户id就可以了。对于视频服务而言,是没有问题的。

OAuth2解决方案:按照一定规则生成字符串,字符串包含用户信息。

OAuth2只是一种解决方案,这种方案具体怎么做由方案提供者自己定。

下面我们开始进行微信扫码登录:

准备工作

1. 首先你想要使用腾讯公司微信做登录这样的相关操作,必须在腾讯的开发者平台注册,获得资质!微信开放平台

(1) 支持企业类型(以前只支持企业注册)

(2) 注册之后,会给你提供微信id和微信密钥

ps:注册需要准备营业执照、1-2个工作日审批、300元认证费

2. 申请网站应用名称。

就是说你扫描二维码,它会显示当前网站的应用名称,比如:我的谷粒。这个一般在7个工作日内审批。

3. 需要域名地址。

地址作用:微信扫完二维码之后,找你的域名做跳转。

熟悉微信登录流程:

下面我们快速看一下

参考文档:微信开放平台

1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;
3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

后端开发(重点)

1、添加配置

在你的用户模块中,application.properties添加相关配置信息

# 微信开放平台 appid
wx.open.app_id=你的appid
# 微信开放平台 appsecret
wx.open.app_secret=你的appsecret
# 微信开放平台 重定向url
wx.open.redirect_url=http://你的服务器名称/api/ucenter/wx/callback

2、创建常量类

创建util包,创建ConstantWxUtils.java常量类

package com.atguigu.educenter.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ConstantWxUtils 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;

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

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

3、生成微信扫描的二维码

直接请求微信提供的固定的地址,向地址的后面拼接参数即可。

即重定向到微信的二维码生成地址去。

https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirects

参数说明:

参数是否必须说明
appid应用唯一标识
redirect_uri请使用urlEncode对链接进行处理
response_type填code
scope应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即
state用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验

在当前educenter模块中创建api包

api包中创建WxApiController

代码的写法

package com.atguigu.educenter.controller;

import com.atguigu.educenter.utils.ConstantWxUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.net.URLEncoder;

@CrossOrigin
@Controller  //只是请求地址,不需要返回数据,这个时候不能用@RestController
@RequestMapping("/api/ucenter/wx")
public class WxApiController {


    //1 生成微信扫描二维码
    @GetMapping("login")
    public String getWxCode() {
        //固定地址,后面拼接参数
        //写法一(这么做效率很低,多了还容易写错):
//        String url = "https://open.weixin.qq.com/connect/qrconnect" +
//                "?appid="+
//                ConstantWxUtils.WX_OPEN_APP_ID +
//                "&redirect_uri=" +
//                ConstantWxUtils.WX_OPEN_REDIRECT_URL +
//                "&response_type=code" +
//                "&scope=snsapi_login" +
//                "state=atguigu" +
//                "#wechat_redirect";
        //当然,上面这个redirect_uri地址要先编码再进行传,不能那样直接传。

        //写法二(推荐)
        // 微信开放平台授权baseUrl  %s 相当于?标识占位符
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";

        //对redirect_url进行URLEncoder编码
        String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
        try {
            redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
        }catch(Exception e) {
        }

        //设置%s里面值
        String url = String.format(
                    baseUrl,
                    ConstantWxUtils.WX_OPEN_APP_ID,
                    redirectUrl,
                    "atguigu"
                 );

        //重定向到请求微信地址里面
        return "redirect:" + url;
    }
}

此时启动改服务,然后请求当前接口:localhost:8160/api/ucenter/wx/login

它会根据你接口中写的代码,重定向到微信的二维码生成地址,拼接的参数就是你传的参数

比如:

https://open.weixin.qq.com/connect/qrconnect?appid=wxed9954c01bb80b37&redirect_uri=http%3A%2F%2Flocalhost%3A8160%2Fapi%2Fucenter%2Fwx%2Fcallback&response_type=code&scope=snsapi_login&state=atguigu#wechat_redirect

 4.扫描后获取用户信息的过程

扫描成功确认之后,微信会获取到用户的一个code值。然后微信会把code值给到我们,跳转到如下我们的回调地址,地址后拼接了code参数和state参数:

localhost:8160/api/ucenter/wx/callback?code=021Grg0w34r37Z2c9S2w3xmv180Grg0o&state=atguigu

api/ucenter/wx/callback肯定不是我们的地址,我们还没有写这个方法。

ps:这个回调地址就是我们之前在配置文件中配置的redirect_url重定向地址,它是当初注册资质时填写的域名地址。在获取验证码时就传递给了微信,这个时候微信在将地址返回给我们。

wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback

这个回调方法的作用就是用于我们获取用户信息。微信说让我们用这个接口去请求用户信息。

下面我们就需要自己来写这个回调方法,在方法里面,通过获取到微信传回来的code值,带着这个值去请求微信另外的一个固定地址获取asses_token,openid

通过httpclint直接请求微信,然后获取到返回的accsess_token 和 openid信息。

接着我们可以把它当前用户信息加入到自己的数据库。(你也可以选择不加入)

首先判断当前用户的openid是否在自己的数据库已经存在,如果存在不用添加了。

如果不存在,那么就通过这个accsess_token 和 openid再去请求微信的又一个固定地址,获取用户信息。

还是通过HttpClient发送请求,然后接收返回值。

一样也需要通过json转换工具转换成字符串。否则是json格式的数据无法操作。

拿到用户信息后我们就可以加入到自己的数据库里面了。

我们看看代码怎么写:

package com.atguigu.educenter.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.atguigu.commonutils.JwtUtils;
import com.atguigu.educenter.bean.UcenterMember;
import com.atguigu.educenter.service.UcenterMemberService;
import com.atguigu.educenter.utils.ConstantWxUtils;
import com.atguigu.educenter.utils.HttpClientUtils;
import com.atguigu.servicebase.exceptionHandler.GuliException;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

@CrossOrigin
@Controller  //只是请求地址,不需要返回数据。这个时候不能用@RestController
@RequestMapping("/api/ucenter/wx")
public class WxApiController {

    @Autowired
    private UcenterMemberService memberService;

    //2 获取扫描人信息,添加数据
    @GetMapping("callback")
    public String callback(String code, String state) {
        try {
            //1 获取code值,临时票据,类似于验证码
            //2 拿着code请求 微信固定的地址,得到两个值 accsess_token 和 openid
            String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                    "?appid=%s" +
                    "&secret=%s" +
                    "&code=%s" +
                    "&grant_type=authorization_code";
            //拼接三个参数 :id  秘钥 和 code值
            String accessTokenUrl = String.format(
                    baseAccessTokenUrl,
                    ConstantWxUtils.WX_OPEN_APP_ID,
                    ConstantWxUtils.WX_OPEN_APP_SECRET,
                    code
            );
            //请求这个拼接好的地址,得到返回两个值 accsess_token 和 openid
            //使用httpclient发送请求,得到返回结果
            String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);

            //从accessTokenInfo字符串获取出来两个值 accsess_token 和 openid
            //把accessTokenInfo字符串转换map集合,根据map里面key获取对应值
            //使用json转换工具 Gson
            Gson gson = new Gson();
            HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
            String access_token = (String)mapAccessToken.get("access_token");
            String openid = (String)mapAccessToken.get("openid");


            //把扫描人信息添加数据库里面
            //判断数据表里面是否存在相同微信信息,根据openid判断
            UcenterMember member = memberService.getOpenIdMember(openid);
            if(member == null) {//memeber是空,表没有相同微信数据,进行添加

                //3 拿着得到accsess_token 和 openid,再去请求微信提供固定的地址,获取到扫描人信息
                //访问微信的资源服务器,获取用户信息
                String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                        "?access_token=%s" +
                        "&openid=%s";
                //拼接两个参数
                String userInfoUrl = String.format(
                        baseUserInfoUrl,
                        access_token,
                        openid
                );
                //发送请求
                String userInfo = HttpClientUtils.get(userInfoUrl);
                //获取返回userinfo字符串扫描人信息
                HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
                String nickname = (String)userInfoMap.get("nickname");//昵称
                String headimgurl = (String)userInfoMap.get("headimgurl");//头像

                member = new UcenterMember();
                member.setOpenid(openid);
                member.setNickname(nickname);
                member.setAvatar(headimgurl);
                memberService.save(member);
            }

            //使用jwt根据member对象生成token字符串
            String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());
            //最后:返回首页面,通过路径传递token字符串
            return "redirect:http://localhost:3000?token="+jwtToken;
        }catch(Exception e) {
            throw new GuliException(20001,"登录失败");
        }
    }

    //1 生成微信扫描二维码
    @GetMapping("login")
    public String getWxCode() {
        //固定地址,后面拼接参数
        //写法一(这么做效率很低,多了还容易写错):
//        String url = "https://open.weixin.qq.com/connect/qrconnect" +
//                "?appid="+
//                ConstantWxUtils.WX_OPEN_APP_ID +
//                "&redirect_uri=" +
//                ConstantWxUtils.WX_OPEN_REDIRECT_URL +
//                "&response_type=code" +
//                "&scope=snsapi_login" +
//                "state=atguigu" +
//                "#wechat_redirect";
        //当然,上面这个redirect_uri地址要先编码再进行传,不能那样直接传。

        //写法二(推荐)
        // 微信开放平台授权baseUrl  %s 相当于?标识占位符
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";

        //对redirect_url进行URLEncoder编码
        String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
        try {
            redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
        }catch(Exception e) {
        }

        //设置%s里面值
        String url = String.format(
                    baseUrl,
                    ConstantWxUtils.WX_OPEN_APP_ID,
                    redirectUrl,
                    "atguigu"
                 );

        //重定向到请求微信地址里面
        return "redirect:" + url;
    }
}

当然json转换工具你自己选择,fastjson也可以。比如:

    //对比Gson和FastJson    
    //Gson
    Gson gson = new Gson();
    HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
    String access_token = (String)mapAccessToken.get("access_token");
    String openid = (String)mapAccessToken.get("openid");

    //FastJson
    Map parse = (Map) JSON.parse(accessTokenInfo);
    String access_token1 = (String) parse.get("access_token");
    System.out.println(access_token1 +"这是fastjson解析出来的");

 我们这里怎么写都对,因为都是localhost,但是实际项目中,我们不建议这么做。

 然后在首页页面中显示用户信息

有关如何实现微信扫码登录--OAuth2的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  8. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  9. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  10. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

随机推荐