微信小程序一键登录功能,使用uni-app和springboot(JWT鉴权)

news2024/11/24 9:48:47

目录

概述

微信登录接口说明

 关于获取微信用户的信息

前端代码(uni-app)

后端代码(SpringBoot)

配置文件:application.yml 

配置文件:Pom.xml 

类:WeChatModel  

 类:WeChatSessionModel

 类:UserInfoController

业务层实现类:UserInfoServiceImpl

工具类:JWTUtils

拦截器配置-自定义拦截器

拦截器配置-注册自定义拦截器

测试(Postman)

总结


概述

本篇博本主要为了记录使用uni-app开发微信小程序时实现微信一键登录功能,并且使用JWT实现身份认证

微信登录接口说明

可以点击==>官方的登录时序图<== ,看到官方描述登录的流程。

大概描述就是 :uni-app调用login()方法,获取临时凭证code,将此临时凭证发送到我们的后端,后端通过我们传入的临时凭证code,调用微信接口服务获取当前微信用户的唯一标识openid,我们就可以凭借此openid知道哪一个用户在进行登录操作。

值得注意的是

1.通过login()获取的临时凭证code的有效期为5分钟,并且只能使用一次。

2.后端调用微信凭证验证接口获取openid需要appIdappSecret,两者都可以到微信小程序官网==>开发管理==>开发设置 中获取。

如下是我画的整体大概流程

总体说明 整个流程就是当用户点击"微信一键登录",传入临时凭证code,后端通过临时凭证code去微信服务接口获取该用户的openid,此openid是唯一的不会变的。 那么我们就可以将openid存储用户数据表中,用来标识此用户。

 关于获取微信用户的信息

关于API:uni.getUserInfo(OBJECT) 和 uni.getUserProfile(OBJECT) 接口的说明。

目前两个接口都无法获取用户信息,只能获取到默认用户信息,名称为'微信用户',头像为灰色用户头像。

关于官方描述:==>原文<==

 那么解决方案只能是用户登录后,让用户自行上传修改信息。

前端代码(uni-app)

 前端的代码很简单,只是调用uni.login()获取临时凭证code传入后端接口即可。

<template>
	<view>
		<button id="loginHanle" @tap="goLogin">微信一键登录</button>
	</view>
