一、 概念
code
code是用户登录凭证
,个人理解为用户的授权码
(需要用户本人授权给小程序,小程序才有权力获取到你这个用户的数据),code需要由小程序向微信服务器获取。
注意: 每个code只能使用一次,且有效期为5分钟。因此,在使用code进行登录时,需要及时将其转换成用户的openid和session_key等信息,以免出现code过期的情况
openid
openid是用来唯一标识用户的一个字符串。在微信小程序中,每个用户的openid都是唯一的。通过openid,小程序可以获取用户的基本信息,如头像、昵称等。
大概就是,不同的用户,在不同的小程序上会有不同的openid,后台可以通过openid+session_key来对使用本小程序的用户进行标识(某个openid就是某个用户)
注意: 同一个用户在不同的小程序中拥有不同的openid。因此,在开发小程序时,不能使用openid来进行用户的唯一性判断。
二、 流程
流程图
步骤分析:
- 小程序端,调用wx.login()获取
code
授权码。调用wx.request()发送请求并携带code,请求开发者服务器(自己编写的后端服务)。 - 开发者服务端,通过
HttpClient
向微信接口服务发送请求
,并携带appId
+appsecret
+code
三个参数。 - 开发者服务端,接收微信接口服务返回的数据,
session_key
+opendId
等。opendId是微信用户的唯一标识。 - 开发者服务端,自定义登录态,生成令牌(token)和openid等数据返回给小程序端,方便后绪请求身份校验。
- 小程序端,收到自定义登录态(token),存储storage。
- 小程序端,后绪通过wx.request()发起业务请求时,携带token。
- 开发者服务端,收到请求后,通过携带的token,解析当前登录用户的id。
- 开发者服务端,身份校验通过后,继续相关的业务逻辑处理,最终返回业务数据。
三、 服务端代码
配置文件
yml
项目:
wechat:
appid: ${sky.wechat.appid}
secret: ${sky.wechat.secret}
#**配置为微信用户生成jwt令牌时使用的配置项:**
user-secret-key: itheima
user-ttl: 7200000
user-token-name: authentication
DTO传code
@Data
public class UserLoginDTO implements Serializable {
private String code;
}
VO类
返回用户的id , openid,token给前端;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserLoginVO implements Serializable {
private Long id;
private String openid;
private String token;
}
逻辑梳理
controller层
- 接收参数,获取
code
,校验数据; - 给用户生成jwt令牌;
service层
- 通过
code
,向微信服务器发送请求获取openid
; - 判断获取不获取得到(openid == null)
2.1 为空 抛出业务异常(code
可能是错误的); - 判断是否为新用户(openid是否在数据库中已经存在)
不存在 : 自动注册
存在: 直接返回;
Controller
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
@PostMapping("/login")
@ApiOperation("微信登录")
public Result<UserLoginVO> login(@PathVariable UserLoginDTO userLoginDTO){
log.info("微信用户登录:{}",userLoginDTO.getCode());
User user = userService.wxLogin(userLoginDTO);
//登录成功后 为微信用户生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.USER_ID, user.getId());
String token = JwtUtil.createJWT(
jwtProperties.getUserSecretKey(),
jwtProperties.getUserTtl(),
claims);
UserLoginVO userLoginVO = UserLoginVO.builder()
.id(user.getId())
.openid(user.getOpenid())
.token(token)
.build();
return Result.success(userLoginVO);
}
}
ServiceImpl
public class UserServiceImpl implements UserService {
@Autowired
private WeChatProperties weChatProperties;
@Autowired
private UserMapper userMapper;
//微信服务接口地址
public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
/**
* 微信登录
* @param userLoginDTO
* @return
*/
public User wxLogin(UserLoginDTO userLoginDTO) {
String openid = getOpenid(userLoginDTO.getCode());
//1、 判断openid是否为空,如果为空表示登录失败,抛出业务异常
if (openid == null) {
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
//2、 判断当前用户是否为新用户
User user = userMapper.getByOpenid(openid);
//3、 如果是新用户,自动完成注册
if (user == null) {
user = User.builder()
.openid(openid)
.createTime(LocalDateTime.now())
.build();
userMapper.insert(user);
}
//4、返回这个用户对象
return user;
}
/**
* 调用微信接口服务,获取微信用户的openid
* @param code
* @return
*/
private String getOpenid (String code){
//调用微信接口服务,获得当前微信用户的openid
Map<String, String> map = new HashMap<>();
map.put("appid", weChatProperties.getAppid());
map.put("secret", weChatProperties.getSecret());
map.put("js_code", code);
map.put("grant_type", "authorization_code");
String json = HttpClientUtil.doGet(WX_LOGIN, map);
JSONObject jsonObject = JSON.parseObject(json);
String openid = jsonObject.getString("openid");
return openid;
}
}
mapper 和 service接口就省略啦
mapper的insert记得键入主键的返回useGeneratedKeys=“true”
接着就是设置用户的 jwt令牌拦截器
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*
* @param request
* @param response
* @param handler 目标方法
* @return true ,代表放行, false,表示禁止放行
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌 因为token存放在请求头中,
String token = request.getHeader(jwtProperties.getUserTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
log.info("当前用户id:", userId);
BaseContext.setCurrentId(userId); //通过LocalThread,把id存到该线程的存储空间中;
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
WebMvcConfiguration配置拦截器;
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenUserInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/user/user/login")
.excludePathPatterns("/user/shop/status");
}
参考链接:https://juejin.cn/post/7212074532340908091