首页> 博客> 微信一键登录功能的实现
25 02 2019
一、背景

最近在学习微信支付和扫码登录这块功能开发,了解到现在很多网站上都会有一些第三方登录的入口,比如:QQ、微信、微博等,这些用起来很方便快捷,直接通过扫码即可完成一键登录网站,从而省去了很多注册信息的填写,也就不需要牢记繁多的用户名和密码了。也间接避免了个人信息的暴露。非常之方便!本次,在闲暇之余记录一下微信扫码登录功能的实现教程。

需要用到的网站

二、前期准备工作
2.1、注册一个开放平台账号

登录微信开放平台官网 ,注册一个开发者账号。注意:这里注册的邮箱必须是没有注册过微信公众号开发平台的。邮箱类型没有限制。

点击注册:

2.2、新建一个应用

完成开放平台账号注册后,登录账户。进入管理中心,然后根据自己的应用类型(APP/网站站点/第三方平台)创建对应应用。本次是做网站的微信扫码登录,即选择新建一个网站应用。

2.3、填写网站站点相关信息

目前,微信开放平台新建网站应用的申请,只开放给企业主体,还没有开放给个人申请。个人如果想要学习微信开发相关的话,就需要注册个体工商户才行(这里省略n多字......)。

2.4、开放着账号认证

我们第一步注册的账号,还需要进行完成开发者资质的认证。也就是说,需要给微信平台那边交300RMB,每年都需要认证一次。

提交认证资料后,一般1到2个工作日,会有第三方审核平台打电话给你进行相关信息的确认,只要提交的审核资料没有问题,很快就能审核下来的。

2.5、新建的网站应用信息

完成开发者资质认证并且微信平台那边对新建网站应用审批通过后,就可进行相关开发工作了。网站详情如下:

三、熟读微信开发文档

本次开发工作,使用 SpringBoot 进行整合开发调试。开发之前,需要熟读微信开发文档(网站应用微信登录开发文档),了解相关步骤要求之后,方可进行代码的开发工作。

3.1、授权流程说明

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

微信OAuth2.0授权登录目前支持authorization_code模式,适用于拥有server端的应用授权。该模式整体流程 为:

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

获取access_token时序图:

3.2、请求接口获取CODE

第三方使用网站应用授权登录前请注意已获取相应网页授权作用域(scope=snsapi_login),则可以通过在PC端打开以下链接:

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

若提示“该链接无法访问”,请检查参数是否填写错误,如redirect_uri的域名与审核时填写的授权域名不一致或scope不为snsapi_login。

参数说明:

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

返回说明:

  • 用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数
redirect_uri?code=CODE&state=STATE
  • 若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数
redirect_uri?state=STATE
3.3、通过 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

参数说明:

  • appid:应用唯一标识,在微信开放平台提交应用审核通过后获得,必须
  • secret:应用密钥 AppSecret,在微信开放平台提交应用审核通过后获得,必须
  • code:填写第一步获取的code参数,必须
  • grant_type:填 authorization_code,必须

返回说明:

请求返回的内容是 json 格式的字符串,成功的数据:

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

参数说明:

  • access_token:接口调用凭证,请求微信的接口这个是必须的
  • expires_in:access_token 接口调用凭证超时时间,单位(秒),有效期2小时
  • refresh_token:用户刷新 access_token,用于请求刷新 access_token 接口,刷新一次 access_token 有效期提升至一个月
  • openid:授权用户的唯一标识
  • scope:用户授权作用域,使用逗号(,)分隔
  • unionid:当且仅当该网站应用已获得该用户的 userinfo 授权时,才会出现该字段。

错误返回的数据:

{"errcode":40029,"errmsg":"invalid code"}

刷新access_token有效期

access_token 是调用授权关系接口的调用凭证,由于 access_token 有效期(目前为2个小时)较短,当access_token 超时后,可以使用 refresh_token 进行刷新,但是此步骤非必须的。access_token 刷新结果有两种:

1. 若access_token已超时,那么进行refresh_token会获取一个新的access_token,新的超时时间;
2. 若access_token未超时,那么进行refresh_token不会改变access_token,但超时时间会刷新,相当于续期access_token。

refresh_token拥有较长的有效期(30天),当refresh_token失效的后,需要用户重新授权。

  • 请求方法:
获取第一步的code后,请求以下链接进行refresh_token:
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN

参数说明:
	appid:应用唯一标识,必须
 	grant_type:填写 refresh_token,必须
 	refresh_token:填写通过access_token获取到的 refresh_token 参数,必须

