1.开发前准备

参数获取

corpid

每个企业都拥有唯一的corpid,获取此信息可在管理后台“我的企业”-“企业信息”下查看“企业ID”

secret

secret是企业应用里面用于保障数据安全的“钥匙”,每一个应用都有一个独立的访问密钥,为了保证数据的安全,secret务必不能泄漏。

框架

例子使用yishaadmin开源框架为例

2.企业微信OAuth2接入流程

   

  第一步: 用户点击连接

第二步: Index页取得回调Code

  第三步: 根据Code和access_token获取UserID

第四步: 根据UserID到通讯录接口获取其他信息

3.构造网页授权链接

假定当前企业CorpID:wxCorpId
访问链接:http://api.3dept.com/cgi-bin/query?action=get

根据URL规范,将上述参数分别进行UrlEncode,得到拼接的OAuth2链接为:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxCorpId&redirect_uri=http%3a%2f%2fapi.3dept.com%2fcgi-bin%2fquery%3faction%3dget&response_type=code&scope=snsapi_base&state=#wechat_redirect
 

然后新建应用,将链接放入,配置应用可信域名。

官方文档链接:https://developer.work.weixin.qq.com/document/path/91335

4. 调用代码部分

4.1 appsettings配置

"Wx": {
"corpid": "",
"corpsecret": "",
"baseurl": "https://qyapi.weixin.qq.com",
"getUserByCode": "/cgi-bin/user/getuserinfo?access_token={0}&code={1}",
"getToken": "/cgi-bin/gettoken?corpid={0}&corpsecret={1}",
"getUserByUserId": "/cgi-bin/user/get?access_token={0}&userid={1}"
}

4.2  配置IHttpClientFactory调用微信客户端

public static IHttpClientFactory  httpClientFactory { get; set; }

