SpringBoot快速实现微信授权登录

SpringBoot中快速实现微信授权回调获取用户信息,支持配置多个appId

1.引入weixin-java-mp

pom.xml文件中引入

<!-- https://mvnrepository.com/artifact/com.github.binarywang/weixin-java-mp -->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-mp</artifactId>
    <version>4.0.0</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2.配置文件配置公众号信息

application.yml中配置

wx:
  mp:
    configs:
      - appId: wxdafc984af070114c # 第一个公众号的appid
        secret: c5b427fdf9d925816cdc4f5420c47278 # 公众号的appsecret
        token: token # 接口配置里的Token值
        aesKey:  # 接口配置里的EncodingAESKey值
      - appId: 2222 # 第二个公众号的appid,以下同上
        secret: 1111
        token: 111
        aesKey: 111

3.加载配置信息

3.1加载配置属性

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;

/**
 * wechat mp properties
 */
@Data
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpProperties {
    /**
     * 多个公众号配置信息
     */
    private List<MpConfig> configs;

    @Data
    public static class MpConfig {
        /**
         * 设置微信公众号的appid
         */
        private String appId;

        /**
         * 设置微信公众号的app secret
         */
        private String secret;

        /**
         * 设置微信公众号的token
         */
        private String token;

        /**
         * 设置微信公众号的EncodingAESKey
         */
        private String aesKey;
    }

}

3.2初始化配置

import com.google.common.collect.Maps;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
public class WxMpConfiguration {

    private WxMpProperties properties;

    private static Map<String, WxMpService> mpServices = Maps.newHashMap();

    @Autowired
    public WxMpConfiguration(WxMpProperties properties) {
        this.properties = properties;
    }

    public static Map<String, WxMpService> getMpServices() {
        return mpServices;
    }

    @Bean
    public Object services() {
        final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();
        if (configs == null) {
            throw new RuntimeException("无相关配置");
        }

        mpServices = configs.stream().map(a -> {
            WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl();
            configStorage.setAppId(a.getAppId());
            configStorage.setSecret(a.getSecret());
            configStorage.setToken(a.getToken());
            configStorage.setAesKey(a.getAesKey());

            WxMpService service = new WxMpServiceImpl();
            service.setWxMpConfigStorage(configStorage);
            return service;
        }).collect(Collectors.toMap(s -> s.getWxMpConfigStorage().getAppId(), a -> a, (o, n) -> o));

        return Boolean.TRUE;
    }

}

3.3Controller实现

import com.alibaba.fastjson.JSON;
import com.cztv.news.common.util.ToUrlParamsUtils;
import com.cztv.news.portal.config.WxMpConfiguration;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

@Slf4j
@RestController
@RequestMapping("/api/wx")
public class WxOauthController {

    @RequestMapping("/callback")
    public ModelAndView wxCallback(@RequestParam String appId,
                                   @RequestParam String state,
                                   @RequestParam String code) {
        final WxMpService wxService = WxMpConfiguration.getMpServices().get(appId);

        if (!wxService.switchover(appId)) {
            throw new IllegalArgumentException(String.format("未找到对应appId=[%s]的配置,请核实!", appId));
        }

        try {
            WxOAuth2AccessToken accessToken = wxService.getOAuth2Service().getAccessToken(code);
            WxOAuth2UserInfo user = wxService.getOAuth2Service().getUserInfo(accessToken, null);
            String url = ToUrlParamsUtils.addParamToUrl(state, "wxUserInfo", JSON.toJSONString(user));
            return new ModelAndView("redirect:" + url);
        } catch (WxErrorException e) {
            log.error("微信appId:{}授权获取用户信息异常", appId, e);
        }
        String url = ToUrlParamsUtils.addParamToUrl(state, "wxUserInfo", null);
        return new ModelAndView("redirect:" + url);
    }

}

防止URL中可能有同名参数以及?#的拼接处理

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @Description 实体类对象转换为url参数
 */
@Slf4j
public class ToUrlParamsUtils {

    /**
     * url后追加参数
     * 如果url包含当前参数,则输出原始url
     * @param url
     * @param param
     * @return
     */
    public static String addParamToUrl(String url, String param, String paramValue) {
        if (StringUtils.isBlank(url)) {
            return StringUtils.EMPTY;
        }
        if (url.contains("?")) {
            if (isContainParam(url, param)) {
                return url;
            }
            return url.contains("#") && url.indexOf("#") > url.indexOf("?") ? url.replaceFirst("#", "&" + param + "=" + paramValue + "#") : url + "&" + param + "=" + paramValue;
        } else {
            return url.contains("#") ? url.replaceFirst("#", "?" + param + "=" + paramValue + "#") : url + "?" + param + "=" + paramValue;
        }
    }

