springboot3实现微信小程序登录及jwt加密

写在前面

本文介绍了springboot开发微信小程序后端服务中,用户登录功能的设计与实现,坚持看完相信对你有帮助。

登录流程

如图:

这是微信官方文档中微信小程序登录的流程时序图,我在图中红色序号标注的五步就是完整的微信小程序登录流程。

流程解析

  1. 小程序端通过wx.login()获取用户登录凭证code。
  2. 小程序将code发送到服务端的登录接口。
  3. 服务端的登录接口使用该code、小程序的AppID和AppSecret向微信服务器发起请求,获取用户的openid。
  4. 服务端拿到openid后,可以根据业务需求生成用户令牌(Token),通常包括用户信息、权限等,并返回给小程序。
  5. 小程序在本地缓存该用户令牌。
  6. 后续小程序发起请求时,在请求头中携带该用户令牌(Token)。
  7. 服务端接收到请求时,验证用户令牌的有效性,确保用户是经过认证和授权的。

具体实现

登录接口设计思路

  1. 接收小程序端传递的code参数。
  2. 使用code、小程序的AppID和AppSecret向微信服务器发起请求,获取用户的openid。
  3. 在数据库用户表中查询是否存在该openid。
  4. 如果存在该openid,则使用查询到的用户信息生成令牌(Token)。
  5. 如果不存在该openid,则进行用户注册操作,将新用户信息插入数据库用户表,并生成令牌(Token)。
  6. 返回生成的令牌(Token)给小程序端。

相关代码

说明

下面代码基于jdk17,发请求用的时jdk自带的http工具类

 Java端

实体类

package com.xad.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

/**
 * @author cxy
 * @create 2024-08-05 14:18:35
 */

@Data
@TableName("user")
public class User {
    @Schema(description = "用户id")
    @TableField(value = "id")
    private Integer id;

    @Schema(description = "用户名称")
    @TableField(value = "user_name")
    private String name;

    @Schema(description = "用户负责内容")
    @TableField(value = "user_range")
    private String range;

    @Schema(description = "用户部门")
    @TableField(value = "user_department")
    private String department;

    @Schema(description = "用户类型")
    @TableField(value = "user_type")
    private Integer type;

    @Schema(description = "用户openid")
    @TableField(value = "user_open_id")
    private String openId;

    @Schema(description = "用户头像")
    @TableField(value = "user_pic")
    private String pic;
}

登录dto

package com.xad.dto;

import com.xad.entity.User;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;

import java.io.Serializable;
import java.util.Map;

/**
 * @author cxy
 * @create 2024-08-05 14:32:47
 */

@Data
public class UserLoginDTO implements Serializable {
    @NotBlank(message = "code不能为空")
    private String code;

    private User userInfo;//用户信息
}

登录返回vo

package com.xad.vo;

import com.xad.entity.User;
import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

/**
 * @author cxy
 * @create 2024-08-05 14:33:13
 */

@Data
@Builder
public class UserLoginVO implements Serializable {
    private String token;//用户登录凭证
    private User userinfo;
}

controller

package com.xad.controller;

import com.xad.dto.UserLoginDTO;
import com.xad.entity.User;
import com.xad.result.Result;
import com.xad.service.UserService;
import com.xad.vo.UserLoginVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

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

/**
 * @author cxy
 * @create 2024-08-05 14:33:59
 */

@Tag(name = "用户接口")
@RequestMapping("/user")
@RestController
@CrossOrigin(origins = "*")
@Slf4j
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @PostMapping("/login")
    @Operation(summary = "微信登录")
    public Result login(@RequestBody UserLoginDTO userLoginDTO) {
        log.info("微信用户登录:{}", userLoginDTO.getCode());

        //微信登录
        UserLoginVO user = userService.login(userLoginDTO);//后绪步骤实现
        return Result.success(user);
    }
}

service

package com.xad.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xad.dto.UserLoginDTO;
import com.xad.entity.User;
import com.xad.vo.UserLoginVO;

public interface UserService extends IService<User> {

    UserLoginVO login(UserLoginDTO userLoginDTO);
}

service.impl

获取openid方法及登录

使用的是jdk17自带的HttpClient,也可以使用okhttp,或者糊涂工具包里面的。

package com.xad.service.impl;

