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

发布于:2025-02-11 ⋅ 阅读:(140) ⋅ 点赞:(0)

微信小程序官方文档

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

项目依赖

<!--胡图工具-->
        <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>

微信小程序登录

在这里插入图片描述

配置文件

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

微信令牌配置类

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

Code验证

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

控制器
private final IWxUserService iWxUserService;

@GetMapping("/getSession") //获取用户的唯一标识符 openId
public R getSession(String code){
    return  R.success("登录成功",iWxUserService.getSession(code));
}
实体类
@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表示一个用户,每个用户唯一的值,不会变化

    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,后续用户授权使用了信息可以保存数据库一起返回。

{"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"}}

手机号登录(获取)

控制器

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);
}

请求实体类

前端需要传递的参数

@Data
public class UserPhoneLoginDto {
    private String code;
    private String encryptedData;
    private String iv;
    private String sessionId;
}

业务层

@Override
public WxUser getUserPhoneMessage(UserPhoneLoginDto userPhoneLoginDto) { //用户数据解密  手机号登录
    String sessionKey =  StpUtil.getSession().getString(WxLoginKey.SESSION_KEY);
    //获取session_key解密获取手机号
    WxUser user = WxUtils.phoneDecrypt(userPhoneLoginDto,sessionKey);
    return user;
}
{"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请求工具类封装

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);
    }
}

微信工具类

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);
        }
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到