SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken

微信小程序官方文档

https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html

登录流程

在登录时需要在小程序内部获取code,如何带上code发送给后端,后端带上appid+appsecret+code获取openid+session_key,拿到openid+session_key即可登录获取有用户信息。

  • 1.调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
  • 2.调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key。

之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

后端服务API

文档

https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html

登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。

请求参数

属性 类型 必填 说明

appid string 是 小程序 appId

secret string 是 小程序 appSecret

js_code string 是 登录时获取的 code,可通过wx.login获取

grant_type string 是 授权类型,此处只需填写 authorization_code

项目依赖

xml 复制代码
<!--胡图工具-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.26</version>
        </dependency>

        <!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>1.39.0</version>
        </dependency>

<!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>  <!-- 只在开发环境下使用 -->
        </dependency>
        
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-redis-jackson</artifactId>
            <version>1.39.0</version>
        </dependency>

        <!-- 提供Redis连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

微信小程序登录

配置文件

yml 复制代码
wx:
  appID: wxcfdadd0c4342e898da8d2
  appSecret: 6937ffc6fxda289aa64edd551b26c4021ed30

server:
  redis:
    # Redis数据库索引(默认为0)
    database: 1
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    # password:
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 200
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0
  # 端口
  port: 8080
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
  # token 名称(同时也是 cookie 名称)
  token-name: Authorization
  # token 有效期(单位:秒) 默认30天,-1 代表永久有效
  timeout: 432000
  # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
  active-timeout: 1200
  # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
  is-share: true
  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
  token-style: random-128
  # 是否输出操作日志
  is-log: true

微信令牌配置类

java 复制代码
@Data
@Component
@ConfigurationProperties(prefix = "wx")
public class WxConfig {
    private String appID;
    private String appSecret;
}

Code验证

登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。

控制器
java 复制代码
private final IWxUserService iWxUserService;

@GetMapping("/getSession") //获取用户的唯一标识符 openId
public R getSession(String code){
    return  R.success("登录成功",iWxUserService.getSession(code));
}
实体类
java 复制代码
@Data
public class WxUser {
    private String nickName;
    private String gender;
    private String language;
    private String city;
    private String province;
    private String country;
    private String avatarUrl;
    private Long timestamp;
    private String phoneNumber;
    private String countryCode;
    private String token;
    @JsonIgnore  // 不序列化这个字段
    private String openId;
    @JsonIgnore  // 不序列化这个字段
    private String sessionKey;

}
业务层

openid表示一个用户,每个用户唯一的值,不会变化

java 复制代码
    private  final String WX_LOGIN_URL="https://api.weixin.qq.com/sns/jscode2session";
    private final WxConfig wxConfig;
    //微信登录URL
       @Override
    public WxUser getSession(String code) { //登录凭证校验接口  返回OPENID
        Map<String,String> query  =new HashMap<>();
        query.put("appid",wxConfig.getAppID()); //小程序 appId
        query.put("secret",wxConfig.getAppSecret());//小程序 appSecret
        query.put("js_code",code);//登录时获取的 code,可通过wx.login获取
        query.put("grant_type","authorization_code"); //授权类型,此处只需填写 authorization_code
        String url= Http.getSplice(WX_LOGIN_URL,query); //拼接URL
        String json =Http.get(url);
        JSONObject user = JSONUtil.parseObj(json);
        String openId=user.getStr("openid");
        String sessionKey=user.getStr("session_key");
        WxUser loginUser =new WxUser();

//        WxUser user= WxUserMapper.query(openId);
//        if(user==null) //新用户
//        {
//
//        }else{ //老用户
//
//
//        }

        loginUser.setOpenId(openId);
        loginUser.setSessionKey(sessionKey);

        StpUtil.login(openId); //用户登录
        StpUtil.getSession().set(WxLoginKey.SESSION_KEY,sessionKey ); //会话密钥 后面解密

        loginUser.setToken(StpUtil.getTokenValue());//设置token
       return loginUser;
    }
返回结果

新用户未授权只能拿到token,后续用户授权使用了信息可以保存数据库一起返回。

json 复制代码
{"code":200,"meg":"登录成功","data":{"nickName":null,"gender":null,"language":null,"city":null,"province":null,"country":null,"avatarUrl":null,"timestamp":null,"phoneNumber":null,"countryCode":null,"token":"HApeTHAHjo8uA8S3RJO2Z0QIibTiJoupcrcBE9ReeghTy09yI5zFlm9ztePspkNGmIVjylqubnDcm6r3CzKm8iqV8OFWz1cG9xeLslvFf4RbzSC42OqDr2W0XHjLFoVW"}}