    /**
     * 解析出url请求的路径,包括页面
     * @param strURL url地址
     * @return url路径
     */
    public static String urlPage(String strURL) {
        String strPage = null;
        String[] arrSplit;
        strURL = strURL.trim().toLowerCase();
        arrSplit = strURL.split("[?]");
        if (strURL.length() > 0) {
            if (arrSplit.length > 1) {
                if (arrSplit[0] != null) {
                    strPage = arrSplit[0];
                }
            }
        }
        return strPage;
    }

    /**
     * 去掉url中的路径,留下请求参数部分
     * @param strURL url地址
     * @return url请求参数部分
     */
    private static String truncateUrlPage(String strURL) {
        String strAllParam = null;
        String[] arrSplit;

        strURL = strURL.trim().toLowerCase();

        arrSplit = strURL.split("[?]");
        if (strURL.length() > 1) {
            if (arrSplit.length > 1) {
                if (arrSplit[1] != null) {
                    strAllParam = arrSplit[1];
                }
            }
        }
        return strAllParam;
    }

    /**
     *  解析出url参数中的键值对
     *  如 "index.jsp?Action=del&id=123",解析出Action:del,id:123存入map中
     *  @param url  url地址
     *  @return url请求参数部分
     */
    public static Map<String, String> urlRequest(String url) {
        Map<String, String> mapRequest = new HashMap<>();
        String[] arrSplit;

        String strUrlParam = truncateUrlPage(url);
        if (strUrlParam == null) {
            return mapRequest;
        }
        // 每个键值为一组
        arrSplit = strUrlParam.split("[&]");
        return putKVInMap(mapRequest, arrSplit);
    }

    public static Map<String, String> strToMap(String splitStr, String delimiter) {
        Map<String, String> map = new HashMap<>();

        if (StringUtils.isBlank(splitStr)) {
            return map;
        }
        // 以指定分隔符切分
        String[] arrSplit = splitStr.split("[" + delimiter + "]");
        return putKVInMap(map, arrSplit);
    }

    private static Map<String, String> putKVInMap(Map<String, String> map, String[] arrSplit) {
        for (String strSplit : arrSplit) {
            String[] arrSplitEqual = strSplit.split("[=]");

            //解析出键值
            if (arrSplitEqual.length > 1) {
                //正确解析
                map.put(arrSplitEqual[0], arrSplitEqual[1]);
            } else {
                if (!arrSplitEqual[0].equals("")) {
                    //只有参数没有值,不加入
                    map.put(arrSplitEqual[0], "");
                }
            }
        }
        return map;
    }

    /**
     * 请求参数中是否包含参数
     * @param url
     * @param param
     * @return
     */
    public static boolean isContainParam(String url, String param) {
        return urlRequest(url).containsKey(param);
    }

    /**
     * @param clazz 参数实体类
     * @return String
     * @Description: 将实体类clazz的属性转换为url参数
     */
    public static String getParams(Object clazz) {
        // 遍历属性类、属性值
        Field[] fields = clazz.getClass().getDeclaredFields();

        StringBuilder requestURL = new StringBuilder();
        try {
            boolean flag = true;
            String property, value;
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                // 允许访问私有变量
                field.setAccessible(true);

                // 属性名
                property = field.getName();
                // 属性值
                value = Optional.ofNullable(String.valueOf(field.get(clazz))).orElse("");

                String params = property + "=" + value;
                if (flag) {
                    requestURL.append(params);
                    flag = false;
                } else {
                    requestURL.append("&" + params);
                }
            }
        } catch (Exception e) {
            log.error("转换URL参数异常:", e);
        }
        return requestURL.toString();
    }

}

4.设置微信公众平台

JS接口安全域名

网页授权获取用户基本信息配置授权回调页面域名

5.前台请求及地址跳转

前台访问地址

https://open.weixin.qq.com/connect/oauth2/authorize?
appid=wxdafc984af070114c&redirect_uri=http://www.xxx.com/solang/api/wx/callback?appId=wxdafc984af070114c
&response_type=code&scope=snsapi_userinfo&state=http://www.baidu.com?id=123#wechat_redirect

http://www.xxx.com/solang/api/wx/callback?appId=wxdafc984af070114c改为服务器端回调的地址,state微信会原样返回,此处修改为要跳转的链接地址。

用户授权后跳转会在地址上带上用户信息json参数

