SpringCloud(四) - 微信获取用户信息

1、项目介绍

2、微信公众平台 和 微信开放文档

2.1 微信公众平台

2.1.1 网址链接

https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index

2.1.2 测试号信息

2.1.3 微信扫描关注测试公众号

2.1.4 授权回调页面域名

2.1.4.1 网页服务->网页账号->修改

2.1.4.2 填写 授权回调页面域名

2.1.4.3 内网穿透 NATAPP
2.1.4.3.1 使用教程
NATAPP1分钟快速新手图文教程: https://natapp.cn/article/natapp_newbie

下载: https://natapp.cn/#download

使用本地配置文件config.ini: https://natapp.cn/article/config_ini
2.1.4.3.2 authtoken

注意每次打开程序域名都会变化;

2.1.4.3.3 授权回调页面域名

2.2 微信开放文档

2.2.1 网址链接

https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#2

2.2.2 官方 基本步骤教程

1 第一步:用户同意授权,获取code

2 第二步:通过 code 换取网页授权access_token

3 第三步:刷新access_token(如果需要)

4 第四步:拉取用户信息(需 scope 为 snsapi_userinfo)

5 附:检验授权凭证(access_token)是否有效

3、http请求工具类 HttpClient4Util

HttpClient4Util 用来发http请求;

https://www.cnblogs.com/xiaoqigui/p/16839536.html

4、配置文件 和 配置类

4.1 配置文件

application.yml

#端口
server:
  port: 8096

# 自定义微信授权信息
wechat:
  auth:
    app-id: wxd4e20add67******   #  appID
    app-secret: a21e97d21d0d6ce408b7a6c******  # appsecret
    code-uri: https://open.weixin.qq.com/connect/oauth2/authorize  # 请求微信官方获取用户授权code 的请求地址
    redirect-uri: http://******.natappfree.cc/wechat/auth/codeBack # 微信官方返回  用户授权code 的回调地址
    access-token-uri: https://api.weixin.qq.com/sns/oauth2/access_token # 根据微信回调的code值,请求微信官方获取用户access_token  的请求地址
    user-info-uri: https://api.weixin.qq.com/sns/userinfo #根据用户的 accessToken 和  openId 拉取用户信息  的请求地址

4.2 配置类

//自定义微信授权参数信息配置类
@Data
@Component
@ConfigurationProperties(prefix = "wechat.auth")
public class WeChatAuthConfig {
    /*
        应用id
     */
    private String appId;
    /*
        应用密钥
     */
    private String appSecret;
    /*
        请求获取code的地址
     */
    private String codeUri;
    /*
        微信官方回调code的地址
     */
    private String redirectUri;
    /**
     * 微信官方获取access_token地址
     */
    private String accessTokenUri;
    /*
        微信官方获取userInfo地址
     */
    private String userInfoUri;

}

5、server 层

5.1 微信授权的业务接口

/**
 * Created On : 28/10/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 微信授权的业务接口
 */
public interface WeChatAuthService {

    //生成请求微信官方获取用户授权code的请求地址
    
    //根据微信回调的code值,请求微信官方获取用户access_token
    
    //根据用户的 accessToken 和  openId 拉取用户信息

}

5.1.1 生成请求微信官方获取用户授权code的请求地址

/**
* @author : huayu
* @date   : 28/10/2022
* @param  : []
* @return : java.lang.String
* @description : 生成请求微信官方获取用户授权code的请求地址
*/

String generateWeChatAuthCodeUrl();

5.1.2 根据微信回调的code值,请求微信官方获取用户access_token

/**
* @author : huayu
* @date   : 28/10/2022
* @param  : [wechatAuthCode]
* @return : java.lang.String
* @description : 根据微信回调的code值,请求微信官方获取用户access_token
*/
String getAccessTokenFromWechatUseCode(String wechatAuthCode);

5.1.3 根据用户的 accessToken 和 openId 拉取用户信息