</template>
<script>
	export default {
		methods: {
			// 登录按钮触发
			loginHanle() {
			    // 获取临时登录凭证code。
			    uni.login({
			        provider: 'weixin',
			        success(res) {
                        console.log(res.code);
						// 调用后端接口,传入code
			            axios.post('http://localhost:8888/api/userInfo/login',{code:res.code})
			        }
			    })
		}
	}
</script>

后端代码(SpringBoot)

后端需要接收前端传入的临时凭证code,向微信服务器发送请求获取登录用户的openid。并且操作数据库后返回用户信息,以及响应头返回token


配置文件:application.yml 

# JWT配置
jwt:
  header: "Authorization" #token返回头部
  tokenPrefix: "Bearer " #token前缀
  secret: "maohe101" #密钥
  expireTime: 3600000 #token有效时间 3600000毫秒 ==> 60分钟
# 微信小程序配置码
APPID: 自己的appid
APPSECRET: 自己的密匙

配置文件:Pom.xml 

添加需要依赖 

<!-- jwt支持 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.19.2</version>
</dependency>

<!-- json格式化 -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.9</version>
</dependency>

类:WeChatModel  

接收前端传入参数

package com.mh.common;

import lombok.Data;

/**
 * Date:2023/5/24
 * author:zmh
 * description: 接收小程序传入参数
 **/

@Data
public class WeChatModel {

    /**
     * 临时登录凭证
     */
    private String code;

    /**
     * 微信服务器上的唯一id
     */
    private String openId;
}

 类:WeChatSessionModel

接收调用微信验证code后返回的数据。

package com.mh.common;

import lombok.Data;

/**
 * Date:2023/5/24
 * author:zmh
 * description: 接收微信服务器返回参数
 **/

@Data
public class WeChatSessionModel {

    /**
     * 微信服务器上辨识用户的唯一id
     */
    private String openid;

    /**
     * 身份凭证
     */
    private String session_key;

    /**
     * 错误代码
     */
    private String errcode;

    /**
     * 错误信息
     */
    private String errmsg;
}

 类:UserInfoController

接收临时凭证code,调用业务层方法

@Autowired
private UserInfoService userInfoService;

/**
 * 微信登录
 * @param weChatModel 获取临时凭证code
 * @param response ·
 * @return 返回执行结果
 */
@PostMapping("/login")
public R<String> loginCheck(@RequestBody WeChatModel weChatModel, HttpServletResponse response){
    // 检查登录
    Map<String, Object> resultMap = userInfoService.checkLogin(weChatModel.getCode());
    // resultMap大于1为通过,业务层判断正确后返回用户信息和token,所以应该size为2才正确。
    if (resultMap.size() > 1){
        log.info("创建的token为=>{}", resultMap.get("token"));
        // 将token添加入响应头以及返回用户信息
        response.setHeader(JWTUtils.header, (String) resultMap.get("token"));
        return R.success(resultMap.get("user").toString());
    }else{
        // 当返回map的size为1时,即为报错信息
        return R.error(resultMap.get("errmsg").toString());
    }
}

业务层实现类:UserInfoServiceImpl

登录验证的逻辑处理 

package com.mh.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.mh.common.R;
import com.mh.common.WeChatSessionModel;
import com.mh.common.WeChatModel;
import com.mh.dao.FansInfoDao;
import com.mh.dao.FollowInfoDao;
import com.mh.dao.UserInfoDao;
import com.mh.pojo.FansInfo;
import com.mh.pojo.FollowInfo;
import com.mh.pojo.UserInfo;
import com.mh.service.UserInfoService;
import com.mh.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

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

/**
 * Date:2023/5/24
 * author:zmh
 * description: 用户信息业务层实现类
 **/

@Service
@Slf4j
public class UserInfoServiceImpl extends ServiceImpl<UserInfoDao, UserInfo> implements UserInfoService {

    @Autowired
    private UserInfoDao userInfoDao;

    @Value("${APPID}")
    private String appid;

    @Value("${APPSECRET}")
    private String appsecret;

    @Autowired
    private RestTemplate restTemplate;
	
	// 用于存储用户信息和token
    Map<String,Object> map = new HashMap<>();

    /**
     * 登录验证
     * @param code 临时登录码
     * @return ·
     */
    public Map<String,Object> checkLogin(String code){
        // 根据传入code,调用微信服务器,获取唯一openid
        // 微信服务器接口地址
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid="+appid+ "&secret="+appsecret
                +"&js_code="+ code +"&grant_type=authorization_code";
        String errmsg = "";
        String errcode = "";
        String session_key = "";
        String openid = "";
        WeChatSessionModel weChatSessionModel;
        // 发送请求
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
		// 判断请求是否成功
        if(responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
			// 获取主要内容
            String sessionData = responseEntity.getBody();
            Gson gson = new Gson();
            //将json字符串转化为实体类;
            weChatSessionModel = gson.fromJson(sessionData, WeChatSessionModel.class);
            log.info("返回的数据==>{}",weChatSessionModel);
            //获取用户的唯一标识openid
            openid = weChatSessionModel.getOpenid();
            //获取错误码
            errcode = weChatSessionModel.getErrcode();
            //获取错误信息
            errmsg = weChatSessionModel.getErrmsg();
        }else{
            log.info("出现错误,错误信息:{}",errmsg );
            map.put("errmsg",errmsg);
            return map;
        }
        // 判断是否成功获取到openid
        if ("".equals(openid) || openid == null){
            log.info("错误获取openid,错误信息:{}",errmsg);
            map.put("errmsg",errmsg);
            return map;
        }else{
            // 判断用户是否存在,查询数据库
            LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserInfo::getOpenid, openid);
            UserInfo userInfo = userInfoDao.selectOne(queryWrapper);
            // 不存在,加入数据表
            if (userInfo == null){
                // 填充初始信息
                UserInfo tempUserInfo = new UserInfo(UUID.randomUUID().toString(), openid, "微信用户", 1,"default.png", "",0,0,0);
				// 加入数据表
                userInfoDao.insert(tempUserInfo);
				// 加入map返回
                map.put("user",tempUserInfo);
				// 调用自定义类封装的方法,创建token
                String token = JWTUtils.createToken(tempUserInfo.getId().toString());
                map.put("token",token);
                return map;
            }else{
                // 存在,将用户信息加入map返回
                map.put("user",userInfo);
                String token = JWTUtils.createToken(userInfo.getId().toString());
                map.put("token",token);
                return map;
            }
        }
    }


}

