企业微信的签名在之前讲了,这里免密登录直接上代码.
@GetMapping(value = "/oAuth2Url")
public String oAuth2Url(HttpServletRequest request, @RequestParam("url") String url) throws IOException {
//目前url为前端传,不需要后端做拼接
String orgId = "企业id";
String oauth2Url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={CORPID}&redirect_uri={REDIRECTURI}&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect".replace("{CORPID}",orgId).replace("{REDIRECTURI}", url);
return oauth2Url;
}
因为这个重定向跳转的地址是固定的,前端拼接还是后端拼接其实都一样,我这边就直接让前端拼好了地址传给我。
但是这里有个大坑,需要注意下:
跳转到他写的重定向地址后,企业微信会返回给他code,前端在跳转后的页面直接调我的下一个接口:用code获取用户信息(code是一次性的)。
@GetMapping(value = { "/oAuth2Check"})
public Result oAuth2Check(HttpServletRequest request, @RequestParam String code) throws Exception {
String token = null;
UserEntity entity = null;
String refreshToken = null;
JSONObject json = new JSONObject();
// 调用获取access_token的接口,默认从缓存中获取
AccessToken accessToken = getAccessToken();
if(accessToken.getErrcode() != 0) return Result.error(accessToken.getErrmsg());
if (Objects.nonNull(accessToken) && StringUtil.isNotBlank(accessToken.getAccessToken())) {
// 通过access_token和code,调用获取用户信息的接口
WXUserResponse wxUser = WeiXinQiYeUtil.getMemberByCode(accessToken.getAccessToken(), code, WeiXinQiYeConstants.AGENTID);
// 根据手机号校验用户正确性
entity = userService.getUserListByMobile(wxUser.getMobile(), wxUser.getName());
//小程序端登录后返回可用权限
List<MenuEntity> list = menuService.getMenuListByName(entity, "小程序首页");
List<String> permissionsList = new ArrayList<>();
if (CollectionUtil.isNotEmpty(list)) {
list.parallelStream().forEach(e -> permissionsList.add(e.getName()));
}
json.put("permissions", permissionsList);
//授权登录,必须走免密登录
MyUsernamePasswordToken myUsernamePasswordToken = new MyUsernamePasswordToken(entity.getAccount());
// 登录不在该处处理,交由shiro处理
Subject subject = SecurityUtils.getSubject();
subject.login(myUsernamePasswordToken);
//生成token并返回
token = JwtUtils.generateToken(new UserToken(entity.getAccount(), entity.getId().toString(), entity.getName(),null));
//生成refreshToken
refreshToken = UUID.randomUUID().replaceAll("-", "");
//保存refreshToken至redis,使用hash结构保存使用中的token以及用户标识
String refreshTokenKey = String.format(JwtUtils.REFRESH_TOKEN_KEY_FORMAT, refreshToken);
String refreshTokenUserKey = String.format(JwtUtils.REFRESH_TOKEN_KEY_FORMAT, entity.getAccount());
template.opsForHash().put(refreshTokenUserKey,
"refreshToken", refreshToken);
template.opsForHash().put(String.format(JwtUtils.REFRESH_TOKEN_KEY_FORMAT, refreshTokenKey),
CommonConstants.TOKEN, token);
template.opsForHash().put(refreshTokenKey,
"userName", entity.getAccount());
//refreshToken设置过期时间
template.expire(refreshTokenKey,
JwtUtils.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
//用户表TOKEN 等于refreshToken
entity.setToken(refreshToken);
entity.setLoserTime(null);
entity.setLoserNumber(CommonDefinition.LOSER_NUMBER);
entity.setLoginType(CommonDefinition.LOGIN_TYPE_SUCCEED);
userService.updateById(entity);
}
json.put(CommonConstants.TOKEN, token);
json.put("refreshTokenKey", refreshToken);
LoginLog(true, "", entity.getAccount(), request, "0");
return new Result(ResultCodeEnum.SUCCESS, json);
}
这里首先通过getAccessToken()获取必要参数access_token。access_token要做缓存,我这里是存在redis中。
WeiXinQiYeUtil.getMemberByCode(accessToken.getAccessToken(), code, WeiXinQiYeConstants.AGENTID)。这里是通过code去获取用户信息,AGENTID为snsapi-userinfo。
/**
* 根据code获取用户信息
* @param token acess_token
* @param code 企业微信返回的code
* @param agentId 查看用户信息范围
*/
public static WXUserResponse getMemberByCode(String token, String code, String agentId){
WXUserResponse wxUser = null;
//获取userId
WXUserResponse result = oAuth2GetUserByCode(token, code, agentId);
logger.info("result :" + result);
if (result.getErrcode().equals("0")) {
if (StringUtils.isNotBlank(result.getUserid())) {
//拼装获取访问用户身份详细信息的url请求
String userurl = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token={ACCESS_TOKEN}&userid={USERID}".replace("{ACCESS_TOKEN}", token).replace("{USERID}", result.getUserid());
//获取访问用户身份的详细信息
String userinfo = HttpUtil.get(userurl);
if(Objects.nonNull(userinfo)){
com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(userinfo);
wxUser = JSON.toJavaObject(jsonObject, WXUserResponse.class);
}
return wxUser;
}
}
return result;
}
/**
* 获取用户userid
* @param token
* @param code
* @param agentId
* @return
*/
public static WXUserResponse oAuth2GetUserByCode(String token, String code, String agentId) {
WXUserResponse wxUser = new WXUserResponse();
String menuUrl = WeiXinQiYeConstants.GET_OAUTH2_URL.replace("ACCESS_TOKEN", token).replace("CODE", code).replace("AGENTID", agentId + "");
String userinfo = HttpUtil.get(menuUrl);
logger.info("userinfo: " + userinfo);
JSONObject jsonObject = null;
if (userinfo != null) {
try {
jsonObject = JSONObject.fromObject(userinfo);
logger.info("jsonObject: " + jsonObject);
if (jsonObject.getString("UserId") != null && jsonObject.getString("UserId").length() > 0) {
wxUser.setErrmsg(jsonObject.getString("errmsg"));
wxUser.setErrcode(jsonObject.getString("errcode"));
wxUser.setUserid(jsonObject.getString("UserId"));
} else {
wxUser.setErrmsg(jsonObject.getString("errmsg"));
wxUser.setErrcode(jsonObject.getString("errcode"));
}
} catch (Exception e) {
wxUser.setErrmsg("accessToken 超时......");
wxUser.setErrcode("42001");
}
}
return wxUser;
}
因为自建应用的作用域为snsapi_base。获取的用户信息非常简单,校验用户直接就用姓名和手机号就可以了,继续返回该用户的用户权限信息,并用shiro的免密登录,jwt生成token,缓存在redis中并返回。