正确返回:
	{
	"access_token":"ACCESS_TOKEN", 
	"expires_in":7200, 
	"refresh_token":"REFRESH_TOKEN", 
	"openid":"OPENID", 
	"scope":"SCOPE" 
	}

参数说明:
	access_token:	接口调用凭证
	expires_in:	access_token 接口调用凭证超时时间,单位(秒)
	refresh_token:	用户刷新 access_token
	openid:	授权用户唯一标识
	scope:	用户授权的作用域,使用逗号(,)分隔

错误返回:
	{"errcode":40030,"errmsg":"invalid refresh_token"}

备注:

1、Appsecret 是应用接口使用密钥,泄漏后将可能导致应用数据泄漏、应用的用户数据泄漏等高风险后果;存储在客户端,极有可能被恶意窃取(如反编译获取Appsecret);
2、access_token 为用户授权第三方应用发起接口调用的凭证(相当于用户登录态),存储在客户端,可能出现恶意获取access_token 后导致的用户数据泄漏、用户微信相关接口功能被恶意发起等行为;
3、refresh_token 为用户授权第三方应用的长效凭证,仅用于刷新access_token,但泄漏后相当于access_token 泄漏,风险同上。

建议将secret、用户数据(如access_token)放在App云端服务器,由云端中转接口调用请求。

3.4、通过获取的 access_token 请求接口获取用户信息

获取access_token后,进行接口调用,有以下前提:

  • access_token 有效且未超时;
  • 微信用户已授权给第三方应用帐号相应接口作用域(scope)。

获取用户个人信息(UnionID机制):

此接口用于获取用户个人信息。开发者可通过 OpenID 来获取用户基本信息。特别需要注意的是,如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的 unionid 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号,用户的 unionid 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid 是相同的。请注意,在用户修改微信头像后,旧的微信头像 URL 将会失效,因此开发者应该自己在获取用户信息后,将头像图片保存下来,避免微信头像URL失效后的异常情况。

请求接口:

http请求方式: GET
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID

参数说明:

  • access_token:调用凭证,必须
  • openid:普通用户的标识,对当前开发者帐号唯一,必须
  • lang:国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语,默认为 zh-CN,设置微信后台给我们返回的数据语言类型

正确返回结果:

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

}

参数说明:
	openid:	普通用户的标识,对当前开发者帐号唯一
	nickname:	普通用户昵称
	sex:	普通用户性别,1为男性,2为女性
	province:	普通用户个人资料填写的省份
	city:	普通用户个人资料填写的城市
	country:	国家,如中国为CN
	headimgurl:	用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
	privilege:	用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)
	unionid:	用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
建议:
	开发者最好保存用户 unionID 信息,以便以后在不同应用中进行用户信息互通。

错误返回结果:

{
"errcode":40003,"errmsg":"invalid openid"
}

相关接口调用频率限制:

接口名							频率限制

通过code换取access_token	   1万/分钟
刷新access_token				 5万/分钟
获取用户基本信息				5万/分钟
四、代码开发
4.1、微信配置类 WechatConfig.java
package com.xmlvhy.xmclass.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * Author: 小莫
 * Date: 2019-02-13 15:22
 * Description:微信配置类
 */
@Configuration
@PropertySource("classpath:application.properties")
@Data
public class WechatConfig {

    /*微信开放平台 appid*/
    @Value("${wxopen.appid}")
    private String openAppid;

    /*微信开放平台秘钥*/
    @Value("${wxopen.appsecret}")
    private String openAppSecret;

    /*微信开放平台回调地址*/
    @Value("${wxopen.redirect_url}")
    private String openRedirectUrl;

    /*微信开放平台二维码连接*/
    public final static String OPEN_QRCODE_URL="https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect";

    /*微信开放平台获取access_token地址*/
    public final static String OPEN_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";

    /*获取用户信息*/
    public final static String OPEN_USER_INFO_URL="https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";

}

4.2、返回结果封装类 JsonData.java
package com.xmlvhy.xmclass.entity;

import lombok.Data;

import java.io.Serializable;

/**
 * 功能描述:工具类
 *
 * <p> 创建时间:May 14, 2018 7:58:06 PM </p>
 */
