随笔微信扫码登录(SpringBoot项目)

这是微信的开发文档:微信登录功能 / 网站应用微信登录开发指南 (qq.com)

微信扫码登录流程

1,用户访问网页,网页向微信开放平台发送请求,获取一个access_token和一个ticket。

2,网页根据ticket向微信开放平台请求二维码的地址,并将二维码显示在网页上。

3,用户使用手机微信扫描二维码,微信开放平台会向网页发送一个事件推送,包含一个授权码。

4,网页根据授权码向微信开放平台请求用户信息,并完成登录逻辑。

具体实现

那我们逻辑明白要怎么写代码呢?

前提:在微信开发者平台完成资历验证(要200块)

首先,编写一个生成二维码的地址并重定向返回给前端的,用来让用户扫码登录。(默认Springboot,SpringMVC都有所了解)。

里面ConstantPropertiesUtil是初始化属性类,用于获取properties文件的数据:

package com.atguigu.educenter.util;

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

@Component
//@PropertySource("classpath:application.properties")//解释的文件
public class ConstantPropertiesUtil implements InitializingBean {

    @Value("${wx.open.app_id}")//微信开发者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;
    }
}

 注:为什么这里使用InitializingBean获取properties数据,而不是使用@ConfigurationProperties()的原因是:

使用InitializingBean可以在bean的属性被设置后,做一些自定义的操作,比如把非静态变量的值赋值给静态变量。(主要是这个)

使用InitializingBean可以避免使用反射调用init-method指定的方法,提高效率。

使用InitializingBean可以实现解耦,把一些配置信息写在配置文件中,然后通过@Value注解来获取。

下面的微信开放平台授权baseUrl有六个参数:

appid                    应用唯一标识

redirect_uri           请使用urlEncode对链接进行处理

response_type     填code

scope                   应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login

state(不必须)    用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验

lang(不必须)     界面语言,支持cn(中文简体)与en(英文),默认为cn

用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数。
 

@GetMapping("login")
    public String getWxCode(HttpSession session) {

        // 微信开放平台授权baseUrl
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";

        // 回调地址
        String redirectUrl = ConstantPropertiesUtil.WX_OPEN_REDIRECT_URL; //获取业务服务器重定向地址
        try {
            redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8"); //url编码
        } catch (UnsupportedEncodingException e) {
            throw new GuliException(20001, e.getMessage());
        }

        String state = "xxxx";//这里填写你在ngrok的前置域名
        System.out.println("state = " + state);


        //生成qrcodeUrl
        String qrcodeUrl = String.format(
                baseUrl,
                ConstantPropertiesUtil.WX_OPEN_APP_ID,
                redirectUrl,
                state);

        return "redirect:" + qrcodeUrl;//重定向显示二维码
    }

然后接收微信的回调信息,获取用户的信息,并生成token返回给前端的

HttpClientUtils.get()方法:HttpClientUtils是自定义的一个模拟浏览器请求的工具类,get方法用来发送一个HTTP GET请求的方法使用了以下两个包:

org.apache.httpcomponents:httpclient: 这是一个用于发送和接收HTTP请求和响应的依赖,它是Apache HttpComponents项目的一部分,提供了HttpClient, HttpGet, HttpResponse等类,可以帮助你实现HTTP客户端的功能。

org.apache.commons:commons-io: 这是一个用于处理输入输出操作的依赖,它是Apache Commons项目的一部分,提供了IOUtils, FileUtils, FilenameUtils等工具类,可以帮助你简化输入输出相关的代码。

<!-- Apache HttpClient -->
  <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
  </dependency>
  <!-- Apache Commons IO -->
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
  </dependency>

它的参数和返回值如下:

url: 一个字符串,表示要请求的网址。

charset: 一个字符串,表示响应的字符编码,例如 "UTF-8"。

connTimeout: 一个整数,表示连接超时的时间,单位是毫秒,如果为null,则使用默认值

readTimeout: 一个整数,表示读取超时的时间,单位是毫秒,如果为null,则使用默认值。

