目录
一、官网登录时序图
二、实现方式
1、换取(session_key,openId):将小程序的code值返回给后端,获取session_key,openId,存入redis中,返回一个sessinId给前端。
2、登录:通过前端传回来的sessionID,去redis中获取(session_kye)并和 encryptedData,iv ,这三个参数,获取据。自定义用户登录状态(通过openID进行关联)
三、代码如下:
controller层:
@RestController
@RequestMapping("/miniprogram/")
@Api(tags = {"小程序登录管理"}, description = "小程序登录")
@Slf4j
public class UserController {
@Resource
private MiniProgramService miniProgramService;
@GetMapping(value = "/getSessionId")
@ApiOperation(value = "获取sessinID-通过前端传递过来的code")
public Result<?> getSessionId(String code){
String sessionId = miniProgramService.getSessionId(code);
if(StringUtils.isNotBlank(sessionId)){
return Result.OK(sessionId);
}
return Result.error("获取sessionId失败");
}
@PostMapping(value = "/autoLogin")
@ApiOperation(value = "autoLogin-用户进行登录")
public Result<?> autoLogin(@RequestBody WXAuth wxAuth){
try {
if(miniProgramService.autoLogin(wxAuth)){
return Result.OK("用户登录成功");
}else {
return Result.error("用户登录失败");
}
} catch (Exception e) {
log.info("用户登录失败,原因是:"+e.getMessage());
return Result.error("用户登录失败");
}
}
}
service层
public interface MiniProgramService extends IService<WXUser> {
/**
*
* @param code 前端传入的code值
* @return 返回sessinid
*/
String getSessionId(String code);
/**
*
* @param wxAuth 前端传入的参数三个
* @return
* @throws Exception
*/
boolean autoLogin(WXAuth wxAuth);
/**
*
* @param token
* @param refresh
* @return
*/
boolean getUserInfo(String token, boolean refresh);
}
WXService (发请求)
@Service
@Slf4j
public class WXService {
@Resource
private RestTemplate restTemplate;
/**
* 获取Headers
* @return HttpHeaders 头部信息
*/
public HttpHeaders getHeaders(int type) {
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(mediaType);
//0为token 1为其他
if (CommonConstant.DATA_INT_0 == type) {
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
}
headers.add("accept", "application/json");
return headers;
}
/**
* 发送数据
* @param url 请求接口地址
* @param headers 请求头
* @param t 请求体参数
* @param type 请求方式
* @return 返回值
*/
public String postHeadersT(String url, HttpHeaders headers, Object t, HttpMethod type) {
try {
log.info("restTemplate 接口,url:" + url);
HttpEntity<Object> entity;
if (t != null) {
entity = new HttpEntity<>(t, headers);
} else {
entity = new HttpEntity<>(headers);
}
ResponseEntity<String> responseEntity;
//将请求头部和参数合成一个请求
responseEntity = restTemplate.exchange(url, type, entity, String.class);
//执行HTTP请求,将返回的结构使用ResultVO类格式化
String resultData = responseEntity.getBody();
log.info("返回数据:" + JSON.toJSONString(resultData));
// if (StringUtils.isNotBlank(resultData)) {
// JSONObject parseObject = JSONObject.parseObject(resultData);
// if (parseObject != null && !parseObject.isEmpty()) {
// if (CommonConstant.DATA_STR_0.equals(parseObject.get(CommonConstant.REST_TEMPLATE_RESULT_CODE).toString())
// || CommonConstant.DATA_STR_200.equals(parseObject.get(CommonConstant.REST_TEMPLATE_RESULT_CODE).toString())){
// return resultData;
// }
// }
// }
return resultData;
} catch (Exception e) {
log.error("发送数据异常,url为" + url + "异常原因为:" + e);
return null;
}
}
}
WXServiceExt extends WXService
@Service
@Slf4j
public class WXServiceExt extends WXService {
@Value("${wx.appid}")
String appid;
@Value("${wx.secret}")
String secret;
public JSONObject sendWX(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session?appid="+appid+"&secret="+secret+"&js_code="+code+"&grant_type=authorization_code";
String resultData = postHeadersT(url, getHeaders(CommonConstant.DATA_INT_1), null, HttpMethod.GET);
JSONObject parseObject = JSONObject.parseObject(resultData);
return parseObject;
}
}
serviceImpl层
@Slf4j
@Service
public class MiniProgramServiceImpl extends ServiceImpl<MiniUserMapper, WXUser> implements MiniProgramService {
@Resource
WXServiceExt wxServiceExt;
@Autowired
RedisTemplate redisTemplate;
@Autowired
MiniProgramService miniProgramService;
@Autowired
private JWTUtil jwtUtil;
@Override
public String getSessionId(String code) {
try {
JSONObject jsonObject = wxServiceExt.sendWX(code);
String s = jsonObject.toString();
if(s.contains(CommonConstant.ERRMSG)){
throw new UnsupportedOperationException("当前code 值"+code + "已经使用,原因是:"+s);
}
String uuid = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(CommonConstant.WX_SESSION_ID + uuid, jsonObject, 10, TimeUnit.MINUTES);
return uuid;
} catch (Exception e) {
log.info("获取session_key,openid失败,原因是:" + e.getMessage());
return "";
}
}
//使用jwt技术生产要给token,提供给前端 token 令牌,用户在下一次登录的时候,携带token访问
//后端进行验证,token 知道是哪个用户登录u的
@Override
public boolean autoLogin(WXAuth wxAuth) {
try {
String json = redisTemplate.opsForValue().get(CommonConstant.WX_SESSION_ID + wxAuth.getSessionId()).toString();
//获取redis中的session_key
String sessionKey = JSON.parseObject(json).get(CommonConstant.SESSION_KEY).toString();
String openId = JSON.parseObject(json).get(CommonConstant.OPPENID).toString();
//解析加密数据
String userInfo = WXUtils.wxDecrypt(wxAuth.getEncryptedData(), sessionKey, wxAuth.getIv());
WXUser wxUser = JSON.parseObject(userInfo, WXUser.class);
WXUser user = miniProgramService.getOne(new QueryWrapper<WXUser>().eq(CommonConstant.OPPEN_ID, openId).last("limit 1"));
if (!Objects.isNull(user)) {
return this.login(user);
} else {
//需要注册
return this.register(wxUser,openId);
}
} catch (Exception e) {
log.info("用户登录/注册失败,原因是:"+e.getMessage());
return false;
}
}
private boolean register(WXUser wxUser,String openId) {
try {
WXUser user = miniProgramService.getOne(new QueryWrapper<WXUser>().eq(CommonConstant.OPPEN_ID, openId)
.eq(CommonConstant.IDEL,CommonConstant.DATA_INT_0).last("limit 1"));
if(Objects.isNull(user)){
wxUser.setOpenId(openId);
wxUser.setIdel(CommonConstant.DATA_INT_0);
miniProgramService.save(wxUser);
}
return this.login(wxUser);
} catch (Exception e) {
log.info("用户注册失败,原因是"+e.getMessage());
return false;
}
}
private boolean login(WXUser wxUser) {
try {
String token = jwtUtil.creatJwtToken(wxUser.getId());
wxUser.setToken(token);
miniProgramService.saveOrUpdate(wxUser);
//保存到redis内,下次就直接跳过验证
redisTemplate.opsForValue().set(CommonConstant.TOKEN + token, JSON.toJSONString(wxUser), 7, TimeUnit.DAYS);
return true;
} catch (Exception e) {
log.info("用户登录失败,原因是"+e.getMessage());
return false;
}
}
@Override
public boolean getUserInfo(String token, boolean refresh) {
/**
* 1 验证token是否有效
* 拿到登录时候的redis 的键,redis.token+token (实体类),
* 获取user, 如果拿到的值是空,也是未登录的状态
*
* 如果refresh 是true,重新生产token,签发,存入redis
* 2 refresh true 代表刷新, 重新生成tokne和redis里 重新存储
* 3 false 直接返回用户信息,redis 中查询出来,直接返回
*/
return false;
}
}
entity层
-----------解析参数
@Data
public class WXAuth implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "后台返回给前端的sessionId")
private String sessionId;
@ApiModelProperty(value = "加密数据")
private String encryptedData;
@ApiModelProperty(value = "偏移量")
private String iv;
}
数据库对应实体
@Data
@TableName("wx_user")
public class WXUser implements Serializable {
private static final long serialVersionUID = 1L;
private java.lang.String id;
private java.lang.String token;
private java.lang.String nickName;
private java.lang.String avatarUrl;
private java.lang.Integer gender;
private java.lang.String country;
private java.lang.String province;
private java.lang.String city;
private java.lang.String mobile;
private java.lang.String openId;
private java.lang.String unionId;
private java.lang.Integer idel;
private java.lang.String password;
private java.lang.String userName;
}
public interface CommonConstant {
/**
* 微信登录
*
*/
String WX_SESSION_ID="wx_session_id_";
String SESSION_KEY="session_key";
String DATA_STR_200 = "200";
String NODE_SUCCESS= "success";
String REST_TEMPLATE_RESULT_CODE= "code";
String OPPEN_ID= "open_id";
String OPPENID= "openid";
Integer DATA_INT_1 = 1;
Integer DATA_INT_0 = 0;
String DATA_STR_0 = "0";
String TOKEN = "token_";
String ERRMSG = "errmsg";
String IDEL = "idel";
}
wxUtils(解密)
public class WXUtils {
public static String wxDecrypt(String encryptedData, String sessionKey, String iv) {
// 被加密的数据
byte[] dataByte = Base64.decode(encryptedData);
// 加密秘钥
byte[] keyByte = Base64.decode(sessionKey);
// 偏移量
byte[] ivByte = Base64.decode(iv);
try {
// 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
int base = 16;
if (keyByte.length % base != 0) {
int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
byte[] resultByte = cipher.doFinal(dataByte);
if (null != resultByte && resultByte.length > 0) {
String result = new String(resultByte, "UTF-8");
// return JSONObject.parseObject(result);
return result;
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
// public static String getUserInfo(String encryptedData, String sessionKey, String iv) 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[] vi = cn.hutool.core.codec.Base64.decode(iv);
// byte[] key = Base64.decode(sessionKey);
// AlgorithmParameterSpec ivSpec = new IvParameterSpec(vi);
// 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");
// }
}