微信小程序官方文档
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);
}
}
}