/**
* @author : huayu
* @date   : 28/10/2022
* @param  : [accessToken, openId]
* @return : java.lang.String
* @description : 根据用户的 accessToken 和  openId 拉取用户信息
*/
String getUserInfoFromWechatUseAccessToken(String accessToken,String openId);

5.2 微信授权的业务接口 实现类

/**
 * Created On : 28/10/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 微信授权的业务接口 实现类
 */
@Service
@Slf4j
public class WeChatAuthServiceImpl implements WeChatAuthService{

    //注入 http请求工具类
    @Autowired
    private WeChatAuthConfig weChatAuthConfig;
	
    //生成请求微信官方获取用户授权code的请求地址
    
    //根据微信回调的code值,请求微信官方获取用户access_token
    
    //根据用户的 accessToken 和  openId 拉取用户信息

}

5.2.1 生成请求微信官方获取用户授权code的请求地址

/**
* @author : huayu
* @date   : 29/10/2022
* @param  : []
* @return : java.lang.String
* @description : 生成请求微信官方获取用户授权code的请求地址
*/
@Override
public String generateWeChatAuthCodeUrl() {

    //微信官方引导用户打开授权页面,获取code的完整路径
    //https://open.weixin.qq.com/connect/oauth2/authorize
    // ?appid=APPID
    // &redirect_uri=REDIRECT_URI
    // &response_type=code
    // &scope=SCOPE
    // &state=STATE
    // #wechat_redirect
    //尤其注意:由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问

    //生成请求卫星官方获取用户code的完整地址
    StringBuilder weCharAuthCodeUrl = new StringBuilder(weChatAuthConfig.getCodeUri());
    weCharAuthCodeUrl.append("?appid=").append(weChatAuthConfig.getAppId())
        .append("&redirect_uri=").append(weChatAuthConfig.getRedirectUri())
        .append("&response_type=code")
        //&scope=snsapi_userinfo&state=STATE
        .append("&scope=").append("snsapi_userinfo")
        .append("&state=").append("kh96_wechat_auth")
        .append("#wechat_redirect");

    log.info("------ 请求微信官方授权网站地址:{}  ------",weCharAuthCodeUrl.toString());

    //返货完整的请求地址
    return weCharAuthCodeUrl.toString();

}

5.2.2 根据微信回调的code值,请求微信官方获取用户access_token

/**
* @author : huayu
* @date   : 29/10/2022
* @param  : [wechatAuthCode]
* @return : java.lang.String
* @description : 根据微信回调的code值,请求微信官方获取用户access_token
*/
@Override
public String getAccessTokenFromWechatUseCode(String wechatAuthCode) {
    // 尤其注意:由于公众号的 secret 和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。
    // 请求方法:获取 code 后,请求以下链接获取access_token:
    // https://api.weixin.qq.com/sns/oauth2/access_token
    // ?appid=APPID
    // &secret=SECRET
    // &code=CODE
    // &grant_type=authorization_code

    // 封装根据code,请求微信官方获取access_token的完整地址
    StringBuilder accessTokenUrl = new StringBuilder(weChatAuthConfig.getAccessTokenUri());
    accessTokenUrl.append("?appid=").append(weChatAuthConfig.getAppId())
        .append("&secret=").append(weChatAuthConfig.getAppSecret())
        .append("&code=").append(wechatAuthCode)
        .append("&grant_type=authorization_code");

    log.info("------ 根据code,请求微信官方获取access_token的完整地址:{} ------", accessTokenUrl.toString());

    // 根据code,请求微信官方获取access_token,返回结果是同步返回的,不再是异步回调
    // 请求是服务器内部发起的,也就是说:在程序中,要根据上面完整的请求地址,主动发送请求到微信官方,接口同步会返回一个json格式的字符串结果,程序内要解析获取的结果

    // 程序内主动发起http请求,获取access_token
    return HttpClient4Util.getResponse4GetAsString(accessTokenUrl.toString(), "utf-8");
}

5.2.3 根据用户的 accessToken 和 openId 拉取用户信息