result返回值: 一个字符串,表示响应的内容。

	/**
	 * 发送一个 GET 请求
	 *
	 * @param url
	 * @param charset
	 * @param connTimeout  建立链接超时时间,毫秒.
	 * @param readTimeout  响应超时时间,毫秒.
	 * @return
	 * @throws ConnectTimeoutException   建立链接超时
	 * @throws SocketTimeoutException   响应超时
	 * @throws Exception
	 */
	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 = HttpClientUtils.client;
				res = client.execute(get);
			}

			result = IOUtils.toString(res.getEntity().getContent(), charset);
		} finally {
			get.releaseConnection();
			if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
				((CloseableHttpClient) client).close();
			}
		}
		return result;
	}

下面的baseAccessTokenUrl对应的参数:

appid                唯一标识,在微信开放平台提交应用审核通过后获得

secret               密钥AppSecret,在微信开放平台提交应用审核通过后获得

code                 填写上面获取的code参数

grant_type        填authorization_code

请求完后Url返回:

access_token    接口调用凭证

expires_in    access_token接口调用凭证超时时间,单位(秒)

refresh_token    用户刷新access_token

openid    授权用户唯一标识

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

unionid    当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。

{ 
"access_token":"ACCESS_TOKEN", 
"expires_in":7200, 
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID", 
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

下面的baseUserInfoUrl对应的参数:

access_token         调用凭证

openid                    普通用户的标识,对当前开发者账号唯一

lang(不必须)           国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语,默认为en

返回值:

openid    普通用户的标识,对当前开发者账号唯一

nickname    普通用户昵称

sex    普通用户性别,1为男性,2为女性

province    普通用户个人资料填写的省份

city    普通用户个人资料填写的城市

country    国家,如中国为CN

headimgurl    用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空

privilege    用户特权信息,json数组,如微信沃卡用户为(chinaunicom)

unionid    用户统一标识。针对一个微信开放平台账号下的应用,同一用户的unionid是唯一的。

{
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":1,
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"

}

callback具体代码: 

callback():这个方法是用来接收微信的回调信息,获取用户的信息,并生成token返回给前端的。下面方法的详细解释:

它有三个参数,分别是code、state和session。它首先拼接了一个获取access_token的URL,其中包含了微信的appid、密钥和code等参数。然后它用HttpClientUtils.get()方法向这个URL发送请求,获取到一个包含access_token和openid的JSON字符串。

接着它用Gson.fromJson()方法将这个字符串转换成一个HashMap对象,并从中取出access_token和openid的值。然后它根据openid查询数据库中是否已经存在该用户,如果不存在,则再拼接一个获取用户信息的URL,其中包含了access_token和openid等参数。再次用HttpClientUtils.get()方法向这个URL发送请求,获取到一个包含用户昵称和头像等信息的JSON字符串。

再用Gson.fromJson()方法将这个字符串转换成一个HashMap对象,并从中取出用户昵称和头像的值。然后创建一个UcenterMember对象,并将openid、昵称和头像等属性赋值给它,并保存到数据库中。如果已经存在该用户,则直接从数据库中获取该用户的信息。最后用JwtUtils.getJwtToken()方法根据用户的id和昵称生成一个token,并用redirect()方法将其返回给前端。前端收到token后,就可以实现用户的登录状态。

@GetMapping("callback")
    public String callback(String code, String state, HttpSession session) {
        //向认证服务器发送请求换取access_token
        String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                "?appid=%s" +
                "&secret=%s" +
                "&code=%s" +
                "&grant_type=authorization_code";
        String accessTokenUrl = String.format(baseAccessTokenUrl,
                ConstantPropertiesUtil.WX_OPEN_APP_ID,
                ConstantPropertiesUtil.WX_OPEN_APP_SECRET,
                code);

        try {
            String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
            System.out.println("accessTokenInfo:"+accessTokenInfo);

            //使用gson将accessTokenInfo的JSON字符串转换map
            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){

                //访问微信的资源服务器,获取用户信息
                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);
                System.out.println("userInfo"+userInfo);
                HashMap mapUserInfo = gson.fromJson(userInfo, HashMap.class);
                String nickname = (String) mapUserInfo.get("nickname");
                String headimgurl = (String) mapUserInfo.get("headimgurl");

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

            //
            String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());

            //得到授权临时票据code
            System.out.println("code = " + code);
            System.out.println("state = " + state);
            return "redirect:http://localhost:8080?token="+jwtToken;
        } catch (Exception e) {
            e.printStackTrace();
            throw new  GuliException(20001,"登录异常");
        }

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值