Startup添加以下内容

 public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("WxClient", config =>
{
config.BaseAddress = new Uri(Configuration["Wx:baseurl"]);
config.DefaultRequestHeaders.Add("Accept", "application/json");
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
GlobalContext.httpClientFactory = app.ApplicationServices.GetService<IHttpClientFactory>();
}

4.3 类准备

UserCache 类保存用户id,头像,用户名,以及code,按需新增。

using System;
using System.Collections.Generic;
using System.Text; namespace YiSha.Model.Result
{
public class UserCache
{
/// <summary>
/// 用户id
/// </summary>
public string UserID { get; set; } /// <summary>
/// 头像
/// </summary>
public string Portrait { get; set; } /// <summary>
/// 用户名
/// </summary>
public string Username { get; set; } /// <summary>
/// 缓存最近一次Code 用于刷新时code不更新问题
/// </summary>
public string Code { get; set; }
}
}
ApplicationContext用于缓存Token 过期时间以及用户集合避免多次调用微信接口提高响应速度
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using YiSha.Model.Result; namespace YiSha.Admin.Web.App_Code
{
public static class ApplicationContext
{
/// <summary>
/// 用于多点登录的微信用户
/// </summary>
public const string WxUser = "taskUser"; /// <summary>
/// 用于多点登录的微信密码
/// </summary>
public const string WxPassWord = "123456"; /// <summary>
/// 过期时间
/// </summary>
public static DateTime TimeOutDate { get; set; } /// <summary>
/// Token
/// </summary>
public static string Token { get; set; } /// <summary>
/// 缓存UserID Name 头像
/// </summary>
public static List<UserCache> UserCache { get; set; } = new List<UserCache>();
}
}

获取Token返回实体

using System;
using System.Collections.Generic;
using System.Text; namespace YiSha.Entity.OAManage
{
public class GetTokenResult
{ /// <summary>
/// 错误编号
/// </summary>
public int errcode { get; set; } /// <summary>
/// 错误信息
/// </summary>
public string errmsg { get; set; } /// <summary>
/// Token
/// </summary>
public string access_token { get; set; } /// <summary>
/// 过期时间
/// </summary>
public int expires_in { get; set; }
}
}

获取用户id返回实体

using System;
using System.Collections.Generic;
using System.Text; namespace YiSha.Entity.OAManage
{
//获取用户ID
public class GetUserInfoResult
{
/// <summary>
/// 错误编号
/// </summary>
public int errcode { get; set; } /// <summary>
/// 错误信息
/// </summary>
public string errmsg { get; set; } /// <summary>
/// 用户ID
/// </summary>
public string UserID { get; set; }
}
}

获取用户通讯录返回实体

using System;
using System.Collections.Generic;
using System.Text; namespace YiSha.Entity.OAManage
{
public class GetUserResult
{
/// <summary>
/// 错误编号
/// </summary>
public int errcode { get; set; } /// <summary>
/// 错误信息
/// </summary>
public string errmsg { get; set; } /// <summary>
/// 名称
/// </summary>
public string name { get; set; } /// <summary>
/// 头像
/// </summary>
public string avatar { get; set; } }
}

4.4方法准备

获取Token方法,该方法对Token进行了一个缓存,避免重复获取.

注意事项:
开发者需要缓存access_token,用于后续接口的调用(注意:不能频繁调用gettoken接口,否则会受到频率拦截)。当access_token失效或过期时,需要重新获取。

access_token的有效期通过返回的expires_in来传达,正常情况下为7200秒(2小时),有效期内重复获取返回相同结果,过期后获取会返回新的access_token。
由于企业微信每个应用的access_token是彼此独立的,所以进行缓存时需要区分应用来进行存储。
access_token至少保留512字节的存储空间。
企业微信可能会出于运营需要,提前使access_token失效,开发者应实现access_token失效时重新获取的逻辑。

获取Token文档链接https://developer.work.weixin.qq.com/document/path/91039#15074

    /// <summary>
/// 获取Token
/// </summary>
/// <returns>Item1 Token;Item2 是否成功</returns>
public Tuple<string,bool> GetToken()
{
//判断Token是否存在 以及Token是否在有效期内
if(string.IsNullOrEmpty(ApplicationContext.Token) || ApplicationContext.TimeOutDate > DateTime.Now)
{
//构造请求链接
var requestBuild = GlobalContext.Configuration["Wx:getToken"];
requestBuild = string.Format(requestBuild,
GlobalContext.Configuration["Wx:corpid"],
GlobalContext.Configuration["Wx:corpsecret"]
);
using (var wxClient = GlobalContext.httpClientFactory.CreateClient("WxClient"))
{
var httpResponse = wxClient.GetAsync(requestBuild).Result;
if(httpResponse.StatusCode == System.Net.HttpStatusCode.OK)
{
var dynamic= JsonConvert.DeserializeObject<GetTokenResult>(
httpResponse.Content.ReadAsStringAsync().Result
); ApplicationContext.Token = dynamic.access_token;
//过期5分钟前刷新Token
var expires_in = Convert.ToDouble(dynamic.expires_in - 5 * 60);
ApplicationContext.TimeOutDate = DateTime.Now.AddSeconds(expires_in);
return Tuple.Create(ApplicationContext.Token,true);
}
else
{
return Tuple.Create("获取企业微信Token失败,请稍后重试!", false);
}
}
}
else
{
return Tuple.Create(ApplicationContext.Token, true);
}
}

获取用户ID方法,该方法根据获取到的token,以及回调的code进行请求,得到用户id实体

获取访问用户身份文档链接:https://developer.work.weixin.qq.com/document/path/91023

  /// <summary>
/// 获取用户ID
/// </summary>
/// <param name="token">企业微信Token</param>
/// <param name="code">构造请求的回调code</param>
/// <returns>Item1 UserId;Item2 是否成功</returns>
public Tuple<string, bool> GetUserID(string token,string code)
{
//构造请求链接
var requestBuild = GlobalContext.Configuration["Wx:getUserByCode"];
requestBuild = string.Format(requestBuild,token,code);
using (var wxClient = GlobalContext.httpClientFactory.CreateClient("WxClient"))
{
var httpResponse = wxClient.GetAsync(requestBuild).Result;
if (httpResponse.StatusCode == System.Net.HttpStatusCode.OK)
{
var dynamic = JsonConvert.DeserializeObject<GetUserInfoResult>(
httpResponse.Content.ReadAsStringAsync().Result
); return Tuple.Create(dynamic.UserID, true);
}
else
{
return Tuple.Create("获取用户ID失败,请稍后重试!", false);
}
} }

获取用户通讯录方法,该方法可以通过token和userid进行获取用户头像等信息,按需要调用

读取成员接口文档:https://developer.work.weixin.qq.com/document/path/90196

   /// <summary>
/// 获取用户通讯录
/// </summary>
/// <returns>Item1 头像,获取失败时为错误信息;Item2 名称;Item3 是否成功</returns>
public Tuple<string,string, bool> GetUserByID(string token, string userid)
{
//构造请求链接
var requestBuild = GlobalContext.Configuration["Wx:getUserByUserId"];
requestBuild = string.Format(requestBuild, token, userid);
//建立HttpClient
using (var wxClient = GlobalContext.httpClientFactory.CreateClient("WxClient"))
{
var httpResponse = wxClient.GetAsync(requestBuild).Result;
if (httpResponse.StatusCode == System.Net.HttpStatusCode.OK)
{
var dynamic = JsonConvert.DeserializeObject<GetUserResult>(
httpResponse.Content.ReadAsStringAsync().Result
);
return Tuple.Create(dynamic.avatar, dynamic.name, true);
}
else
{
return Tuple.Create("获取用户ID失败,请稍后重试!","", false);
}
}
}

4.5调用

  本方法是为了企业微信登录时绕过用户登录直接使用企业微信用户登录,有其他需求根据需要调整。

  index 中使用code参数获取回调传进来的code,调用GetToken方法获取Token,然后根据Token和Code获取UserID,最后根据UserID和Token获取通讯录的头像和名称。需要注意的是我们要对每个用户最新的code进行缓存,在企业微信内部浏览器时刷新code参数不会变动,但是code只能使用一次会导致接口调用失败。

 [HttpGet]
public async Task<IActionResult> Index(string code)
{
OperatorInfo operatorInfo = default;
TData<List<MenuEntity>> objMenu = await menuBLL.GetList(null);
List<MenuEntity> menuList = objMenu.Data;
menuList = menuList.Where(p => p.MenuStatus == StatusEnum.Yes.ParseToInt()).ToList();
if (code != null)//企业微信登录
{
//获取联系人 从内存中取||从接口取
string username, portrait = default;
bool issuccess2 = default; //缓存最近的一次code 用于刷新URL时重复code请求失败
var codeCache = ApplicationContext.UserCache.FirstOrDefault(o => o.Code == code);
if(codeCache == null)
{
//获取token Token时间为过期时间减5分钟
var (token, issuccess) = GetToken();
if (!issuccess) return RedirectToAction("error1", new { errormessage = token });
//获取userid
var (userid, issuccess1) = GetUserID(token, code);
if (!issuccess1) return RedirectToAction("error1", new { errormessage = userid }); var useridCache = ApplicationContext.UserCache.FirstOrDefault(o => o.UserID == userid);
if (useridCache == null)//不存在缓存中
{
(portrait, username, issuccess2) = GetUserByID(token, userid);
if (!issuccess2) return RedirectToAction("error1", new { errormessage = portrait });
//加缓存
ApplicationContext.UserCache.Add(new UserCache()
{
Code = code,
Username = username,
Portrait = portrait,
UserID = userid
}); //保存登录日志
var log = logLoginBLL.SaveForm(new LogLoginEntity
{
Remark = username,
ExtraRemark = token + ":" + userid
});
}
else//从缓存中获取用户信息
{
username = useridCache.Username;
portrait = useridCache.Portrait;
//更新最新code
useridCache.Code = code;
}
}
else
{
username = codeCache.Username;
portrait = codeCache.Portrait;
} //模拟登录
TData<UserEntity> userObj = await userBLL.CheckLogin(ApplicationContext.WxUser
, ApplicationContext.WxPassWord
, (int)PlatformEnum.Web);
if (userObj.Tag == 1)
{
await new UserBLL().UpdateUser(userObj.Data);
await Operator.Instance.AddCurrent(userObj.Data.WebToken);
var op = await Operator.Instance.Current();
AuthorizeListWhere(op);
}
//构建前端返回的用户名 以及头像
operatorInfo = new OperatorInfo();
operatorInfo.RealName = username;
operatorInfo.UserName = username;
operatorInfo.Portrait = portrait;
}
else//正常网页登录
{ operatorInfo = await Operator.Instance.Current();
if (operatorInfo == null) return RedirectToAction("Login");
if (operatorInfo.IsSystem != 1)
{
AuthorizeListWhere(operatorInfo);
}
} //授权筛选
void AuthorizeListWhere(OperatorInfo info)
{
TData<List<MenuAuthorizeInfo>> objMenuAuthorize = menuAuthorizeBLL.GetAuthorizeList(info).Result;
List<long?> authorizeMenuIdList = objMenuAuthorize.Data.Select(p => p.MenuId).ToList();
menuList = menuList.Where(p => authorizeMenuIdList.Contains(p.Id)).ToList();
} new CookieHelper().WriteCookie("UserName", operatorInfo.UserName, false);
new CookieHelper().WriteCookie("RealName", operatorInfo.RealName, false);
ViewBag.OperatorInfo = operatorInfo;
ViewBag.MenuList = menuList;
return View();
}

Index.Html调整

5.截图

.NET Core企业微信网页授权登录的更多相关文章

  1. Asp.Net Core 企业微信静默授权

    企业微信接口文档 1.构造授权网页链接 2.回调获取到 Code 通过code+access_token去请求用户信息 3.获取access_token 调试准备工作 -->内网穿透+域名 推荐 ...

  2. JustAuth 1.15.9 版发布,支持飞书、喜马拉雅、企业微信网页登录

    新增 修复并正式启用 飞书 平台的第三方登录 AuthToken 类中新增 refreshTokenExpireIn 记录 refresh token 的有效期 PR 合并 Github #101:支 ...

  3. php 微信登录 公众号 获取用户信息 微信网页授权

    php 微信登录 公众号 获取用户信息 微信网页授权 先自己建立两个文件: index.php  和  getUserInfo.php index.php <?php //scope=snsap ...

  4. PHP微信公众平台oauth2.0网页授权登录类的封装demo

    一.微信授权使用的是OAuth2.0授权的方式.主要有以下简略步骤: 第一步:用户同意授权,获取code 第二步:通过code换取网页授权access_token 第三步:拉取用户信息(需scope为 ...

  5. 玩玩微信公众号Java版之六:微信网页授权

    我们经常会访问一些网站,用微信登录的时候需要用到授权,那么微信网页授权是怎么一回事呢,一起来看看吧!   参考官方文档:https://mp.weixin.qq.com/wiki?t=resource ...

  6. 服务号使用微信网页授权(H5应用等)

    获取授权准备 AppId 服务号已经认证且获取到响应接口权限 设置网页授权域名 公众号设置 - 功能设置 - 网页授权域名.注意事项: 回调页面域名或路径需使用字母.数字及"-"的 ...

  7. Java微信公众平台开发(十六)--微信网页授权(OAuth2.0授权)获取用户基本信息

    转自:http://www.cuiyongzhi.com/post/78.html 好长时间没有写文章了,主要是最近的工作和生活上的事情比较多而且繁琐,其实到现在我依然还是感觉有些迷茫,最后还是决定静 ...

  8. 微信网页授权access_token与基础支持的access_token

    问题1:网页授权access_token与分享的jssdk中的access_token一样吗? 答:不一样.网页授权access_token 是一次性的,而基础支持的access_token的是有时间 ...

  9. Java实现微信网页授权

    开发前的准备: 1.需要有一个公众号(我这里用的测试号),拿到AppID和AppSecret: 2.进入公众号开发者中心页配置授权回调域名.具体位置:接口权限-网页服务-网页账号-网页授权获取用户基本 ...

随机推荐

  1. uoj450 【集训队作业2018】复读机(生成函数,单位根反演)

    uoj450 [集训队作业2018]复读机(生成函数,单位根反演) uoj 题解时间 首先直接搞出单个复读机的生成函数 $ \sum\limits_{ i = 0 }^{ k } [ d | i ] ...

  2. Spring Boot 传参 序列化和反序列化

    序列化 反序列化

  3. 下面这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d"?

    对于如下代码: String s1 = "a"; String s2 = s1 + "b"; String s3 = "a" + " ...

  4. @RequestMapping 注解?

    该注解是用来映射一个URL到一个类或一个特定的方处理法上.

  5. Redis的Jedis操作(五)

    需要把jedis依赖的jar包添加到工程中.Maven工程中需要把jedis的坐标添加到依赖. 推荐添加到服务层. 1.连接单机版 第一步:创建一个Jedis对象.需要指定服务端的ip及端口. 第二步 ...

  6. BIO、NIO、AIO的区别

    一.基本概念 1.BIO:同步阻塞IO 2.NIO:同步非阻塞IO 3.AIO:异步阻塞IO IO操作包括两部分,发起IO请求.IO数据读写.阻塞非阻塞主要针对线程发起IO请求之后是否立即返回来定义的 ...

  7. spring 支持哪些 ORM 框架 ?

    Hibernate iBatis JPA JDO OJB

  8. rbac-基于角色的权限控制系统(8种常用场景再现)

    首先要抛出的问题是在代码世界里什么是权限? url就代表权限 如何实现权限控制? 下面详细介绍控制流程 1.1简单权限控制--表结构 简单权限控制,三个model,五张表 权限表permission ...

  9. 文件缓存tmpfs + 数据缓存SSDB(一)

    一.文件缓存tmpfs 1.特性 1) 基于内存的文件系统,RAW+SWAP,虚拟内存 2) tmpfs使用虚拟内存,/dev/shm/使用共享内存 3) 访问速度快,可以动态调整大小 4) 没有持久 ...

  10. 单例模式应用 | Shared_ptr引用计数管理器

    在我们模拟设计 shared_ptr 智能指针时发现,不同类型的 Shared_ptr 不能使用同一个引用计数管理器,这显然会造成内存上的浪费.因此我们考虑将其设计为单例模式使其所有的 Shared_ ...