https://www.baidu.com/?id=123&wxUserInfo={%22openid%22:%22oCK1JuNf_CIsSBvHAnmRZMa5SMOw%22,%22nickname%22:%22suo?%22,%22sex%22:1,%22city%22:%22??%22,%22province%22:%22??%22,%22country%22:%22??%22,%22headimgurl%22:%22https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLRAEjMy5cspdSHPL91ILukRVxN1dc7qOyrRrOk4FPicFfJVFSeQpd9mNXaaEx2UGbFT3IcBicne86w/132%22,%22privilege%22:[]}

这里面跳转实现可以根据业务处理,用户信息可以存储下然后单独出个接口供前台调用。由于考虑到多个appId处理的通用性,本人这边获取到用户信息后直接拼接到前台要跳转的地址上,由前台处理。一开始考虑redirect_uri地址使用http://www.xxx.com/solang/api/wx/callback?appId=wxdafc984af070114c&redirect=http://www.baidu.com?id=123,然后Controller层接参数,@RequestParam String appId, @RequestParam String redirect, @RequestParam String code,但是微信回调时&redirect=http://www.baidu.com?id=123未带入到回调地址上,故使用state参数处理页面跳转。注意state参数微信会原样回调,所以和前台约定好形式就好。

本文参考:

https://github.com/binarywang/weixin-java-mp-demo
网页授权

### 回答1: Spring Boot是一种基于Spring框架的快速开发框架,它使Web应用程序开发过程变得更加简单。同时,jQuery是一种常用的JavaScript库,它可以使开发人员编写更少的代码来实现更好的用户体验。微信扫码登录是一种便捷的用户登录方式,它使用户可以通过扫描二维码的方式来登录系统,大大提高了用户的体验与使用方便性。将这三个技术库结合起来,可以实现一个简单易用的登录系统。 具体来说,可以使用Spring Boot框架开发后端接口,用jQuery库进行前端页面开发,实现微信扫码登录的集成。首先,后端应用需要将微信扫码API集成进来,实现用户信息的获取。通过在前端页面调用这些API,用户可以扫描二维码进行登录。在用户提交登录信息以后,后端可以将这些信息进行验证,并在验证成功后完成用户的授权操作,使其可以使用系统的相关功能。 总之,Spring Boot,jQuery,和微信扫码登录三者的融合可以实现一个简单而又好用的登录系统。通过它,用户可以享受高效、快速和安全的登录体验。同时,这种解决方案也可以为开发人员提供更加简便的开发体验,加速开发过程,节约开发成本。 ### 回答2: Spring Boot是一个开源的Java框架,简化了Spring框架的配置和开发,使Java应用程序的构建和部署变得更加简单。Spring Boot集成了Spring框架和大量的其他开源框架和工具,提供了全面的基础设施支持,可以快速构建高效的Java应用程序。 jQuery是一种广泛使用的JavaScript库,使开发人员可以更轻松地处理HTML文档、处理事件、创建动画效果、处理AJAX请求等。jQuery具有快速、简单、小巧的特点,广泛应用于Web前端开发。 微信扫码登录是一种流行的身份验证方式,用户可以通过扫描微信二维码来登录网站或应用程序。开发人员可以使用Spring Boot和jQuery来实现微信扫码登录功能。具体来说,可以使用Spring Boot实现身份验证和用户管理功能,使用jQuery实现前端界面和动态效果。 在实现微信扫码登录功能时,需要通过微信开放平台注册账号并获取应用ID和应用密钥。然后,可以使用Spring Boot创建RESTful API接口,处理微信服务器发送的请求,并将认证结果返回给前端。在前端界面上,可以使用jQuery生成并显示二维码,并轮询后台接口确定用户是否已经扫描了二维码,从而实现微信扫码登录功能。 总之,使用Spring Boot和jQuery可以快速、简单地实现微信扫码登录功能,提高了Web应用程序的安全性和用户体验。 ### 回答3: 在现代的web应用中,微信扫码登录已经成为普遍的登录方式。为了实现这种方式,我们需要在后端实现微信登录接口并且生成二维码,在前端使用jquery实现扫码功能。springboot可以作为后端框架来实现微信登录接口。 首先,需要到微信公众平台或开放平台中获取网站应用的appidappsecret。然后,使用springboot中的restful风格接口实现微信登录功能。接口需要返回二维码图片和对应的token,用于后续的验证和获取用户信息。 在前端,使用jquery实现扫码功能,需要引用jquery-QRcode插件来生成二维码,并且使用ajax定时轮询token接口,来获取用户信息。用户扫码后,微信会把用户信息返回给后端,后端会生成一个token和过期时间,并将token返回给前端。前端每隔一段时间轮询token接口,直到token未过期且获取到用户信息。 总的来说,使用springboot和jquery实现微信扫码登录并不困难,但需要一定的微信开发经验和相关技术的掌握。在实际使用中,要注意安全性和认证授权等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值