/**
* @author : huayu
* @date   : 29/10/2022
* @param  : [accessToken, openId]
* @return : java.lang.String
* @description : 根据用户的 accessToken 和  openId 拉取用户信息
*/
@Override
public String getUserInfoFromWechatUseAccessToken(String accessToken, String openId) {

    // 如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和 openid 拉取用户信息了。
    // http:GET(请使用 https 协议):
    // https://api.weixin.qq.com/sns/userinfo
    // ?access_token=ACCESS_TOKEN
    // &openid=OPENID
    // &lang=zh_CN

    // 封装根据accessToken和openId,请求微信官方获取用户信息详情地址
    StringBuilder userInfoUrl = new StringBuilder(weChatAuthConfig.getUserInfoUri());
    userInfoUrl.append("?access_token=").append(accessToken)
        .append("&openid=").append(openId)
        .append("&lang=zh_CN");
    log.info("------ 根据access_token,请求微信官方获取userinfo的完整地址:{} ------", userInfoUrl.toString());

    // 程序内主动发起http请求,获取用户详情
    return HttpClient4Util.getResponse4GetAsString(userInfoUrl.toString(), "utf-8");
}

6、controller 层

/**
 * Created On : 28/10/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 测试微信授权登录操作入口
 */
//@SuppressWarnings("all")
@Slf4j
@RestController
@RequestMapping("/wechat/auth")
public class WeChatAuthController {

    //注入service层实现类
    @Autowired
    private WeChatAuthService weChatAuthService;

}

6.1 获取请求微信官方货物code的完整地址,用户访问该地址

/**
* @author : huayu
* @date   : 28/10/2022
* @param  : []
* @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
* @description :  获取请求微信官方货物code的完整地址,用户访问该地址,可以进行授权操作(把地址交给前端生成二维码给用户扫码,或者后端生成)
*/
@GetMapping("/codeUrl")
public RequestResult<String> codeUrl(){

    //调用业务接口,获取完整用户授权访问的地址
    return ResultBuildUtil.success(weChatAuthService.generateWeChatAuthCodeUrl());

}

6.2 获取用户详情信息

用户授权后,接收微信官方异步回调请求,获取用户授权的code:(code,state)
	1.通过 code 换取网页授权access_token
	2.拉取用户信息(需 scope snsapi_userinfo)
	3.接口返回用户详情信息
/**
* @author : huayu
* @date   : 28/10/2022
* @param  : []
* @return : com.kgc.scd.uitl.RequestResult<java.util.Map<java.lang.String,java.lang.Object>>
* @description : 接收微信官方异步回调请求,获取用户授权的code
* 流程:用户先根据上一步返回请求地址,进行授权操作,如果用户统一授权,微信官方自动根据上一步请求带过去的回调地址redirectUri,进行结果回调
*/
@RequestMapping("/codeBack")
public RequestResult<Map<String, Object>> codeBack(HttpServletRequest request){

    // 用户同意授权后,如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
    // code说明:code作为换取access_token的票据,每次用户授权带上的 code 将不一样,code只能使用一次,5分钟未被使用自动过期。

    // 从官方回调的请求中,获取用户授权后的code参数值
    String wechatAuthCode = request.getParameter("code");
    // 从官方回调的请求中,获取用户授权时的自定义参数state
    String wechatAuthState = request.getParameter("state");

    log.info("------ 微信授权后,官方异步回调结果:code={},state={} ------", wechatAuthCode, wechatAuthState);

    // 定义接口返回集合对象
    Map<String, Object> resultMap = new HashMap<>();

    // 参数非空校验
    if(StringUtils.isBlank(wechatAuthCode)){
        resultMap.put("msg", "授权code为空!");
        return ResultBuildUtil.fail(resultMap);
    }

    // 1. 调用业务接口,通过 code 换取网页授权access_token
    String accessTokenJson = weChatAuthService.getAccessTokenFromWechatUseCode(wechatAuthCode);
    log.info("------ 通过 code 换取网页授权access_token返回结果:{} ------", accessTokenJson);

    // 正确时返回的 JSON 数据包如下:
    // {"access_token":"ACCESS_TOKEN","expires_in":7200,"refresh_token":"REFRESH_TOKEN","openid":"OPENID","scope":"SCOPE"}
    // 错误时返回的 JSON 数据包如下:
    // {"errcode":40029,"errmsg":"invalid code"}

    // 解析返回的json数据
    JSONObject accessTokenJsonObj = JSON.parseObject(accessTokenJson);

    // 判断获取access_token结果是否正确,如果错误,直接结束,如果正确,获取对应的access_token
    if(StringUtils.isNotBlank(accessTokenJsonObj.getString("errcode"))){
        resultMap.put("wxCode", accessTokenJsonObj.getString("errcode"));
        resultMap.put("wxMsg", accessTokenJsonObj.getString("errmsg"));
        return ResultBuildUtil.fail(resultMap);
    }

    // 2. 拉取用户信息(需 scope 为 snsapi_userinfo)
    // 根据上一步返回json,获取拉取用户信息凭证-access_token和用户唯一标识-openid
    String accessToken = accessTokenJsonObj.getString("access_token");
    String openId = accessTokenJsonObj.getString("openid");

    //  调用业务接口,通过access_token和openId,拉取用户详情
    String userInfoJson = weChatAuthService.getUserInfoFromWechatUseAccessToken(accessToken, openId);
    log.info("------ 通过access_token和openId,拉取用户详情:{} ------", userInfoJson);

    // 3.接口返回用户详情信息
    resultMap.put("userInfo", userInfoJson);

    // TODO 获取成功用户信息后,系统要完成静默注册-把用户信息注册到系统数据中,存储用户的头像,昵称,openId信息,并给系统用户表增加其它的基本信息

    //返回用户详情
    return ResultBuildUtil.success(resultMap);
}

