wxiao个人技术分享 wxiao的技术分享

OAuth2实现微信扫码登录第三方

⚠️ 本文最后更新于2024年09月12日,已经过了259天没有更新,若内容或图片失效,请留言反馈

OAuth2.0是什么

OAuth(Open Authotization)是一个关于授权的开放网络标准,允许用户授权第三方应用访问存储在另外的服务提供者上的信息,而且不需要将用户名和密码提供给第三方应用。

优点

用户安全性: OAuth 2.0允许用户授权第三方应用程序访问其受保护的资源,而无需共享其凭据(例如用户名和密码)。这样可以大大降低用户的安全风险,因为用户不必将其敏感凭据直接提供给第三方应用程序。

用户体验: OAuth 2.0通过简化用户授权过程,提供了更好的用户体验。相比传统的用户名和密码验证,OAuth 2.0使得用户无需在每个第三方应用程序中输入其凭据,而只需在授权服务器上一次性授权即可。这大大简化了用户的操作流程,提高了用户体验。

授权管理: OAuth 2.0提供了灵活的授权机制,使得用户能够控制哪些第三方应用程序可以访问其资源,以及可以访问资源的范围。用户可以随时撤销对某个应用程序的访问权限,从而更好地管理其数据的安全性和隐私性。

客户端安全性: OAuth 2.0通过使用访问令牌而不是用户凭据来保护第三方应用程序与授权服务器之间的通信。这降低了客户端存储用户凭据的风险,并使得客户端更容易实现安全性措施。

适应多种场景: OAuth 2.0提供了多种授权类型,适用于不同的应用场景,包括Web应用程序、移动应用程序、服务端到服务端通信等。这使得OAuth 2.0成为了一个通用的授权框架,能够满足各种不同的需求。

角色

资源所有者(Resource Owner):拥有受保护资源的用户。该用户授予客户端对其资源的访问权限。
客户端(Client):第三方应用程序,希望访问资源所有者的受保护资源。它可以是网站、移动应用程序或其他类型的应用程序。
授权服务器(Authorization Server):负责认证资源所有者并颁发访问令牌给客户端。这是OAuth 2.0流程的核心组件之一。
资源服务器(Resource Server):托管受保护资源的服务器。客户端通过访问令牌与资源服务器通信以访问资源。

授权模式

授权码模式、隐式授权模式、用户名密码模式、客户端模式。

2024-09-12T03:49:13.png

测试公众号申请

https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

登录后会给我们appID、appsecret,还要填写接口配置信息
2024-09-12T06:09:58.png
这个时候需要使用内网穿透工具,地址https://www.cpolar.com
下面是我的
2024-09-12T06:20:34.png

2024-09-12T06:23:36.png

微信会去访问后端接口,请求会传上面的四个参数,其中关键的是echostr参数,我们必须原样返回。

@GetMapping("/check")
public String check(String signature, String timestamp, String nonce, String echostr) {
    // 校验
    return echostr;
}

然后就可以配置成功了,再做以下操作
2024-09-12T06:29:57.png
2024-09-12T06:30:45.png
2024-09-12T06:31:00.png

2024-09-12T06:32:06.png
给我们项目添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.3.0</version>
</dependency>

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.5</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>

用户同意授权,获取code
在确保微信公众账号拥有授权作用域(scope参数)的权限的前提下(已认证服务号,默认拥有scope参数中的snsapi_base和snsapi_userinfo 权限),引导关注者打开https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
2024-09-12T06:54:32.png
使用Hutool工具包来生成二维码

@GetMapping("/wxLogin")
public void wxLogin() throws IOException {

    // 回调地址
    String redirectUrl = URLEncoder.encode("http://626faac1.r11.cpolar.top/wxCallBack", StandardCharsets.UTF_8);
    // 二维码地址
    String appid = "填写appid";
    String url = "https://open.weixin.qq.com/connect/oauth2/authorize" +
            "?appid=" + appid +
            "&redirect_uri=" + redirectUrl +
            "&response_type=code" +
            "&scope=snsapi_userinfo" +
            "&state=STATE#wechat_redirect";
    // 生成二维码并返回
    response.setContentType("image/png");
    QrCodeUtil.generate(url, 300, 300, "png", response.getOutputStream());
}