工具类:JWTUtils

 用于创建,验证和更新token

package com.mh.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * Date:2023/5/24
 * author:zmh
 * description:JWT工具类,JWT生成,验证
 **/


@Component
@Data
@ConfigurationProperties(prefix = "jwt")
@Slf4j
public class JWTUtils {
    //定义token返回头部
    public static String header;

    //token前缀
    public static String tokenPrefix;

    //签名密钥
    public static String secret;

    //有效期
    public static long expireTime;

    public void setHeader(String header) {
        JWTUtils.header = header;
    }

    public void setTokenPrefix(String tokenPrefix) {
        JWTUtils.tokenPrefix = tokenPrefix;
    }

    public void setSecret(String secret) {
        JWTUtils.secret = secret;
    }

    public void setExpireTime(long expireTime) {
        JWTUtils.expireTime = expireTime;
    }

    /**
     * 创建TOKEN
     *
     * @param sub
     * @return
     */
    public static String createToken(String sub) {
        return tokenPrefix + JWT.create()
                .withSubject(sub)
                .withExpiresAt(new Date(System.currentTimeMillis() + expireTime))
                .sign(Algorithm.HMAC512(secret));
    }


    /**
     * 验证token
     *
     * @param token
     */
    public static String validateToken(String token) {
        try {
            return JWT.require(Algorithm.HMAC512(secret))
                    .build()
                    .verify(token.replace(tokenPrefix, ""))
                    .getSubject();
        } catch (TokenExpiredException e) {
            log.info("token已过期");
            return "";
        } catch (Exception e) {
            log.info("token验证失败");
            return "";
        }
    }

    /**
     * 检查token是否需要更新
     * @param token ·
     * @return
     */
    public static boolean isNeedUpdate(String token) {
        //获取token过期时间
        Date expiresAt = null;
        try {
            expiresAt = JWT.require(Algorithm.HMAC512(secret))
                    .build()
                    .verify(token.replace(tokenPrefix, ""))
                    .getExpiresAt();
        } catch (TokenExpiredException e) {
            return true;
        } catch (Exception e) {
            log.info("token验证失败");
            return false;
        }
        //如果剩余过期时间少于过期时常的一般时 需要更新
        return (expiresAt.getTime() - System.currentTimeMillis()) < (expireTime >> 1);
    }

}

拦截器配置-自定义拦截器

当用户访问非登录接口时,需要拦截请求,判断用户的请求头是否携带了正确的token,携带了代表登录过了,请求通过,返回数据,若未token验证失败则错误提示。

package com.mh.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Date:2023/5/26
 * author:zmh
 * description: 自定义登录拦截器
 **/

@Slf4j
public class UserLoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求头,header值为Authorization,承载token
        String token = request.getHeader(JWTUtils.header);
        //token不存在
        if (token == null || token.equals("")) {
            log.info("传入token为空");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token为空!");
            return false;
        }
        //验证token
        String sub = JWTUtils.validateToken(token);
        if (sub == null || sub.equals("")){
            log.info("token验证失败");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token验证失败!");
            return false;
        }
        //更新token有效时间 (如果需要更新其实就是产生一个新的token)
        if (JWTUtils.isNeedUpdate(token)){
            String newToken = JWTUtils.createToken(sub);
            response.setHeader(JWTUtils.header,newToken);
        }
        return true;
    }

}

拦截器配置-注册自定义拦截器

package com.mh.config;

import com.mh.utils.UserLoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Date:2023/5/26
 * author:zmh
 * description: MVW配置
 **/

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 注册自定义拦截器
     * @param registry ·
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserLoginInterceptor())
                .addPathPatterns("/api/**") // 拦截地址
                .excludePathPatterns("/api/userInfo/login");// 开放登录路径
    }

}

测试(Postman)

1.测试微信一键登录 

微信小程序获取临时凭证code

 通过返回的code到postman中测试调用后端登录接口

 获取到返回,代表登录成功。

2.测试token的验证 

调用非登录接口,会被拦截进行token的检查。

后端日志输出: 

 携带错误或过期的token,验证失败

 后端日志输出 

 携带正确且在有效期内的token,验证成功,测试通过。

总结

 对于如上代码,其实微信登录的逻辑是比较简单的,代码更多的是在处理身份验证(token验证),后端设置了请求拦截器,会去拦截所有非登录接口,通过检查token判断是否登录过了。