手机号登录(获取)

控制器

java 复制代码
private final IWxUserService iWxUserService;

@PostMapping("/getPhone") //获取到用户的手机号
public R getSession(@RequestBody UserPhoneLoginDto userPhoneLoginDto){
    System.out.println(userPhoneLoginDto);
    WxUser user =iWxUserService.getUserPhoneMessage(userPhoneLoginDto);
    return R.success("操作成功",user);
}

请求实体类

前端需要传递的参数

java 复制代码
@Data
public class UserPhoneLoginDto {
    private String code;
    private String encryptedData;
    private String iv;
    private String sessionId;
}

业务层

java 复制代码
@Override
public WxUser getUserPhoneMessage(UserPhoneLoginDto userPhoneLoginDto) { //用户数据解密  手机号登录
    String sessionKey =  StpUtil.getSession().getString(WxLoginKey.SESSION_KEY);
    //获取session_key解密获取手机号
    WxUser user = WxUtils.phoneDecrypt(userPhoneLoginDto,sessionKey);
    return user;
}
json 复制代码
{"code":200,"meg":"操作成功","data":{"nickName":null,"gender":null,"language":null,"city":null,"province":null,"country":null,"avatarUrl":null,"timestamp":null,"phoneNumber":"122222222","countryCode":"86","token":null}}

HTTP请求工具类封装

java 复制代码
public class Http {
    public static String getSplice(String url,Map<String, String> params){ //优雅拼接GET请求
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url);
        for (Map.Entry<String, String> entry : params.entrySet()) {
            uriBuilder.queryParam(entry.getKey(), entry.getValue());
        }
        return uriBuilder.toUriString();
    }
    public static String get(String url){
        return  HttpUtil.get(url);
    }
}

微信工具类

java 复制代码
public class WxUtils {


    public static WxUser phoneDecrypt(UserPhoneLoginDto userPhoneLoginDto, String tempSession) {
        //微信用户手机号解密

        String sessionKey=tempSession;

        byte[] encData =Base64.decodeBase64(userPhoneLoginDto.getEncryptedData());
        byte[] iv=Base64.decodeBase64(userPhoneLoginDto.getIv());
        byte[] key =Base64.decodeBase64(sessionKey);

        AlgorithmParameterSpec algorithmParameters =new IvParameterSpec(iv);

        try {
            Cipher cipher =Cipher.getInstance("AES/CBC/PKCS5Padding");

            SecretKeySpec keySpec =new SecretKeySpec(key,"AES");

            cipher.init(Cipher.DECRYPT_MODE,keySpec,algorithmParameters);

            String userData =new String(cipher.doFinal(encData),"UTF-8");

            System.out.println(userData);
            WxUser wxUser = JSONUtil.toBean(userData, WxUser.class);

        return wxUser;
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (NoSuchPaddingException e) {
            throw new RuntimeException(e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (IllegalBlockSizeException e) {
            throw new RuntimeException(e);
        } catch (BadPaddingException e) {
            throw new RuntimeException(e);
        }
    }
}
相关推荐
奔波霸的伶俐虫12 小时前
redisTemplate.opsForList()里面方法怎么用
java·开发语言·数据库·python·sql
自在极意功。12 小时前
简单介绍SpringAOP
java·spring·aop思想
__万波__12 小时前
二十三种设计模式(二十三)--责任链模式
java·设计模式·责任链模式
TT哇12 小时前
基础的IDEA基本使用,如:debug流程、常用快捷键
java·ide·intellij-idea
梵得儿SHI12 小时前
(第七篇)Spring AI 核心技术攻坚:国内模型深度集成与国产化 AI 应用实战指南
java·人工智能·spring·springai框架·国产化it生态·主流大模型的集成方案·麒麟系统部署调优
jmxwzy12 小时前
Redis
数据库·redis·缓存
北辰当尹12 小时前
【实习之旅】Kali虚拟机桥接模式ping通百度
java·服务器·桥接模式
零叹12 小时前
Redis热Key——大厂是怎么解决的
数据库·redis·缓存·热key
qq_2562470512 小时前
除了“温度”,如何用 Penalty (惩罚) 治好 AI 的“复读机”毛病?
后端
王五周八12 小时前
基于 Redis+Redisson 实现分布式高可用编码生成器
数据库·redis·分布式