通过code换取网页授权access_token

获取code后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
2024-09-12T06:55:28.png
2024-09-12T06:56:33.png

封装TokenInfo实体类:

// 网贞授权按口调用凭证,注意:此accessToken与基础支持的accessToken不同
private String accessToken;

//expiresIn接口调用凭证超时时间,单位(秒)
private Integer expiresIn;

// 用户剧新accessToken
private String refreshToken;

// 用户唯一标识
private String openid;

// 用户授权的作用域,使用逗号(,)分隔
private String scope;

private Integer isSnapshotuser;

private String unionid;

拉取用户信息(需scope为 snsapi_userinfo)
如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
2024-09-12T06:58:17.png

封装WxUserInfo实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class WxUserInfo {

    private String openid;

    private String nickname;

    private String sex;

    private String country;

    private String city;

    private String province;

    private String headimgurl;

    private String privilege;

    private String unionid;

}

回调接口

@RestController
@RequiredArgsConstructor
public class WxController {

    private final HttpServletResponse response;

    @GetMapping("/wxCallBack")
    public String wxCallBack(String code) throws IOException {
        WxUserInfo user = GetUserInfoUtils.getUser(code);
        return JSONObject.toJSONString(user);
    }


    @GetMapping("/wxLogin")
    public void wxLogin() throws IOException {

        // 回调地址
        String redirectUrl = URLEncoder.encode("http://626faac1.r11.cpolar.top/wxCallBack", StandardCharsets.UTF_8);
        // 二维码地址
        String appid = "";
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize" +
                "?appid=" + appid +
                "&redirect_uri=" + redirectUrl +
                "&response_type=code" +
                "&scope=snsapi_userinfo" +
                "&state=STATE#wechat_redirect";
        // 生成二维码并返回
        response.setContentType("image/png");
        QrCodeUtil.generate(url, 300, 300, "png", response.getOutputStream());
    }

    @GetMapping("/check")
    public String check(String signature, String timestamp, String nonce, String echostr) {
        // 校验
        return echostr;
    }
}

工具类

public class GetUserInfoUtils {

    public static WxUserInfo getUser(String code) throws IOException {

        // 构造HTTP请求
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 用code交换token
        String appid = "填写appid";
        String secret = "55f5b1fb35b29f8c575c4b1149ca643e";
        String tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                "?appid=" + appid +
                "&secret=" + secret +
                "&code=" + code +
                "&grant_type=authorization_code";
        // 发起请求
        HttpGet httpGet = new HttpGet(tokenUrl);
        String responseResult = "";
        // 接收返回的数据,转成utf-8编码
        HttpResponse response = httpClient.execute(httpGet);
        if (response.getStatusLine().getStatusCode() == 200) {
            responseResult = EntityUtils.toString(response.getEntity(), "utf-8");
        }
        // 封装到TokenInfo
        TokenInfo tokenInfo = JSON.parseObject(responseResult, TokenInfo.class);

        // 用access_token获取用户信息
        String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                "?access_token=" + tokenInfo.getAccessToken() +
                "&openid=" + tokenInfo.getOpenid() +
                "&lang=zh_CN";
        // 发起请求
        HttpGet httpGet1 = new HttpGet(userInfoUrl);
        // 接收返回的数据,转成utf-8编码
        HttpResponse response1 = httpClient.execute(httpGet1);
        if (response.getStatusLine().getStatusCode() == 200) {
            responseResult = EntityUtils.toString(response1.getEntity(), "utf-8");
        }
        // 封装到WxUserInfo
        WxUserInfo wxUserInfo = JSON.parseObject(responseResult, WxUserInfo.class);
        return wxUserInfo;
    }
}

访问http://626faac1.r11.cpolar.top/wxLogin会出现二维码,当微信扫码后出现返回的信息,就成功了

By xiao On