import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xad.dto.UserLoginDTO;
import com.xad.entity.User;
import com.xad.handler.CxyException;
import com.xad.mapper.UserMapper;
import com.xad.result.Result;
import com.xad.result.ResultCodeEnum;
import com.xad.service.UserService;
import com.xad.utils.JwtUtil;
import com.xad.vo.UserLoginVO;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author cxy
 * @create 2024-08-05 14:36:47
 */

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserLoginVO login(UserLoginDTO userLoginDTO) {
        //获取openid
        Map<String, String> map = getOpenid(userLoginDTO.getCode());
        assert map != null;
        if (StringUtils.isBlank(map.get("openid"))) {
            throw new CxyException(ResultCodeEnum.USER_OPENID_ERROR); // openid获取失败
        }

        //查询用户
        User user = userMapper.findByUser(map.get("openid"));
        Map<String, Object> claims = new HashMap<>();

        if (Objects.isNull(user)) {
            // 用户不存在则注册
            user = new User();
            //获取完善信息,用户呢称、头像url
            user.setId(userLoginDTO.getUserInfo().getId());
            user.setType(userLoginDTO.getUserInfo().getType());
            user.setName(userLoginDTO.getUserInfo().getName());
            user.setRange(userLoginDTO.getUserInfo().getRange());
            user.setDepartment(userLoginDTO.getUserInfo().getDepartment());
            user.setOpenId(map.get("openid"));
            // 注册用户
            if (!this.save(user)) {
                throw new CxyException(ResultCodeEnum.FAIL); // 用户注册失败
            }
        }


        return UserLoginVO.builder()
                //.id(Long.valueOf(user.getId()))
                //.openid(user.getOpenId())
                .token(JwtUtil.genToken(claims))
                //.session_key(map.get("session_key"))
                .userinfo(user)
                .build();
    }

    private Map<String, String> getOpenid(String code) {
        HttpClient httpClient = HttpClient.newHttpClient();
        // 构建请求参数字符串
        String params = String.format("appid=%s&secret=%s&js_code=%s&grant_type=%s",
                URLEncoder.encode("appid", StandardCharsets.UTF_8),
                URLEncoder.encode("AppSecret(小程序密钥)	", StandardCharsets.UTF_8),
                URLEncoder.encode(code, StandardCharsets.UTF_8),
                URLEncoder.encode("authorization_code", StandardCharsets.UTF_8));

        // 创建 GET 请求
        String url = "https://api.weixin.qq.com/sns/jscode2session?" + params;
        HttpRequest httpRequest = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .build();

        try {
            // 发送 GET 请求
            HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
            Map<String, String> map = new HashMap<>();
            // 获取响应结果
            int statusCode = httpResponse.statusCode();
            String responseBody = httpResponse.body();
            JSONObject responseJson = new JSONObject(responseBody);
            //JSONObject responseJson = JSONObject.parseObject(responseBody);
            String openid = responseJson.getString("openid");
            String session_key = responseJson.getString("session_key");
            map.put("session_key", session_key);
            map.put("openid", openid);
            // 处理响应结果
            System.out.println("状态代码: " + statusCode);
            System.out.println("响应正文: " + responseBody);
            return map;
        } catch (Exception e) {
            //log.error("发送请求时出错: {}", e.getMessage());
            return null;
        }
    }
}

工具类

package com.xad.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import java.util.Date;
import java.util.Map;

public class JwtUtil {

    private static final String KEY = "xad_java";
	
	//接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)//添加载荷
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))//添加过期时间
                .sign(Algorithm.HMAC256(KEY));//指定算法,配置密钥
    }

	//接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}

配置 jwt

package com.xad.config;


import com.xad.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author cxy
 * @create 2024-07-22 10:02:08
 */

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //登录注册接口不拦截
        registry.addInterceptor(loginInterceptor)
                .excludePathPatterns("/user/login", "/user/register", "/static/**",
                        "/webjars/**",
                        "/doc.html",
                        "/v3/**" );
    }
}

  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个用于构建企业级应用程序的开源框架,它简化了Java开发过程。JWT(JSON Web Token)是一种用于身份验证和授权的开放标准,它基于JSON格式并使用了JWT令牌来安全地传输用户声明信息。Redis 是一个开源的高性能内存数据库,它提供了快速的数据访问和存储功能。微信小程序是一种由微信官方提供的移动应用开发框架,它能够帮助开发者快速搭建小程序应用。 结合这三个技术,可以实现一个安全、高性能的微信小程序后端开发。具体的实现方式可以是使用Spring Boot作为后端框架,通过集成JWT实现身份认证和授权的功能。当用户登录时,后端可以生成一个JWT令牌并返回给前端,在后续的请求中,前端会将该令牌携带在请求头中。后端可以通过验证JWT令牌来验证用户的身份和权限。 另外,可以使用Redis作为身份认证令牌的存储介质。当用户登录时,将JWT令牌存储到Redis中,并设置过期时间。在后续的请求中,后端可以通过查询Redis来验证JWT令牌的有效性,从而实现快速的身份认证。 总结来说,通过使用Spring Boot、JWT和Redis,可以构建一个安全、高性能的微信小程序后端。Spring Boot提供了强大的开发框架,JWT提供了身份认证和授权的功能,而Redis提供了快速的令牌存储和验证功能。这样的组合将能够为微信小程序用户提供安全、高效的服务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值