asp.net4.5基础教程(ASP.NETCore学习记录)
https://developer.work.weixin.qq.com/document/path/91025
企业微信提供了OAuth的扫码登录授权方式,可以让企业的网站在浏览器内打开时,引导成员使用企业微信扫码登录授权,从而获取成员的身份信息,免去登录的环节。(注:此授权方式需要用户扫码,不同于“网页授权登录”;仅企业内可以使用此种授权方式,第三方服务商不支持使用。)在进行企业微信授权登录之前,需要先在企业的管理端后台创建一个具备“企业微信授权登录”能力的应用。
1.1 企业微信扫码登陆接入流程1.2 开启网页授权登陆
登录 企业管理端后台->进入需要开启的自建应用->点击 “企业微信授权登录”,进入如下页面
然后点击 "设置授权回调域",输入回调域名,点击“保存”。(域名:需要找运维做解析)
要求配置的授权回调域,必须与访问链接的域名完全一致,如下图:
1.3 构造独立窗口登陆二维码
开发者需要构造如下的链接来获取code参数:
https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=CORPID&agentid=AGENTID&redirect_uri=REDIRECT_URI&state=STATE
参数说明:
参数 |
必须 |
说明 |
appid |
是 |
企业微信的CorpID,在企业微信管理端查看 |
agentid |
是 |
授权方的网页应用ID,在具体的网页应用中查看 |
redirect_uri |
是 |
重定向地址,需要进行UrlEncode |
state |
否 |
用于保持请求和回调的状态,授权请求后原样带回给企业。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议企业带上该参数,可设置为简单的随机数加session进行校验 |
lang |
否 |
自定义语言,支持zh、en;lang为空则从Headers读取Accept-Language,默认值为zh |
若提示“该链接无法访问”,请检查参数是否填写错误,如redirect_uri的域名与网页应用的可信域名不一致。
若用户不在agentid所指应用的可见范围,扫码时会提示无权限。
返回说明:
用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数
redirect_uri?code=CODE&state=STATE
若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数
redirect_uri?state=STATE
示例:
假定当前
企业CorpID:wxCorpId
开启授权登录的应用ID:1000000
登录跳转链接:http://api.3dept.com
state设置为:weblogin@gyoss9
需要配置的授权回调域为:api.3dept.com
根据URL规范,将上述参数分别进行UrlEncode,得到拼接的OAuth2链接为:
https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=wxCorpId&agentid=1000000&redirect_uri=回调域名&state=web_login@gyoss9
在需要展示企业微信网页登录二维码的网站引入如下JS文件(支持https):
步骤一:引入JS文件 (vue框架的话,放在index.html文件中)
<script src="https://rescdn.qqmail.com/node/ww/wwopenmng/js/sso/wwLogin-1.0.0.js" type="text/javascript"></script>
版本:
旧版:http://rescdn.qqmail.com/node/ww/wwopenmng/js/sso/wwLogin-1.0.0.js
新版(20220415更新):http://wwcdn.weixin.qq.com/node/wework/wwopen/js/wwLogin-1.2.7.js
步骤二:在需要使用微信登录的地方实例JS对象(React同理)
注意:从wwLogin-1.2.5.js开始需要使用new WwLogin进行实例化
<template>
<el-tabs v-model="activeName" @tab-click="handleClick" >
<el-tab-pane label="账户密码登录" name="first" class="wechart-pane">
<el-form-item prop="tenant">
<el-input
v-model="loginForm.tenant"
type="text"
auto-complete="off"
placeholder="租户"
>
<i
slot="prefix"
class="el-input__icon el-icon-office-building"
></i>
</el-input>
</el-form-item>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
icon="el-icon-user"
type="text"
auto-complete="off"
placeholder="账号"
>
<i slot="prefix" class="el-input__icon el-icon-user"></i>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
icon="el-icon-unlock"
type="password"
auto-complete="off"
placeholder="密码"
@keyup.enter.native="handleLogin"
>
<i slot="prefix" class="el-input__icon el-icon-unlock"></i>
</el-input>
</el-form-item>
<el-form-item>
<el-button
:loading="loading"
size="medium"
type="primary"
style="width: 100%"
@click.native.prevent="handleLogin"
>
登 录
</el-button>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="扫码登录" name="second" class="wechart-pane" >
<div id="wx_qrcode"></div>
</el-tab-pane>
</el-tabs>
</template>
脚本部分:定义全局变量wwLogin,方便后面销毁
handleClick(tab, event) {
const that = this;
if (tab){
switch (tab.name) {
case 'first':
if (that.wwLogin != null){
that.wwLogin.destroyed(); // 注意wwLogin为实例对象,无需登录时,可手动销毁实例
}
break;
case 'second':
that.wwLogin = new WwLogin({
'id': 'wx_qrcode', //二维码显示区域div的id值
'appid': '企业微信后台的corpid',
'agentid': '企业微信后台的agentid',
'redirect_uri': '回调地址(必须为域名模式)', //http://localhost:53362/connect/token
'state': '',
'href': '',
'lang': 'zh',
})
break;
default:break;
}
}
},
此处先介绍一下abpvnext登陆时访问接口或者服务顺序:
1. 发现文档配置http://localhost:53362/.well-known/openid-configuration
访问结果如下所示:
{
"issuer": "http://localhost:53362",
"jwks_uri": "http://localhost:53362/.well-known/openid-configuration/jwks",
"authorization_endpoint": "http://localhost:53362/connect/authorize",
"token_endpoint": "http://localhost:53362/connect/token",
"userinfo_endpoint": "http://localhost:53362/connect/userinfo",
"end_session_endpoint": "http://localhost:53362/connect/endsession",
"check_session_iframe": "http://localhost:53362/connect/checksession",
"revocation_endpoint": "http://localhost:53362/connect/revocation",
"introspection_endpoint": "http://localhost:53362/connect/introspect",
"device_authorization_endpoint": "http://localhost:53362/connect/deviceauthorization",
"frontchannel_logout_supported": true,
"frontchannel_logout_session_supported": true,
"backchannel_logout_supported": true,
"backchannel_logout_session_supported": true,
"scopes_supported": [
"openid",
"profile",
"email",
"address",
"phone",
"role",
"BaseService",
"InternalGateway",
"WebAppGateway",
"BusinessService",
"offline_access"
],
"claims_supported": [
"sub",
"birthdate",
"family_name",
"gender",
"given_name",
"locale",
"middle_name",
"name",
"nickname",
"picture",
"preferred_username",
"profile",
"updated_at",
"website",
"zoneinfo",
"email",
"email_verified",
"address",
"phone_number",
"phone_number_verified",
"role"
],
"grant_types_supported": [
"authorization_code",
"client_credentials",
"refresh_token",
"implicit",
"password",
"urn:ietf:params:oauth:grant-type:device_code"
],
"response_types_supported": [
"code",
"token",
"id_token",
"id_token token",
"code id_token",
"code token",
"code id_token token"
],
"response_modes_supported": [
"form_post",
"query",
"fragment"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"subject_types_supported": [
"public"
],
"code_challenge_methods_supported": [
"plain",
"S256"
],
"request_parameter_supported": true
}
代码方式获取(Url可配置在appsettings.json或者nacos配置中心):
var client = new HttpClient() ;
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:53362/.well-known/openid-configuration");
http://localhost:53362/connect/token
3. 根据token获取用户信息地址http://localhost:53362/connect/userinfo
4. vue-element-admin菜单权限是使用用户角色来控制的,我们不需要role,通过接口:http://localhost:53362/api/abp/application-configuration
返回结果中的auth.grantedPolicies字段,与对应的菜单路由绑定,就可以实现权限的控制。
2、企业微信扫码成功后回调/connect/token原理:通过查看IdentityServer4的源码发现,通过GrantType来区分不同的授权方式,除了常规的授权方式之外,在defaut条件中,有自定义授权生成token的方式(ProcessExtensionGrantRequestAsync),可以通过这种方式集成旧的业务系统验证,比如,企业微信扫码、小程序授权、短信登陆、微信登陆、钉钉登陆 等等不同第三方进行集成。
2.1自定义授权实现
public class ExtensionGrantTypes
{
//扩展授权名称
public const string WeChatQrCodeGrantType = "WeChat";
}
public class WeChatQrCodeGrantValidator : IExtensionGrantValidator
{
public string GrantType => ExtensionGrantTypes.WeChatQrCodeGrantType;
private readonly DateTime DateTime1970 = new DateTime(1970, 1, 1).ToLocalTime();
private readonly UserManager<Volo.Abp.Identity.IdentityUser> _userManager;
private readonly IJsonSerializer _jsonSerializer;
public WeChatQrCodeGrantValidator(
UserManager<Volo.Abp.Identity.IdentityUser> userLoginManager,
IJsonSerializer jsonSerializer)
{
_userManager = userLoginManager;
_jsonSerializer = jsonSerializer;
}
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
string code = context.Request.Raw.Get("Code");
if (string.IsNullOrEmpty(code))
{
context.Result = new GrantValidationResult(IdentityServer4.Models.TokenRequestErrors.InvalidGrant);
}
//下面第1、2可以封装成接口或服务,参考下面3.1、3.2 部分,方便后期接入
//1、获取企业微信访问令牌access_token
string accessToken = "123123123123";
//2、获取企业微信访问用户身份(企业微信号) UserId
string userId = "ZhangSan";
//3、根据企业微信用户身份userId找到业务库用户表对比,找到真实的用户信息
if (!string.IsNullOrEmpty(userId))
{
context.Result = await ServerValidate("", ""); //可以把UserId传进去
}
else
context.Result = new GrantValidationResult(IdentityServer4.Models.TokenRequestErrors.InvalidGrant);
}
/// <summary>
/// 服务器端验证并输出用户信息,后续自动生成token
/// </summary>
/// <param name="loginProvider"></param>
/// <param name="providerKey"></param>
/// <returns></returns>
private async Task<GrantValidationResult> ServerValidate(string loginProvider, string providerKey)
{
var user = await _userManager.FindByLoginAsync(loginProvider, providerKey); //业务库用户
if (user == null)
return new GrantValidationResult(IdentityServer4.Models.TokenRequestErrors.InvalidGrant);
var principal = new ClaimsPrincipal();
List<ClaimsIdentity> claimsIdentity = new List<ClaimsIdentity>();
ClaimsIdentity identity = new ClaimsIdentity();
identity.AddClaim(new Claim("sub", user.Id.ToString()));
identity.AddClaim(new Claim("tenantid", user.TenantId.ToString())); //租户Id
identity.AddClaim(new Claim("idp", "local"));
identity.AddClaim(new Claim("amr", loginProvider));
long authTime = (long)(DateTime.Now.ToLocalTime() - DateTime1970).TotalSeconds;
identity.AddClaim(new Claim("auth_time", authTime.ToString()));
claimsIdentity.Add(identity);
principal.AddIdentities(claimsIdentity);
return new GrantValidationResult(principal);
}
}
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<IIdentityServerBuilder>(builder => {
builder.AddExtensionGrantValidator<WeChatQrCodeGrantValidator>();
});
}
await CreateClientAsync(
name: "wechat-web",
scopes: commonScopes.Union(new[] {
"IdentityService", "InternalGateway", "WebAppGateway", "BusinessService","WeChat"
}),
grantTypes: new[] { "WeChat" },
//redirectUri: $"http://localhost:44307/authentication/login-callback",
requireClientSecret: false
);
http://localhost:53362/connect/token
是不是发现这个链接熟悉,没错就是上面“@@登陆顺序”部分,前端按之前账号、密码登陆方式调用即可,切换为下面的参数,后续同@@登陆顺序部分一致。
3、企业微信获取token和用户账号3.1 获取访问令牌access_token
请求地址:
https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
#corpid、corpsecret换为自己的corpid、应用secret
返回结果:
{"access_token":"sdfadsf","expires_in":15,"errcode":0,"errmsg":"ok"}
请求地址:
https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
返回结果:
{"UserId":"WangWu","DeviceId":"","errcode":0,"errmsg":"ok"}
获取用户信息(账号、密码)去取token(类似用户账号、密码登录的token)
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com