微信官方文档:
图解
下图是参考官方再加入本人的思路,红色字体为本人登录的思路
代码实现(供参考)
1. controller 层
package com.mszlu.courseware.controller;
import com.mszlu.courseware.common.Result;
import com.mszlu.courseware.model.WXAuth;
import com.mszlu.courseware.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("getSessionId")
public Result getSessionId(String code) {
return userService.getSessionId(code);
}
@PostMapping("/authLogin")
public Result authLogin(@RequestBody WXAuth wxAuth) {
Result result = userService.authLogin(wxAuth);
log.info("{}", result);
return result;
}
//这是登录后获取用户信息的接口,可以选择不看这部分
@GetMapping("userinfo")
public Result userinfo(@RequestHeader("Authorization") String token,Boolean refresh){
return userService.userinfo(token, refresh);
}
}
2. service 层
package com.mszlu.courseware.service;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.mszlu.courseware.common.RedisKey;
import com.mszlu.courseware.common.Result;
import com.mszlu.courseware.mapper.UserMapper;
import com.mszlu.courseware.model.WXAuth;
import com.mszlu.courseware.model.WxUserInfo;
import com.mszlu.courseware.pojo.User;
import com.mszlu.courseware.pojo.dto.UserDto;
import com.mszlu.courseware.utills.JWTUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
@Value("${wxmini.appid}")
private String appid;
@Value("${wxmini.secret}")
private String secret;
@Autowired
private StringRedisTemplate redisTemplate;
public Result getSessionId(String code) {
/**
* 1. 拼接一个微信登录凭证登录校验接口
* 2. 发起一个 http 的调用, 获取微信的返回结果
* 3. 存到 Redis
* 4. 生成一个 sessionId, 返回给前端,作为当前需要登录用户的标识
* 5. 生成一个 sessionId 用户在点击微信登录的时候, 我们可以标识是谁点击微信登录
*/
String url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
String replaceUrl = url.replace("{0}", appid).replace("{1}", secret).replace("{2}", code);
final String res = HttpUtil.get(replaceUrl);//返回的是Json字符串
String uuid = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(RedisKey.WX_SESSION_ID + uuid, res, 30, TimeUnit.MINUTES);
Map<String, String> map = new HashMap<>();
map.put("sessionId", uuid);
return Result.SUCCESS(map);
}
@Autowired
private WxService wxService;
@Resource
private UserMapper userMapper;
public Result authLogin(WXAuth wxAuth) {
/**
* 1. 通过 wxAuth中的值, 要对它进行解密
* 2. 解密完成后会获取微信用户信息 其中包含 openID,性别,昵称,头像等信息
* 3. openID 是唯一都得,需要去 user 表中查询 openID 是否存在,以此用户的身份登录成功
* 4。 不存在, 新用户,注册流程, 登录成功
* 5. 使用 jwt 技术,生成一个token,提供给前端 token 令牌, 用户在下次访问的时候,携带 token 来访问
* 6. 后端通过对 token 验证,知道此用于是否处于登录状态,以及是哪个用户登录的
*/
try {
final String json = wxService.wxDecrypt(wxAuth.getEncryptedData(), wxAuth.getSessionId(), wxAuth.getIv());
final WxUserInfo wxUserInfo = JSON.parseObject(json, WxUserInfo.class);
final User user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getOpenId, wxUserInfo.getOpenId()));
UserDto userDto = new UserDto();
userDto.from(wxUserInfo);
if (user == null) {
//注册
return this.register(userDto);
} else {
//登录
userDto.setId(user.getId());
return this.login(userDto);
}
} catch (Exception e) {
e.printStackTrace();
}
return Result.FAIL();
}
private Result login(UserDto userDto) {
String token = JWTUtils.sign(userDto.getId());
userDto.setToken((token));
userDto.setOpenId(null);
userDto.setWxUnionId(null);
// 需要吧 token 存入 redis, value 值为 userDto, 下次用户登录访问需要登录的时候,可以根据 token 拿到用户的详细信息
redisTemplate.opsForValue().set(RedisKey.TOKEN + token, JSON.toJSONString(userDto), 7, TimeUnit.DAYS);
return Result.SUCCESS(userDto);
}
private Result register(UserDto userDto) {
// 注册之前 判断用户是否存在
User user = new User();
BeanUtils.copyProperties(userDto, user);
this.userMapper.insert(user);
userDto.setId(user.getId());
return this.login(userDto);
}
public Result userinfo(String token, Boolean refresh) {
/**
* 1. 根据 token 来验证此 token 是否有效
* 2. refresh 如果为 true 代表刷新 重新生成 token 和 Redis 里面重新存储 续期
* 3. false 直接返回用户信息 -》 redis 中查询出来 直接返回
*/
token = token.replace("Bearer ","");
boolean verify = JWTUtils.verify(token);
if (!verify) {
return Result.FAIL("未登录");
}
final String userJson = redisTemplate.opsForValue().get(RedisKey.TOKEN + token);
if (StringUtils.isBlank(userJson)) {
return Result.FAIL("未登录");
}
final UserDto userDto = JSON.parseObject(userJson, UserDto.class);
if (refresh) {
token = JWTUtils.sign(userDto.getId());
userDto.setToken(token);
redisTemplate.opsForValue().set(RedisKey.TOKEN + token, JSON.toJSONString(userDto), 7, TimeUnit.DAYS);
}
return Result.SUCCESS(userDto);
}
}
3.工具类
package com.mszlu.courseware.utills;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.io.UnsupportedEncodingException;
import java.util.Date;
public class JWTUtils {
public static final String AUTH_HEADER_KEY = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 过期时间一周
*/
private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;
private static final String secret = "43dcbc5b-8776-429e-b122-3cae6bd97020";
/**
* 校验token是否正确
*
* @param token 密钥
* @return 是否正确
*/
public static boolean verify(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
/**
* 获得token中的信息无需secret解密也能获得
*
* @return token中包含的id
*/
public static Long getUserId(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("id").asLong();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 签发token
*
* @return 加密的token
*/
public static String sign(Long id) {
try {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
return JWT.create()
.withClaim("id", id)
.withExpiresAt(date)
.sign(algorithm);
} catch (UnsupportedEncodingException e) {
return null;
}
}
/**
* 判断过期
*
* @param token
* @return
*/
public static boolean isExpire(String token) {
DecodedJWT jwt = JWT.decode(token);
return System.currentTimeMillis() > jwt.getExpiresAt().getTime();
}
}
4. 微信返回数据解密的service类
package com.mszlu.courseware.service;
import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mszlu.courseware.common.RedisKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Random;
@Component
public class WxService {
@Autowired
private StringRedisTemplate redisTemplate;
public String wxDecrypt(String encryptedData, String sessionId, String vi) throws Exception {
// 开始解密
String json = redisTemplate.opsForValue().get(RedisKey.WX_SESSION_ID + sessionId);
JSONObject jsonObject = JSON.parseObject(json);
String sessionKey = (String) jsonObject.get("session_key");
byte[] encData = cn.hutool.core.codec.Base64.decode(encryptedData);
byte[] iv = cn.hutool.core.codec.Base64.decode(vi);
byte[] key = Base64.decode(sessionKey);
AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
return new String(cipher.doFinal(encData), "UTF-8");
}
//生成随机用户名,数字和字母组成,
public String getStringRandom(int length) {
StringBuilder val = new StringBuilder();
Random random = new Random();
//参数length,表示生成几位随机数
for (int i = 0; i < length; i++) {
String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
//输出字母还是数字
if ("char".equalsIgnoreCase(charOrNum)) {
//输出是大写字母还是小写字母
int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
val.append((char) (random.nextInt(26) + temp));
} else {
val.append(random.nextInt(10));
}
}
return val.toString();
}
}
5. 模型类(model 层)
package com.mszlu.courseware.model;
import lombok.Data;
@Data
public class WXAuth {
private String encryptedData; //目标密文
private String iv; //解密算法初始向量
private String sessionId;
}
package com.mszlu.courseware.model;
import lombok.Data;
@Data
public class WxUserInfo {
private String openId;
private String nickName;
private String gender;
private String city;
private String province;
private String country;
private String avatarUrl;
private String unionId;
}
6. 实体类(pojo层)
package com.mszlu.courseware.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
*/
@Data
@TableName("user")
public class User implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String nickname;
private String username;
private String password;
private String gender;
/**
* 头像
*/
private String portrait;
/**
* 背景图片
*/
private String background;
private String phoneNumber;
private String openId;
private String wxUnionId;
}
7. dto 层
package com.mszlu.courseware.pojo.dto;
import com.mszlu.courseware.model.WxUserInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDto implements Serializable {
private Long id;
private String nickname;
//更新的时候可以为null(代表不更新)
private String username;
@NotNull
private String password;
private String gender;
@NotNull
private String phoneNumber;
/**
* 背景图片
*/
private String background;
private String portrait;
private String openId;
private String wxUnionId;
//dto拓展属性
private String token;
List<String> permissions;
List<String> roles;
//验证码
private String code;
public void from(WxUserInfo wxUserInfo) {
this.nickname = wxUserInfo.getNickName();
this.portrait = wxUserInfo.getAvatarUrl();
this.username = "";
this.password = "";
this.phoneNumber = "";
this.gender = wxUserInfo.getGender();
this.openId = wxUserInfo.getOpenId();
this.wxUnionId = wxUserInfo.getUnionId();
}
}
了解微信登录最重要还是把整个流程搞清楚,看懂上图即可,代码实现相信还是比较容易的