微信公众号开发——实现用户微信网页授权流程

这篇具有很好参考价值的文章主要介绍了微信公众号开发——实现用户微信网页授权流程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

😊 @ 作者: 一恍过去
💖 @ 主页: https://blog.csdn.net/zhuocailing3390
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: 微信公众号开发——实现用户微信网页授权流程
⏱️ @ 创作时间: 2022年12月16日

准备工作

1、在本地进行联调时,为让微信端能够访问到本地服务,需要进行内网穿透,参考《本地服务器内网穿透实现(NATAPP)》
2、配置网页授权获取用户基本信息,用于告诉微信发起授权的后端服务器地址

  • 正式公众号:在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”进行配置操作;
  • 测试沙箱环境:在 测试环境 中,进行配置网页授权
    微信公众号开发——实现用户微信网页授权流程
    微信公众号开发——实现用户微信网页授权流程

授权说明

微信授权时,分为snsapi_basesnsapi_userinfo两种授权方式

  • snsapi_base: 用来获取进入页面的用户的 openid 的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面,不会有对于的认为操作);
  • snsapi_userinfo: 是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

网页授权流程最主要分三步:
1、引导用户进入授权页面同意授权,获取code
2、通过 code 换取网页授权access_token(与基础支持中的access_token不同)和openid
3、通过网页授权access_token和 openid 获取用户基本信息(支持 UnionID 机制)

后端接口流程说明:
当前Demo,后端只会对前端暴漏一个接口,微信授权回调地址直接重定向到后端地址,由后端进行后续授权以及获取用户信息操作;
暴漏给前端的接口只需要传入两个参数,分别为:socpe(授权类型)、baseUrl(授权成功后重定向到前端的页面地址)

比如:

1、前端调用`wechant/code`接口,传入`socpe=snsapi_userinfo、baseUrl=http://lhz.com/h5`
2、微信回调的授权地址,直接为后端地址
3、获取code、通过code获取access_token的流程由后端完成
4、后端获取到openid信息后,重定向到`http://lhz.com/h5`,比如`return "redirect:" + baseUrl + "?openid=" + openid;`

yaml配置

wx:
  # 来源于测试平台
  appid: wx79ec4331f29311b9
  secret: 1c79a199560f94096f26b8caa2a73a08
  apiUrl: https://api.weixin.qq.com/
  openApiUrl: https://open.weixin.qq.com/
  authRedirectUri: http://6uks3d.natappfree.cc/wechat/auth

接口常量定义

InterfaceConstant:

public interface InterfaceConstant {
    /**
     * 用户同意授权,获取code
     */
    String OAUTH2_AUTHORIZE = "connect/oauth2/authorize";

    /**
     * 通过 code 换取网页授权access_token
     */
    String OAUTH2_ACCESS_TOKEN = "sns/oauth2/access_token";

    /**
     * 获取用户信息
     */
    String OAUTH2_USERINFO = "sns/userinfo";
}

定义工具类

MapUtils:

public class MapUtils {