@Data
public class JsonData implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private Integer code; // 状态码 0 表示成功,1表示处理中,-1表示失败
	private Object data; // 数据
	private String msg;// 描述

	public JsonData() {
	}

	public JsonData(Integer code, Object data, String msg) {
		this.code = code;
		this.data = data;
		this.msg = msg;
	}

	// 成功,传入数据
	public static JsonData buildSuccess() {
		return new JsonData(0, null, null);
	}

	// 成功,传入数据
	public static JsonData buildSuccess(Object data) {
		return new JsonData(0, data, null);
	}

	// 失败,传入描述信息
	public static JsonData buildError(String msg) {
		return new JsonData(-1, null, msg);
	}

	// 失败,传入描述信息,状态码
	public static JsonData buildError(String msg, Integer code) {
		return new JsonData(code, null, msg);
	}

	// 成功,传入数据,及描述信息
	public static JsonData buildSuccess(Object data, String msg) {
		return new JsonData(0, data, msg);
	}

	// 成功,传入数据,及状态码
	public static JsonData buildSuccess(Object data, int code) {
		return new JsonData(code, data, null);
	}
}
4.3、实体类 User.java
package com.xmlvhy.xmclass.entity;


import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

/**
 *功能描述: 用户实体类
 * @Author 小莫
 * @Date 15:16 2019/02/13
 * @Param  
 * @return 
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

  private Integer id;
  private String openid;
  private String name;
  private String headImg;
  private String phone;
  private String sign;
  private Integer sex;
  private String city;
  @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", locale="zh", timezone="GMT+8")
  private Date createTime;
}
4.4、dao层 UserMapper.java
package com.xmlvhy.xmclass.mapper;

import com.xmlvhy.xmclass.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

/**
 * Author: 小莫
 * Date: 2019-02-15 10:23
 * Description:<描述>
 */
public interface UserMapper {

    /**
     *功能描述: 根据主键 id 查询一个用户
     * @Author 小莫
     * @Date 10:25 2019/02/15
     * @Param [userId] 
     * @return com.xmlvhy.xmclass.entity.User
     */
    @Select("select * from user where id = #{id}")
    User selectById(@Param("id") int userId);

    /**
     *功能描述: 根据用户的微信 openId 查询一个用户
     * @Author 小莫
     * @Date 10:28 2019/02/15
     * @Param [openId] 
     * @return com.xmlvhy.xmclass.entity.User
     */
    @Select("select * from user where openid = #{openid}")
    User selectByOpenId(@Param("openid") String openId);

    /**
     *功能描述: 保存用户信息
     * @Author 小莫
     * @Date 10:29 2019/02/15
     * @Param [user] 
     * @return int
     */
    @Insert("insert into `user` " +
            "(`openid`, `name`, `head_img`, `phone`, `sign`, `sex`, `city`, `create_time`) " +
            "values" +
            "(#{openid},#{name},#{headImg},#{phone},#{sign},#{sex},#{city},#{createTime});")
    @Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id")
    int insertUser(User user);
}
4.5、service 层接口 UserService.java
package com.xmlvhy.xmclass.service;

import com.xmlvhy.xmclass.entity.User;

/**
 * Author: 小莫
 * Date: 2019-02-14 18:11
 * Description:用户业务接口类
 */
public interface UserService {

    User saveWechatUser(String code);
}
4.6、service 层接口实现类 UserServiceImpl.java
package com.xmlvhy.xmclass.service.impl;

import com.xmlvhy.xmclass.config.WechatConfig;
import com.xmlvhy.xmclass.entity.User;
import com.xmlvhy.xmclass.mapper.UserMapper;
import com.xmlvhy.xmclass.service.UserService;
import com.xmlvhy.xmclass.utils.HttpUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;