7、测试

7.1 生成请求微信官方获取用户授权code的请求地址

7.2 获取用户信息

7.3 如果确定关注了但是,返回结果说没有关注测试号

重新查看appIDappsecret,可能是因为这里发生了改变;

8、校验授权凭证(access-token)是否有效

8.1 配置信息

8.1 配置文件

# 添加 检验授权凭证(access_token)是否有效 的请求地址
wechat:
  auth:
  	check-access-token-uri: https://api.weixin.qq.com/sns/auth 

8.2 配置类

/*
 检验授权凭证(access_token)是否有效  的请求地址
*/
private String checkAccessTokenUri;

8.3 service 层

8.3.1 接口

/**
* @author : huayu
* @date   : 29/10/2022
* @param  : [accessToken,openid]
* @return : String
* @description : 根据 accessToken 和 openid 校验授权凭证(access-token)是否有效
*/
String checkAccessToken(String accessToken,String openid);

8.3.2 实现类

/**
* @author : huayu
* @date   : 29/10/2022
* @param  : [accessToken,String openid]
* @return : String
 * @description : 根据 accessToken,String openid 校验授权凭证(access-token)是否有效
*/
@Override
public String checkAccessToken(String accessToken,String openid) {

    //https://api.weixin.qq.com/sns/auth
    // ?access_token=ACCESS_TOKEN
    // &openid=OPENID
    //根据 accessToken openid  校验授权凭证(access-token)是否有效
    StringBuilder checkAccessTokenUri = new StringBuilder(weChatAuthConfig.getCheckAccessTokenUri());
    checkAccessTokenUri.append("?access_token=").append(accessToken)
        .append("&openid=").append(openid);

    log.info("------ 校验授权凭证(access-token)是否有效的完整地址:{} ------", checkAccessTokenUri.toString());
    // 程序内主动发起http请求,获取用户详情
    return HttpClient4Util.getResponse4GetAsString(checkAccessTokenUri.toString(),"utf-8");
}

8.4 controller 请求