对于前端发送请求,如上只是使用了Postman进行接口的访问,并没有从代码层面去发送请求,那么,其实前端是比较需要去封装请求方法的,在封装的请求方法中加入请求头携带token,避免每一次请求都需要手动加上请求头携带token。

如博文内容存在不足之处请指出,感谢访问。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/575161.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《面试1v1》JVM内存模型

聊聊 JVM 内存模型 我是 javapub&#xff0c;一名 Markdown 程序员从&#x1f468;‍&#x1f4bb;&#xff0c;八股文种子选手。 面试官&#xff1a; 你好&#xff0c;请问你对JVM内存模型有了解吗&#xff1f; 候选人&#xff1a; 当然&#xff0c;JVM内存模型是Java程序运…

【计算机视觉 | 目标检测】术语理解5:Split Shuffle Block、Group Shuffle Block 和复杂非结构化室内场景

文章目录 一、Split Shuffle Block二、Group Shuffle Block三、复杂非结构化室内场景 一、Split Shuffle Block Split Shuffle Block&#xff08;分割混洗块&#xff09;是一种用于深度学习模型的基础组件&#xff0c;旨在增强模型的表征能力和学习能力。该概念最常用于图像分…

3D图像双线性插值

文章目录 前言结论说明&#xff1a;公式 测试 前言 看了一下2d图像的双线性插值的理论&#xff0c;基本上都是在原图上找到对应的浮点坐标 p f p_f pf​后&#xff0c;将以 p f p_f pf​外围的4个点进行计算。计算的方法类似于二维直线方程的理论&#xff0c;但是写成了权重的…

《低代码指南》——维格云低代码管理系统解决方案,成倍降低开发成本

目录 典型场景介绍 一、采购管理 二、产品BOM管理 三、成本核算管理 “我之前是打算自己去开发ERP系统,大概要用上八九个月时间,而且还不是很稳定。但现在用维格云,我们一个人做个一两个月,就可以做到很稳定了。因此,即使需要付出一些学习成本,但无代码的确能极大的帮…

自动驾驶系统中摄像头相对地面的在线标定

文章&#xff1a;Online Camera-to-ground Calibration for Autonomous Driving 作者&#xff1a;Binbin Li, Xinyu Du, Yao Hu, Hao Yu, and Wende Zhang 编辑&#xff1a;点云PCL 欢迎各位加入知识星球&#xff0c;获取PDF论文&#xff0c;欢迎转发朋友圈。文章仅做学术分享&…

记录--前端小票打印、网页打印

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 一、小票打印 目前市面上的小票打印机大多采用的打印指令集为ESC/POS指令&#xff0c;它可以使用ASCII码、十进制、十六进制来控制打印&#xff0c;我们可以使用它来控制字体大小、打印排版、字体加粗…

如何用 ChatGPT 做数据进阶可视化?(三维交互图与动图视频)

你只需输入数据和需求&#xff0c;结果自然来。 自动可视化 在《如何用 ChatGPT 帮你自动分析数据&#xff1f;》这篇文章里&#xff0c;我已经为你介绍过 Code Interpreter 。它是 ChatGPT 的一个模式&#xff0c;目前还在 alpha 测试阶段。 Code Interpreter 可以接收文件输入…

Android 图片编码之必备技能

在进行 Android 开发时&#xff0c;不可避免地会接触到许多图片格式&#xff0c;例如 JPEG、PNG 等。就以 JPEG 格式为例&#xff0c;它是一种有损压缩模式&#xff0c;使用 YCbCr 的颜色空间来保存色彩信息。当需要在屏幕上显示图片时&#xff0c;会将 JPEG 数据解码成 RGB 进…

淘宝用户体验分析方法论

本专题共10篇内容&#xff0c;包含淘宝APP基础链路过去一年在用户体验数据科学领域&#xff08;包括商详、物流、性能、消息、客服、旅程等&#xff09;一些探索和实践经验&#xff0c;本文为该专题第一篇。 在商详页基于用户动线和VOC挖掘用户决策因子带来浏览体验提升&#x…

chatgpt赋能python:Python扫描IP段的简介

Python 扫描 IP 段的简介 Python 是一种广泛应用于数据科学、机器学习、Web 开发等领域的高级编程语言。作为一种通用编程语言&#xff0c;Python 也可以应用于网络安全领域。其中&#xff0c;Python 可以用于扫描 IP 段的网络安全工具开发。 Python 扫描 IP 段 Python 扫描…