/**
 * Author: 小莫
 * Date: 2019-02-14 18:12
 * Description:<描述>
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private WechatConfig wechatConfig;

    @Autowired
    private UserMapper userMapper;

    @Override
    public User saveWechatUser(String code) {

        String accessTokenUrl = String.format(WechatConfig.OPEN_ACCESS_TOKEN_URL,
                wechatConfig.getOpenAppid(), wechatConfig.getOpenAppSecret(), code);

        //获取 access_token
        Map<String, Object> result = HttpUtils.doGet(accessTokenUrl);
        if (result == null || result.isEmpty()) {
            return null;
        }
        String access_token = (String) result.get("access_token");
        String openId = (String) result.get("openid");

        User dbUser = userMapper.selectByOpenId(openId);
        if (dbUser != null) { //用户存在,直接返回
            return dbUser;
        }

        //获取用户基本信息
        String userInfoUrl = String.format(WechatConfig.OPEN_USER_INFO_URL,access_token,openId);
        Map<String, Object> map = HttpUtils.doGet(userInfoUrl);

        String nickname = (String) map.get("nickname");
        //try {
        //    //解决乱码
        //    nickname = new String(nickname.getBytes("ISO-8859-1"), "UTF-8");
        //} catch (UnsupportedEncodingException e) {
        //    e.printStackTrace();
        //}

        Double sexTemp = (Double) map.get("sex");
        int sex = sexTemp.intValue();

        String province = (String) map.get("province");
        String city = (String) map.get("city");
        String country = (String) map.get("country");
        StringBuffer sb = new StringBuffer(country).append("||").append(province).append("||").append(city);
        String finalAddress = sb.toString();

        String headimgurl = (String) map.get("headimgurl");

        User user = new User();
        user.setName(nickname);
        user.setSex(sex);
        user.setHeadImg(headimgurl);
        user.setOpenid(openId);
        user.setCity(finalAddress);
        user.setCreateTime(new Date());
        userMapper.insertUser(user);
        return user;
    }
}
4.7、编写 WechatController.java
package com.xmlvhy.xmclass.controller;

import com.xmlvhy.xmclass.config.WechatConfig;
import com.xmlvhy.xmclass.entity.JsonData;
import com.xmlvhy.xmclass.entity.User;
import com.xmlvhy.xmclass.entity.VideoOrder;
import com.xmlvhy.xmclass.service.UserService;
import com.xmlvhy.xmclass.service.VideoOrderService;
import com.xmlvhy.xmclass.utils.JwtUtils;
import com.xmlvhy.xmclass.utils.WxPayUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;

/**
 * Author: 小莫
 * Date: 2019-02-14 16:59
 * Description:<描述>
 */
@Controller
@RequestMapping("/api/v1/wechat")
@Slf4j
public class WechatController {

    @Autowired
    private WechatConfig wechatConfig;

    @Autowired
    private UserService userService;

    /**
     * 功能描述: 用户扫码登录接口
     *
     * @return com.xmlvhy.xmclass.entity.JsonData
     * @Author 小莫
     * @Date 11:03 2019/02/15
     * @Param [accessPage]
     */
    @GetMapping("login_url")
    @ResponseBody
    public JsonData loginUrl(@RequestParam(value = "access_page", required = true) String accessPage) throws UnsupportedEncodingException {
        //获取开放平台重定向地址
        String redirectUrl = wechatConfig.getOpenRedirectUrl();
        //进行编码
        String callbackUrl = URLEncoder.encode(redirectUrl, "GBK");
        //调出扫一扫二维码
        String qrcodeUrl = String.format(WechatConfig.OPEN_QRCODE_URL, wechatConfig.getOpenAppid(), callbackUrl, accessPage);

        return JsonData.buildSuccess(qrcodeUrl);
    }

    /**
     * 功能描述: 微信网页扫码登录回调接口
     *
     * @return void
     * @Author 小莫
     * @Date 11:03 2019/02/15
     * @Param [code, state, response]
     */
    @GetMapping("/user/callback")
    public void wechatUserCallback(@RequestParam(value = "code", required = true) String code,
                                   String state, HttpServletResponse response) throws IOException {
        log.info("code= {} ,state= {}", code, state);
        User user = userService.saveWechatUser(code);
        if (user != null) {
            //生成jwt
            String token = JwtUtils.geneJsonWebToken(user);
            //state 表示当前用户的扫码登录需要重定向的页面地址,需要拼接 http:// 这样才不会站内跳转
            String redirectUrl = state + "?token=" + token + "&head_img=" + user.getHeadImg()
                    + "&name=" + URLEncoder.encode(user.getName(), "UTF-8");
            log.info("redirectUrl ={}", redirectUrl);
            //response.sendRedirect("https://www.xmlvhy.com/blog");
            response.sendRedirect(redirectUrl);
        }
    }
}

4.8、编写HttpClient4.x工具类

加入相关依赖:

			<dependency>
				<groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.3</version>
 			</dependency>
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpmime</artifactId>
                <version>4.5.2</version>
            </dependency>

            <dependency>
                <groupId>commons-codec</groupId>
                <artifactId>commons-codec</artifactId>
            </dependency>
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.1.1</version>
            </dependency>
                <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpcore</artifactId>
            </dependency>
			 <!-- gson工具,封装http的时候使用 -->
        	<dependency>
            	<groupId>com.google.code.gson</groupId>
            	<artifactId>gson</artifactId>
            	<version>2.8.0</version>
       		</dependency>

封装doGet doPost 方法:

package com.xmlvhy.xmclass.utils;

import com.google.gson.Gson;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.util.HashMap;
import java.util.Map;

/**
 * 封装http get post
 */
public class HttpUtils {

    private static  final Gson gson = new Gson();
    /**
     * get方法
     * @param url
     * @return
     */
    public static Map<String,Object> doGet(String url){

        Map<String,Object> map = new HashMap<>();
        CloseableHttpClient httpClient =  HttpClients.createDefault();
        //设置参数
        RequestConfig requestConfig =  RequestConfig.custom().setConnectTimeout(5000) //连接超时
                .setConnectionRequestTimeout(5000)//请求超时
                .setSocketTimeout(5000)
                .setRedirectsEnabled(true)  //允许自动重定向
                .build();

        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(requestConfig);

        try{
           // 获取请求响应结果
           HttpResponse httpResponse = httpClient.execute(httpGet);
           if(httpResponse.getStatusLine().getStatusCode() == 200){
               String jsonResult = EntityUtils.toString(httpResponse.getEntity());
               //解决乱码
               jsonResult = new String(jsonResult.getBytes("ISO-8859-1"), "UTF-8");
               map = gson.fromJson(jsonResult,map.getClass());
           }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                httpClient.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return map;
    }

    /**
     * 封装post
     * @return
     */
    public static String doPost(String url, String data, int timeout){
        CloseableHttpClient httpClient =  HttpClients.createDefault();
        //超时设置
        RequestConfig requestConfig =  RequestConfig.custom().setConnectTimeout(timeout) //连接超时
                .setConnectionRequestTimeout(timeout)//请求超时
                .setSocketTimeout(timeout)
                .setRedirectsEnabled(true)  //允许自动重定向
                .build();

        HttpPost httpPost  = new HttpPost(url);
        httpPost.setConfig(requestConfig);
        httpPost.addHeader("Content-Type","text/html; chartset=UTF-8");

        if(data != null && data instanceof  String){ //使用字符串传参
            StringEntity stringEntity = new StringEntity(data,"UTF-8");
            httpPost.setEntity(stringEntity);
        }

        try{
            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            if(httpResponse.getStatusLine().getStatusCode() == 200){
                String result = EntityUtils.toString(httpEntity);
                return result;
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                httpClient.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return null;
    }
}
4.9、SpringBoot 配置文件信息
spring.devtools.restart.trigger-file=trigger.txt
spring.profiles.active=dev

#==========================微信相关=============================
# 微信开放平台配置
wxopen.appid=填写自己的appid
wxopen.appsecret=填写自己的appsecret
# 重定向url,回调地址
wxopen.redirect_url=http://xiaomo.mynatapp.cc/api/v1/wechat/user/callback

#=========================数据库通用配置========================
#可以自动识别
#spring.datasource.driver-class-name =com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/xmclass?useSSL=false&useUnicode=true&characterEncoding=utf-8
spring.datasource.username =数据库账户
spring.datasource.password =数据库密码
#如果不使用默认的数据源 (com.zaxxer.hikari.HikariDataSource)
#spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.maxActive=100
spring.datasource.minIdle=3
spring.datasource.maxWait=50000

#==========================mybatis配置==========================
# mybatis 下划线转驼峰配置,两者都可以
#mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.configuration.map-underscore-to-camel-case=true

五、结果展示

打开浏览器,地址栏输入:

http://localhost:8080/api/v1/wechat/login_url?access_page=http://www.xmlvhy.com/blog

点击微信扫一扫链接或直接右击在新标签中打开链接,并且扫码:

手机端需要确认登录:

点击确认登录后,微信后台会自动回调我们设置的 URL接口,从而重定向到我们设置的 URL(http://www.xmlvhy.com/blog

此时我们看下断点信息,看请求获取用户个人信息是否成功返回:

如图,可知用户的信息都成功拿到了!至此,我们便完成了,微信扫码授权登录的功能!

微信开发核心是要读懂并熟悉微信开发文档!

本篇博客涉及的 获取源码(源码中有微信支付功能模块,此模块后续会在另外一篇博客详情记录!源码的话就跟微信扫码登录一同提供了!)

类似文章

  1. 没有了

评论区

| 2 评论
  • NullPointException
    我想一下的就是你这个账号是个人账号还是企业账号呢?

    AI码真香 博主 回复 NullPointException 我的是个体工商户申请的,微信那边现在还没提供给个人申请的好像。

    中国上海上海市 2020-06-24 17:27:28 回复