• 微信授权登录功能实现


      这是根据网上视频做的一个硅谷课堂微服务项目,理由有涉及到微信公众号的开发。

      个人用户暂时只能用测试号开发,尤其注意内网穿透那里,昨晚域名没配置好卡在那了,改了半天没改好,白天在看论文没搞这个,晚上突然灵感来了找到了问题的关键,就当bug记录了。

      笔记也是参考别人的,我根据自己的代码和配置做了一点小改动。

      也可以去看我去年写过的另一篇文章:微信扫码登录实现

    1、需求描述

      根据流程图通过菜单进入的页面都要授权登录

    page_1

    2、授权登录

      接口文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html

      说明:

       1、严格按照接口文档实现;

       2、应用授权作用域scope:scope为snsapi_userinfo

    image-20220827213317377

    2.1 配置授权回调域名

      (1)在公众号正式号配置

      这个只针对有正式公众号的企业用户,个人用户不用看这一步。

      在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“设置与开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;

      本地测试配置内网穿透地址

    image-20220302141300175

      (2)在公众号测试号配置

    image-20220827213501742

    image-20220827213519589

      将上面的域名经过内网穿透映射到我本地8333端口,也就是我的网关微服务。

    2.2 部署公众号前端页面

      (1)公众号前端页面已经开发完成,直接部署使用即可

       这里我也是用的别人的模板。

    image-20220505110134666

      (2)启动公众号页面项目

      使用命令:npm run serve

    image-20220827214014234

    image-20220827214033712

    2.3 前端处理

      (1)全局处理授权登录,处理页面:/src/App.vue

      说明1:访问页面时首先判断是否有token信息,如果没有跳转到授权登录接口

      说明2:通过localStorage存储token信息

      在HTML5中,加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间很小,只有几K),localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中localStorage会有所不同。它只能存储字符串格式的数据,所以最好在每次存储时把数据转换成json格式,取出的时候再转换回来。

      (2)前端代码实现

    wechatLogin() {
        // 处理微信授权登录
        let token = this.getQueryString('token') || '';
        if(token != '') {
            window.localStorage.setItem('token', token);
        }
    
        // 所有页面都必须登录,两次调整登录,这里与接口返回208状态
        token = window.localStorage.getItem('token') || '';
        if (token == '') {
            let url = window.location.href.replace('#', 'guiguketan')
            window.location = 'http://glkt.atguigu.cn/api/user/wechat/authorize?returnUrl=' + url
        }
        console.log('token2:'+window.localStorage.getItem('token'));
    
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

      上面是老师给的示例代码,你需要将上面的window.location中的域名glkt.atguigu.cn改成你用内网穿透工具配置好的。

    image-20220827214308080

    3、授权登录接口

      操作模块:service-user

    image-20220827214355073

    3.1 引入微信工具包

    <dependencies>
        <dependency>
            <groupId>com.github.binarywanggroupId>
            <artifactId>weixin-java-mpartifactId>
            <version>2.7.0version>
        dependency>
    
        <dependency>
            <groupId>dom4jgroupId>
            <artifactId>dom4jartifactId>
            <version>1.1version>
        dependency>
    
        <dependency>
            <groupId>com.aliyungroupId>
            <artifactId>aliyun-java-sdk-coreartifactId>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.2 添加配置

    #公众号id和秘钥
    # 硅谷课堂微信公众平台appId
    wechat.mpAppId: wx09f201e9013e81d8
    ## 硅谷课堂微信公众平台api秘钥
    wechat.mpAppSecret: 6c999765c12c51850d28055e8b6e2eda
    # 授权回调获取用户信息接口地址
    wechat.userInfoUrl: http://ggkt.vipgz1.91tunnel.com/api/user/wechat/userInfo
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

      上面只是个示例,将这三个值改成你自己的。id和密钥见下图

    image-20220827214502066

      然后上面的回调地址的域名也改成你自己的。

    3.3 添加工具类

    @Component
    public class ConstantPropertiesUtil implements InitializingBean {
    
        @Value("${wechat.mpAppId}")
        private String appid;
    
        @Value("${wechat.mpAppSecret}")
        private String appsecret;
    
        public static String ACCESS_KEY_ID;
        public static String ACCESS_KEY_SECRET;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            ACCESS_KEY_ID = appid;
            ACCESS_KEY_SECRET = appsecret;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    @Configuration
    public class WeChatMpConfig {
    
        @Autowired
        private ConstantPropertiesUtil constantPropertiesUtil;
    
        @Bean
        public WxMpService wxMpService(){
            WxMpService wxMpService = new WxMpServiceImpl();
            wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
            return wxMpService;
        }
    
        @Bean
        public WxMpConfigStorage wxMpConfigStorage(){
            WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage();
            wxMpConfigStorage.setAppId(ConstantPropertiesUtil.ACCESS_KEY_ID);
            wxMpConfigStorage.setSecret(ConstantPropertiesUtil.ACCESS_KEY_SECRET);
            return wxMpConfigStorage;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.4 Controller

    @Slf4j
    @Controller
    @RequestMapping("/api/user/wechat")
    public class WechatController {
    
        @Autowired
        private UserInfoService userInfoService;
    
        @Autowired
        private WxMpService wxMpService;
    
        //取出配置文件中的回调地址
        @Value("${wechat.userInfoUrl}")
        private String userInfoUrl;
    
    
        //授权跳转
        @GetMapping("authorize")
        public String authorize(@RequestParam("returnUrl") String returnUrl,
                                HttpServletRequest request) {
            String url = wxMpService.oauth2buildAuthorizationUrl(userInfoUrl,
                    WxConsts.OAUTH2_SCOPE_USER_INFO,
                    URLEncoder.encode(returnUrl.replace("guiguketan", "#")));
            return "redirect:"+url;
        }
    
        @GetMapping("userInfo")
        public String getUserInfoUrl(@RequestParam("code") String code,
                                     @RequestParam("state") String returnUrl){
            try {
                //拿着code请求
                WxMpOAuth2AccessToken wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
                //获取openid
                String openId = wxMpOAuth2AccessToken.getOpenId();
                log.info("openid:{}",openId);
    
                //获取微信信息
                WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null);
                log.info("wxMpUser:{}", JSON.toJSONString(wxMpUser));
    
                //获取微信信息添加到数据库
                //先根据openid查询用户信息
                UserInfo userInfo=userInfoService.getUserInfoOpenid(openId);
                if(userInfo==null){ //数据库没有时添加到数据库中
                    userInfo=new UserInfo();
                    userInfo.setOpenId(openId);
                    userInfo.setNickName(wxMpUser.getNickname());
                    userInfo.setAvatar(wxMpUser.getHeadImgUrl());
                    userInfo.setSex(wxMpUser.getSexId());
                    userInfo.setProvince(wxMpUser.getProvince());
    
                    userInfoService.save(userInfo);
                }
                //授权完成之后,跳转到具体的功能页面
                //生成token,按照一定规则生成字符串,可以包含用户信息
                String token= JwtHelper.createToken(userInfo.getId(),userInfo.getNickName());
                //localhost:8080/weixin?a=1&token=222
                if(returnUrl.indexOf("?")==-1){//若returnUrl中没有参数
                    return "redirect:"+returnUrl+"?token="+token;
                }else{
                    return "redirect:"+returnUrl+"&token="+token;
                }
            } catch (WxErrorException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    3.5 编写UserInfoService

    @Service
    public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
    
        //根据openid擦寻用户信息
        @Override
        public UserInfo getUserInfoOpenid(String openId) {
            QueryWrapper<UserInfo> wrapper=new QueryWrapper<>();
            wrapper.eq("open_id",openId);
            UserInfo userInfo = baseMapper.selectOne(wrapper);
            return userInfo;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.6 使用token

      通过token传递用户信息

      我们在授权成功之后需要给前端返回一个token。

    3.6.1 JWT介绍

      我以前写过JWT,看这里:JWT实现跨域身份验证

      JWT工具

      JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。

      JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上

      JWT最重要的作用就是对 token信息的防伪作用。

    3.6.2 JWT的原理

      一个JWT由三个部分组成:公共部分、私有部分、签名部分。最后由这三者组合进行base64编码得到JWT。

    0.00022527543306422325

    (1)公共部分

      主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。

    (2)私有部分

      用户自定义的内容,根据实际需要真正要封装的信息。

      userInfo{用户的Id,用户的昵称nickName}

    (3)签名部分

      SaltiP: 当前服务器的Ip地址!{linux 中配置代理服务器的ip}

      主要用户对JWT生成字符串的时候,进行加密{盐值}

      base64编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以把base64编码解成明文,所以不要在JWT中放入涉及私密的信息。

    3.6.3 整合JWT

      (1)在service_utils模块添加依赖

    <dependencies>
    	<dependency>
    		<groupId>org.apache.httpcomponentsgroupId>
    		<artifactId>httpclientartifactId>
    	dependency>
    	<dependency>
    		<groupId>io.jsonwebtokengroupId>
    		<artifactId>jjwtartifactId>
    	dependency>
    	<dependency>
    		<groupId>joda-timegroupId>
    		<artifactId>joda-timeartifactId>
    	dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    image-20220827215150151

      (2)添加JWT工具类

    public class JwtHelper {
        //token字符串有效时间
        private static long tokenExpiration = 24*60*60*1000;
        //加密编码秘钥
        private static String tokenSignKey = "123456";
    
        //根据userid  和  username 生成token字符串
        public static String createToken(Long userId, String userName) {
            String token = Jwts.builder()
                    //设置token分类
                    .setSubject("GGKT-USER")
                    //token字符串有效时长
                    .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                    //私有部分(用户信息)
                    .claim("userId", userId)
                    .claim("userName", userName)
                    //根据秘钥使用加密编码方式进行加密,对字符串压缩
                    .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                    .compressWith(CompressionCodecs.GZIP)
                    .compact();
            return token;
        }
    
        //从token字符串获取userid
        public static Long getUserId(String token) {
            if(StringUtils.isEmpty(token)) return null;
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            Integer userId = (Integer)claims.get("userId");
            return userId.longValue();
        }
    
        //从token字符串获取getUserName
        public static String getUserName(String token) {
            if(StringUtils.isEmpty(token)) return "";
            Jws<Claims> claimsJws
                    = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            return (String)claims.get("userName");
        }
    
        public static void main(String[] args) {
            String token = JwtHelper.createToken(1L, "lucy");
            System.out.println(token);
            System.out.println(JwtHelper.getUserId(token));
            System.out.println(JwtHelper.getUserName(token));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

      测试下JWT的签发和验证:

    image-20220827215256995

    eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWKi5NUrJScnf3DtENDXYNUtJRSq0oULIyNDMzNLM0MTc101EqLU4t8kwBikGYfom5qUAtOaXJlUq1AKdK54FBAAAA.6slWAzHg8oswGzWF411hOgX3i4yTAn-K7MwPBslbLhsXEw-ONikd06ydlDxboGEk8BpXnzm7VLKtrTToSH0w4A
    1
    lucy

    3.7 微信授权登录测试

      我们将负责授权的微服务以Debug模型运行。

    image-20220827215454517

      进入微信公众号测试,随便点击个按钮

    image-20220827215534359

      虽然泄露了我的域名,但是为了展示效果,我还是放出来吧,因为我看评论区好多人都在这一步卡住了。

    image-20220827215645311

      继续放行,会出现用户授权,的用户同意授权,获取code

    image-20220827215917570

      我在测试的时候已经同意过了。这里会自动登录。

      然后拿着给的code,去换取网页授权access_token,然后再通过网页授权access_token和openid获取用户基本信息。到这一步授权已经成功。然后就是存数据库等操作了,页面跳转等就根据你自己业务做了。

    image-20220827220453217

      可以看到,随着断点的不断放行,执行了wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null)方法后将我的微信名和openid、头像等等都查了出来,然后就可以存数据库等操作了,放行进入页面。我这里后面的页面还没做完,所以先暂时这样。

      感觉这次的逻辑稍微有点乱,也可以去看我去年写过的另一篇微信扫码登录的文章:微信扫码登录实现

      好了,到这里微信授权登录就做完了,有什么问题也可以找我交流,我看那个教学视频的评论区,大多数人都卡在这一步了。

  • 相关阅读:
    Python subprocess模块学习笔记
    【新员工座位安排系统】python实现-附ChatGPT解析
    如何使用 Git 管理配置文件
    出海电商APP外包开发及上线流程
    组合数与莫队——组合数前缀和
    【每日一练】20231023
    Ubuntu下Anaconda安装
    AI绘画的崛起与多平台对比
    【Godot测试】【在Godot中添加VRM模型和VMD动画并播放】
    001 rabbitmq减库存demo direct
  • 原文地址:https://blog.csdn.net/qq_43753724/article/details/126563283