/**
* @author : huayu
 * @date   : 29/10/2022
* @param  : [accessToken, openid]
* @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
* @description : 校验授权凭证(access-token)是否有效
*/
@GetMapping("/checkAccessToken")
public RequestResult<String> checkAccessToken(String accessToken,String openid){

    //调取业务接口 校验授权凭证(access-token)是否有效
    String checkAccessTokenResult = weChatAuthService.checkAccessToken(accessToken, openid);
    log.info("------ 根据accessToken 和 openid,校验授权凭证(access-token)是否有效:{} ------",accessToken,openid,checkAccessTokenResult);
    //返回说明
    //正确的 JSON 返回结果:
    //{ "errcode":0,"errmsg":"ok"}
    //错误时的 JSON 返回示例:
    //{ "errcode":40003,"errmsg":"invalid openid"}

    log.info("------ errcode:{} ------",JSONObject.parseObject(checkAccessTokenResult).getString("errcode"));

    return JSONObject.parseObject(checkAccessTokenResult).getString("errcode").equals("0") ?
        ResultBuildUtil.success("access_token 有效")
        : ResultBuildUtil.success("access_token 无效");

}

8.5 测试

9、刷新access_token

9.1 配置信息

wechat:
  auth:
    refresh-access-token-uri: https://api.weixin.qq.com/sns/oauth2/refresh_token #刷新access_token  的请求地址
    grant-type: refresh_token #刷新access_token  的请求地址 的 grant_type 参数值

9.2 配置类

/*
   刷新access_token  的请求地址
*/
private String refreshAccessTokenUri;

/*
   刷新access_token  的请求地址 的 grant_type 参数值
*/
private String grantType;

9.3 service层

9.3.1 接口

/**
* @author : huayu
* @date   : 29/10/2022
* @param  : [refreshToken]
* @return : java.lang.String
* @description : 根据 refreshToken 刷新access_token
*/
String refreshToken(String refreshToken);

9.3.2 实现类

/**
* @author : huayu
* @date   : 29/10/2022
* @param  : [refreshToken]
* @return : java.lang.String
* @description : 根据 refreshToken 刷新access_token
*/
@Override
public String refreshToken(String refreshToken) {

    //https://api.weixin.qq.com/sns/oauth2/refresh_token
    // ?appid=APPID
    // &grant_type=refresh_token
    // &refresh_token=REFRESH_TOKEN
    StringBuilder refreshAccessTokenUri = new StringBuilder(weChatAuthConfig.getRefreshAccessTokenUri());
    refreshAccessTokenUri.append("?appid=").append(weChatAuthConfig.getAppId())
        .append("&grant_type=").append(weChatAuthConfig.getGrantType())
        .append("&refresh_token=").append(refreshToken);

    log.info("------ refreshToken 刷新access_token的完整地址:{} ------", refreshAccessTokenUri.toString());

    // 程序内主动发起http请求,获取用户详情
    return HttpClient4Util.getResponse4GetAsString(refreshAccessTokenUri.toString(),"utf-8");

}

9.4 controller 请求

/**
* @author : huayu
* @date   : 29/10/2022
* @param  : [accessToken]
* @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
* @description : 刷新access_token根据官方接口文档对接
*/
@GetMapping("/refreshAccessToken")
public RequestResult<String> refreshAccessToken(@RequestParam("refreshToken") String refreshToken){

    //调取业务接口 刷新access_token
    String refreshTokenResult = weChatAuthService.refreshToken(refreshToken);
    log.info("------ refreshToken:{},刷新access_token根据官方接口文档对接:{} ------",refreshToken,refreshTokenResult);

    //正确时返回的 JSON 数据包如下:
    //{
    //  "access_token":"ACCESS_TOKEN",
    //  "expires_in":7200,
    //  "refresh_token":"REFRESH_TOKEN",
    //  "openid":"OPENID",
    //  "scope":"SCOPE"
    //}

    //错误时微信会返回 JSON 数据包如下(示例为 code 无效错误):
    //{"errcode":40029,"errmsg":"invalid code"}

    return   JSON.parseObject(refreshTokenResult).getString("expires_in").equals("7200")
        ? ResultBuildUtil.success("access_token 刷新成功")
        : ResultBuildUtil.success("access_token 刷新失败");

}

9.5 测试

 
posted @ 2022-12-01 19:47  hanease  阅读(221)  评论(0编辑  收藏  举报