    /**
     * Map转换为 Entity
     *
     * @param params 包含参数的Map
     * @param t      需要赋值的实体
     * @param <T>    类型
     */
    public static <T> T mapToEntity(Map<String, Object> params, T t) {
        if (null == params) {
            return t;
        }
        Class<?> clazz = t.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        try {
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                String name = declaredField.getName();
                if (null != params.get(name)) {
                    declaredField.set(t, params.get(name));
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("属性设置失败!");
        }
        return t;
    }

    /**
     * 将对象转换为HashMap
     *
     * @param t   转换为Map的对象
     * @param <T> 转换为Map的类
     * @return Map
     */
    public static <T> Map<String, Object> entityToMap(T t) {
        Class<?> clazz = t.getClass();
        List<Field> allField = getAllField(clazz);
        Map<String, Object> hashMap = new LinkedHashMap<>(allField.size());
        try {
            for (Field declaredField : allField) {
                declaredField.setAccessible(true);
                Object o = declaredField.get(t);
                if (null != o) {
                    hashMap.put(declaredField.getName(), o);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("属性获取失败!");
        }
        return hashMap;
    }

    /**
     * 获取所有属性
     *
     * @param clazz class
     * @param <T>   泛型
     * @return List<Field>
     */
    public static <T> List<Field> getAllField(Class<T> clazz) {
        List<Field> fields = new ArrayList<>();
        Class<?> superClazz = clazz;
        while (null != superClazz) {
            fields.addAll(Arrays.asList(superClazz.getDeclaredFields()));
            superClazz = superClazz.getSuperclass();
        }
        return fields;
    }

    /**
     * 将Map参数转换为字符串
     *
     * @param map
     * @return
     */
    public static String mapToString(Map<String, Object> map) {
        StringBuffer sb = new StringBuffer();
        map.forEach((key, value) -> {
            sb.append(key).append("=").append(value.toString()).append("&");
        });
        String str = sb.toString();
        str = str.substring(0, str.length() - 1);
        return str;
    }

    /**
     * 将Bean对象转换Url请求的字符串
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> String getUrlByBean(T t) {
        String pre = "?";
        Map<String, Object> map = entityToMap(t);
        return pre + mapToString(map);
    }

}

用户授权获取Code

接口说明:

由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问;链接属性如下:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

重定向说明:

通过微信接口connect/oauth2/authorize获取授权code后,微信将进行一次重定向回调,回调地址就是请求设置的redirect_uri值;

需要注意的是,微信重定向回调地址,一定要在微信公众号平台进行配置

定义请求实体类 Oauth2AuthorizeRep:

@Data
public class Oauth2AuthorizeRep {
    /**
     * 公众号的唯一标识
     */
    private String appid;

    /**
     * 授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
     */
    private String redirect_uri;

    /**
     * 返回类型,请填写code
     */
    private String response_type = "code";

    /**
     * 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid)
     * snsapi_userinfo (弹出授权页面,可通过 openid 拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
     */
    private String scope;

    /**
     * 重定向后会带上 state 参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
     */
    private String state;
}

Controller层示例:

@Slf4j
@Api(tags = "公众号/订阅号开发")
@Controller
public class WeChantController {
    /**
     * 由后端来进行授权操作(需要在微信页面打开)
     *
     * @param baseUrl 前端页面地址 用于授权完成后,后端重定向到前端页面
     * @param scope   应用授权作用域,此处为了模拟两种情况,进行传值:
     *                snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid)
     *                snsapi_userinfo (弹出授权页面,可通过 openid 拿到昵称、性别、所在地。 即使在未关注的情况下,只要用户授权,也能获取其信息 )
     * @return
     */
    @GetMapping(value = "/code")
    @ApiOperation(value = "用户请求进行授权及获取信息", notes = "用户请求进行授权及获取信息")
    public String code(@RequestParam("baseUrl") String baseUrl, String scope) throws UnsupportedEncodingException {
        log.info("------ 用户请求进行授权及获取信息 ------");
        //通过code获取用户其信息
        String url = weChantService.getAuthCode(baseUrl, scope);
        return "redirect:" + url;
    }
}

Service层示例:

@Service
@Slf4j
public class WeChantService {
    /**
     * 获取用户授权码
     *
     * @param baseUrl
     * @param scope
     * @return
     */
    public String getAuthCode(String baseUrl, String scope) throws UnsupportedEncodingException {
        String appId = wxBean.getAppid();
        // 设置回调地址 http://6uks3d.natappfree.cc/wechat/auth,该地址为后端地址
        String redirectUri = wxBean.getAuthRedirectUri();
        // urlEncode处理
        redirectUri = URLEncoder.encode(redirectUri, "utf-8");
        // 组装url,在url中让state属性存方baseUrl的值
        String url = wxBean.getOpenApiUrl() + InterfaceConstant.OAUTH2_AUTHORIZE;
        // 封装url请求参数
        Oauth2AuthorizeRep rep = new Oauth2AuthorizeRep();
        rep.setAppid(appId);
        rep.setRedirect_uri(redirectUri);
        rep.setScope(scope);
        // 设置回调参数,需要进行urlEncode处理
        Map<String, String> stateMap = new HashMap<>(4);
        stateMap.put("baseUrl", baseUrl);
        stateMap.put("scope", scope);
        String stateMapStr = JSON.toJSONString(stateMap);
        stateMapStr = new String(Base64.getEncoder().encode(stateMapStr.getBytes(StandardCharsets.UTF_8)));
        rep.setState(stateMapStr);
        // 参数的顺序必须是:appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
        url = url + MapUtils.getUrlByBean(rep) + "#wechat_redirect";

        // 重定向url,微信会自动访问redirectUri,进行回调
        return url;
    }
}

请求示例:
需要在微信中打开以下链接(实际情况是前端在微信环境中进行调用),这里进行Demo演示,手动在微信中进行访问:http://6uks3d.natappfree.cc/wechat/code?baseUrl=https://www.baidu.com&scope=snsapi_base
其中http://6uks3d.natappfree.cc为通过NatApp配置的内网穿透域名,保证微信功能访问到本地项目;

通过Code 换取授权access_token及用户信息

接口说明:

由于公众号的 secret 和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。
该接口就是用户授权获取Code中定义的redirectUri值,微信重定向时会进行调用

需要注意的是,微信重定向回调地址,一定要在微信公众号平台进行配置

定义请求实体类 Oauth2AccessTokenRep:

@Data
public class Oauth2AccessTokenRep {
    /**
     * 公众号的唯一标识
     */
    private String appid;

    /**
     * 公众号的appsecret
     */
    private String secret;

    /**
     * 填写第一步获取的 code 参数
     */
    private String code;

    /**
     * 填写为authorization_code
     */
    private String grant_type = "authorization_code";
}

定义响应实体类 Oauth2AccessTokenRes:

@Data
public class Oauth2AccessTokenRes {
    /**
     * 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    private String access_token;

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

    /**
     * 用户刷新access_token
     */
    private String refresh_token;
    /**
     * 用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID
     */
    private String openid;

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

    /**
     * 是否为快照页模式虚拟账号,只有当用户是快照页模式虚拟账号时返回,值为1
     */
    private String is_snapshotuser;

    /**
     * 用户统一标识(针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的),只有当 scope 为"snsapi_userinfo"时返回
     * 并且公众号与微信开放平台进行了绑定才会返回
     */
    private String unionid;
}

Controller层示例:

该接口接收微信的授权回调,获取code及state值,并且在完成授权后,重定向到指定的前端页面

@Slf4j
@Controller
public class WeChantController {
    /**
     * @param code
     * @param state 存放的前端页面地址,授权后回调用
     * @return
     */
    @GetMapping(value = "/auth")
    @ApiOperation(value = "前端根据code获取信息", notes = "前端根据code获取信息")
    public String auth(@RequestParam(value = "code", required = false) String code, @RequestParam(value = "state", required = false) String state) throws UnsupportedEncodingException {
        log.info("------ 回显Code:{} ------", code);
        // 解析回传的 state值
        state = new String(Base64.getDecoder().decode(state.getBytes(StandardCharsets.UTF_8)));
        Map map = JSON.parseObject(state, Map.class);
        String baseUrl = map.get("baseUrl").toString();
        String scope = map.get("scope").toString();

        // 通过code获取用户openid
        String openid = weChantService.getUserAuth(code, scope);

        // 直接跳转到前端地址
        return "redirect:" + baseUrl + "?openid=" + openid;
    }
}

Service层示例:

@Service
@Slf4j
public class WeChantService {
    /**
     * 用户授权,并且返回openid返回给前端
     *
     * @param code
     * @param scope
     * @return
     */
    public String getUserAuth(String code, String scope) {

        String appId = wxBean.getAppid();
        String secret = wxBean.getSecret();
        String url = wxBean.getApiUrl() + InterfaceConstant.OAUTH2_ACCESS_TOKEN;
        // 封装url请求参数
        Oauth2AccessTokenRep rep = new Oauth2AccessTokenRep();
        rep.setAppid(appId);
        rep.setSecret(secret);
        rep.setCode(code);
        url = url + MapUtils.getUrlByBean(rep);
        Map map = restHttpRequest.doHttp(url, HttpMethod.GET, null);
        if (map == null) {
            throw new RuntimeException("授权失败!");
        }
        Oauth2AccessTokenRes res = new Oauth2AccessTokenRes();
        MapUtils.mapToEntity(map, res);
        log.info("Oauth2AccessTokenRes:" + JSON.toJSONString(res));

        // 获取access_token过期时间
        long expiresToken = res.getExpires_in() - 100;
        String access_token = res.getAccess_token();
        String openid = res.getOpenid();

        log.info("------ access_token:{} ------", access_token);
        log.info("------ openid:{} ------", openid);

        // 根据openid和access_token获取用户信息,如果"snsapi_userinfo"授权方式,再调用接口获取用户信息
        if (scope.equals(SNS_API_USERINFO)) {
            getAndInsertUserInfo(openid, access_token);
        }
        return openid;
    }
}

获取用户信息

定义请求实体类 Oauth2UserInfoRep:

@Data
public class Oauth2UserInfoRep {
    /**
     * 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    private String access_token;

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

    /**
     * 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
     */
    private String lang = "zh_CN";

}

定义响应实体类 Oauth2UserInfoRes:

@Data
public class Oauth2UserInfoRes {
    /**
     * 用户昵称
     */
    private String nickname;

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

    /**
     * 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
     */
    private Integer sex;

    /**
     * 用户个人资料填写的省份
     */
    private String province;

    /**
     * 普通用户个人资料填写的城市
     */
    private String city;

    /**
     * 国家,如中国为CN
     */
    private String country;

    /**
     * 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),
     * 用户没有头像时该项为空。若用户更换头像,原有头像 URL 将失效。
     */
    private String headimgurl;

    /**
     * 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)
     */
    private List<String> privilege;

    /**
     * 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
     */
    private String unionid;
}

Service层示例:

该接口在后端通过code获取accessTokenopenid后,直接调用该方法;

@Service
@Slf4j
public class WeChantService {
    private void getAndInsertUserInfo(String openid, String accessToken) {
        // 获取用户信息
        String url = wxBean.getApiUrl() + InterfaceConstant.OAUTH2_USERINFO;
        Oauth2UserInfoRep rep = new Oauth2UserInfoRep();
        rep.setAccess_token(accessToken);
        rep.setOpenid(openid);
        url = url + MapUtils.getUrlByBean(rep);
        Map userMap = restHttpRequest.doHttp(url, HttpMethod.GET, null);
        Oauth2UserInfoRes res = new Oauth2UserInfoRes();
        MapUtils.mapToEntity(userMap, res);
        // 打印信息
        log.info("UserInfo:" + JSON.toJSONString(res));
    }
}

流程测试

注意:由于使用了NatApp进行免费的内网穿透,可以会出现域名变更情况,如果域名发生了变更需要重新在公众号平台配置网页授权域名。

1、微信中访问后端接口,地址:http://6uks3d.natappfree.cc/wechat/code?baseUrl=https://www.baidu.com&scope=snsapi_userinfo

2、弹出授权页面如下
微信公众号开发——实现用户微信网页授权流程
3、同意授权后,重定向到指定的页面(测试时使用了百度页面)
微信公众号开发——实现用户微信网页授权流程
4、查看百度页面的链接信息,发现url后面带上了openid,就表示授权及重定向成功,比如:https://www.baidu.com/?openid=oTnaY6332ssfv4WiQBU0dES-WxJg文章来源地址https://www.toymoban.com/news/detail-416500.html

到了这里,关于微信公众号开发——实现用户微信网页授权流程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包