5.27下周黄金行情走势预测及开盘操作策略

近期有哪些消息面影响黄金走势&#xff1f;下周黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;周五(5月26日)黄金大幅下跌&#xff0c;主要受到美国数据影响&#xff0c;美国公布的4月PCE和耐用品订单数据向好&#xff0c;再次强化市场对美联储的鹰派押注。现货…

软件测试之自动化测试【webdriver API】

目录 一、webdriver API 1.元素的定位 2.操作测试对象 3.添加等待 3.1 sleep 强制等待 3.2 隐式等待 3.3 显式等待 4.打印信息 5.浏览器的操作 5.1 浏览器的前进和后退 5.2 浏览器滚动条操作 5.3 浏览器最大化及设置浏览器宽、高 6.键盘按键 7. 鼠标事件 8.定位…

chatgpt赋能python:Python找零-让你的生活更轻松

Python 找零 - 让你的生活更轻松 在我们日常生活中&#xff0c;找零是一个很常见的问题。无论是在超市买东西、给朋友拿钱、或者是做商业交易&#xff0c;都需要进行找零操作。而使用 Python 编程语言&#xff0c;可以让这个问题更加简单易懂&#xff0c;让我们来一起学习 Pyt…

Python中的布尔类型以及布尔值介绍

什么是布尔类型&#xff1f; 布尔类型是一种逻辑类型&#xff0c;它只有两个取值&#xff1a;True&#xff08;真&#xff09;和False&#xff08;假&#xff09;。在Python中&#xff0c;True和False是内置的布尔类型常量&#xff0c;用于表示真和假的状态。 布尔运算符 在P…

一场九年前的“出发”:奠基多模态,逐鹿大模型

原创&#xff1a;谭婧 全球AI大模型的技术路线&#xff0c;没有多少秘密&#xff0c;就那几条路线&#xff0c;一只手都数得过来。 而举世闻名的GPT-4浑身上下都是秘密。 这两件事并不矛盾。为什么呢&#xff1f; 这就好比&#xff0c;回答“如何制造一台光刻机&#xff1f;”。…

Yolov5/Yolov7涨点技巧:MobileViT移动端轻量通用视觉transformer,MobileViTAttention助力小目标检测,涨点显著

1. MobileViT介绍 论文:https://arxiv.org/abs/2110.02178 现有博客都是将MobileViT作为backbone引入Yolov5,因此存在的问题点是训练显存要求巨大,本文引入自注意力的Vision Transformer(ViTs):MobileViTAttention MobileViT是一种基于Transformers的轻量级模型,它可以用于…

chatgpt赋能python:Python操作手机:SEO指南

Python 操作手机&#xff1a;SEO 指南 在移动设备占据互联网用户市场大头的今天&#xff0c;应用程序的互动变得越来越受欢迎。这就需要我们在开发和优化网站时将手机端无缝集成到我们的计划中。使用 Python 语言可以有效地实现此目标&#xff0c;本文将探讨如何使用 Python 操…

【一篇文章带你掌握HTML中ul、ol和dl列表的使用 - 超详细】

【一篇文章带你掌握HTML中ul、ol和dl列表的使用 - 超详细】_dl标签_China_YF的博客-CSDN博客 前提 在项目开发过程中&#xff0c;列表是非常常见的&#xff0c;因此列表标签也是我们使用相对频繁的标签&#xff0c;但是当我们遇到列表的时候有没有停顿思考一下&#xff0c;我在…

提醒!手机卡注销前,一定要做的四件事!

现在更换手机卡的情况对小伙伴们来说都是家常便饭的事情了&#xff0c;但是很多小伙伴在手机换号的时候&#xff0c;经常忘记解绑以前手机号绑定的一些业务&#xff0c;为此产生了很多不必要的麻烦&#xff0c;今天的这篇文章就是要告诫大家换号之前一定要做的几件事&#xff0…

基于yolov5的双目鱼体长度检测

前言 在水产养殖行业中&#xff0c;鱼体长度是衡量鱼类品质和成熟度的重要指标。然而&#xff0c;传统的鱼体长度测量方法需要手动测量&#xff0c;不仅耗时耗力还容易出现误差。正好最近做了一个基于双目视觉的鱼体检测项目&#xff0c;在这里和大家分享以下思路。 步骤 第一…