建站外贸企业官网推广,制作外贸网站,衡阳哪有做网站推广的,wordpress前台构架图1. 引言
上一节我们讲解了如何创建微信公众号模块#xff0c;这一节我们就继续跟进#xff0c;来讲一讲公众号模块如何与系统进行交互。 微信公众号模块作为一个独立的web模块部署#xff0c;要想与现有的【任务清单】进行交互#xff0c;我们要想明白以下几个问题#x…1. 引言
上一节我们讲解了如何创建微信公众号模块这一节我们就继续跟进来讲一讲公众号模块如何与系统进行交互。 微信公众号模块作为一个独立的web模块部署要想与现有的【任务清单】进行交互我们要想明白以下几个问题
如何进行交互 ABP模板项目中默认创建了webapi项目其动态webapi技术允许我们直接访问appservice作为webapi而不用在webapi层编写额外的代码。所以自然而然我们要通过webapi与系统进行交互。通过webapi与系统进行交互如何确保安全 我们知道暴露的webapi如果不加以授权控制就如同在大街上裸奔。所以在访问webapi时我们需要通过身份认证来确保安全访问。都有哪几种身份认证方式 第一种就是大家熟知的cookie认证方式 第二种就是token认证方式在访问webapi之前先要向目标系统申请令牌token)申请到令牌后再使用令牌访问webapi。Abp默认提供了这种方式 第三种是基于OAuth2.0的token认证方式OAuth2.0是什么玩意建议先看看OAuth2.0 知多少以便我们后续内容的展开。OAuth2.0认证方式弥补了Abp自带token认证的短板即无法进行token刷新。
基于这一节我完善了一个demo大家可以直接访问http://shengjietest.azurewebsites.net/进行体验。 demo
下面我们就以【通过webapi请求用户列表】为例看一看三种认证方式的具体实现。
2. Cookie认证方式
Cookie认证方式的原理就是在访问webapi之前通过登录目标系统建立连接将cookie写入本地。下一次访问webapi的时候携带cookie信息就可以完成认证。
2.1. 登录目标系统
这一步简单我们仅需提供用户名密码Post一个登录请求即可。 我们在微信模块中创建一个WeixinController public class WeixinController : Controller
{private readonly IAbpWebApiClient _abpWebApiClient;private string baseUrl http://shengjie.azurewebsites.net/;private string loginUrl /account/login;private string webapiUrl /api/services/app/User/GetUsers;private string abpTokenUrl /api/Account/Authenticate;private string oAuthTokenUrl /oauth/token;private string user admin;private string pwd 123qwe;public WeixinController(){_abpWebApiClient new AbpWebApiClient();}
}其中IAbpWebApiClient是对HttpClient的封装用于发送 HTTP 请求和接收HTTP 响应。
下面添加CookieBasedAuth方法来完成登录认证代码如下 public async Task CookieBasedAuth()
{Uri uri new Uri(baseUrl loginUrl);var handler new HttpClientHandler() { AutomaticDecompression DecompressionMethods.None, UseCookies true };using (var client new HttpClient(handler)){client.BaseAddress uri;client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(application/json));var content new FormUrlEncodedContent(new Dictionarystring, string(){{TenancyName, Default},{UsernameOrEmailAddress, user},{Password, pwd }});var result await client.PostAsync(uri, content);string loginResult await result.Content.ReadAsStringAsync();var getCookies handler.CookieContainer.GetCookies(uri);foreach (Cookie cookie in getCookies){_abpWebApiClient.Cookies.Add(cookie);}}
}这段代码中有几个点需要注意
指定HttpClientHandler属性UseCookie true使用Cookieclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(application/json));用来指定接受的返回值使用FormUrlEncodedContent进行传参使用var getCookies handler.CookieContainer.GetCookies(uri);获取返回的Cookie并添加到_abpWebApiClient.Cookies的集合中以便下次直接携带cookie信息访问webapi。
2.2. 携带cookie访问webapi
服务器返回的cookie信息在登录成功后已经填充到_abpWebApiClient.Cookies中我们只需post一个请求到目标api即可。 public async TaskPartialViewResult SendRequestBasedCookie()
{await CookieBasedAuth();return await GetUserList(baseUrl webapiUrl);
}private async TaskPartialViewResult GetUserList(string url)
{try{var users await _abpWebApiClient.PostAsyncListResultDtoUserListDto(url);return PartialView(_UserListPartial, users.Items);}catch (Exception e){ViewBag.ErrorMessage e.Message;}return null;
}
3. Token认证方式
Abp默认提供的token认证方式很简单我们仅需要post一个请求到/api/Account/Authenticate即可请求到token。然后使用token即可请求目标webapi。 但这其中有一个问题就是如果token过期就必须使用用户名密码重写申请token体验不好。
3.1. 请求token public async Taskstring GetAbpToken()
{var tokenResult await _abpWebApiClient.PostAsyncstring(baseUrl abpTokenUrl, new{TenancyName Default,UsernameOrEmailAddress user,Password pwd});this.Response.SetCookie(new HttpCookie(access_token, tokenResult));return tokenResult;
}这段代码中我们将请求到token直接写入到cookie中。以便我们下次直接从cookie中取回token直接访问webapi。
3.2. 使用token访问webapi
从cookie中取回token在请求头中添加Authorization Bearer token即可。 public async TaskPartialViewResult SendRequest()
{var token Request.Cookies[access_token]?.Value;//将token添加到请求头_abpWebApiClient.RequestHeaders.Add(new NameValue(Authorization, Bearer token));return await GetUserList(baseUrl webapiUrl);
}这里面需要注意的是abp中配置app.UseOAuthBearerAuthentication(AccountController.OAuthBearerOptions);使用的是Bearer token所以我们在请求weiapi时要在请求头中假如Authorization信息时使用Bearer token的格式传输token信息Bearer后有一个空格。
4. OAuth2.0 Token认证方式
OAuth2.0提供了token刷新机制当服务器颁发的token过期后我们可以直接通过refresh_token来申请token即可不需要用户再录入用户凭证申请token。
4.1. Abp集成OAuth2.0
在WebApi项目中的Api路径下创建Providers文件夹添加SimpleAuthorizationServerProvider和SimpleRefreshTokenProvider类。 其中SimpleAuthorizationServerProvider用来验证客户端的用户名和密码来颁发tokenSimpleRefreshTokenProvider用来刷新token。 public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider, ITransientDependency
{private readonly LogInManager _logInManager;public SimpleAuthorizationServerProvider(LogInManager logInManager){_logInManager logInManager;}public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){string clientId;string clientSecret;if (!context.TryGetBasicCredentials(out clientId, out clientSecret)){context.TryGetFormCredentials(out clientId, out clientSecret);}var isValidClient string.CompareOrdinal(clientId, app) 0 string.CompareOrdinal(clientSecret, app) 0;if (isValidClient){context.OwinContext.Set(as:client_id, clientId);context.Validated(clientId);}else{context.SetError(invalid client);}return Task.FromResultobject(null);}public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context){var tenantId context.Request.Query[tenantId];var result await GetLoginResultAsync(context, context.UserName, context.Password, tenantId);if (result.Result AbpLoginResultType.Success){//var claimsIdentity result.Identity; var claimsIdentity new ClaimsIdentity(result.Identity);claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));var ticket new AuthenticationTicket(claimsIdentity, new AuthenticationProperties());context.Validated(ticket);}}public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context){var originalClient context.OwinContext.Getstring(as:client_id);var currentClient context.ClientId;// enforce client binding of refresh tokenif (originalClient ! currentClient){context.Rejected();return Task.FromResultobject(null);}// chance to change authentication ticket for refresh token requestsvar newId new ClaimsIdentity(context.Ticket.Identity);newId.AddClaim(new Claim(newClaim, refreshToken));var newTicket new AuthenticationTicket(newId, context.Ticket.Properties);context.Validated(newTicket);return Task.FromResultobject(null);}private async TaskAbpLoginResultTenant, User GetLoginResultAsync(OAuthGrantResourceOwnerCredentialsContext context,string usernameOrEmailAddress, string password, string tenancyName){var loginResult await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);switch (loginResult.Result){case AbpLoginResultType.Success:return loginResult;default:CreateExceptionForFailedLoginAttempt(context, loginResult.Result, usernameOrEmailAddress, tenancyName);//throw CreateExceptionForFailedLoginAttempt(context,loginResult.Result, usernameOrEmailAddress, tenancyName);return loginResult;}}private void CreateExceptionForFailedLoginAttempt(OAuthGrantResourceOwnerCredentialsContext context, AbpLoginResultType result, string usernameOrEmailAddress, string tenancyName){switch (result){case AbpLoginResultType.Success:throw new ApplicationException(Dont call this method with a success result!);case AbpLoginResultType.InvalidUserNameOrEmailAddress:case AbpLoginResultType.InvalidPassword:context.SetError(L(LoginFailed), L(InvalidUserNameOrPassword));break;// return new UserFriendlyException((LoginFailed), (InvalidUserNameOrPassword));case AbpLoginResultType.InvalidTenancyName:context.SetError(L(LoginFailed), L(ThereIsNoTenantDefinedWithName, tenancyName));break;// return new UserFriendlyException((LoginFailed), string.Format(ThereIsNoTenantDefinedWithName{0}, tenancyName));case AbpLoginResultType.TenantIsNotActive:context.SetError(L(LoginFailed), L(TenantIsNotActive, tenancyName));break;// return new UserFriendlyException((LoginFailed), string.Format(TenantIsNotActive {0}, tenancyName));case AbpLoginResultType.UserIsNotActive:context.SetError(L(LoginFailed), L(UserIsNotActiveAndCanNotLogin, usernameOrEmailAddress));break;// return new UserFriendlyException((LoginFailed), string.Format(UserIsNotActiveAndCanNotLogin {0}, usernameOrEmailAddress));case AbpLoginResultType.UserEmailIsNotConfirmed:context.SetError(L(LoginFailed), L(UserEmailIsNotConfirmedAndCanNotLogin));break;// return new UserFriendlyException((LoginFailed), (UserEmailIsNotConfirmedAndCanNotLogin));//default: //Can not fall to default actually. But other result types can be added in the future and we may forget to handle it// //Logger.Warn(Unhandled login fail reason: result);// return new UserFriendlyException((LoginFailed));}}private static string L(string name, params object[] args){//return new LocalizedString(name);return IocManager.Instance.ResolveILocalizationService().L(name, args);}
}public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider, ITransientDependency
{private static ConcurrentDictionarystring, AuthenticationTicket _refreshTokens new ConcurrentDictionarystring, AuthenticationTicket();public Task CreateAsync(AuthenticationTokenCreateContext context){var guid Guid.NewGuid().ToString(N);// maybe only create a handle the first time, then re-use for same client// copy properties and set the desired lifetime of refresh tokenvar refreshTokenProperties new AuthenticationProperties(context.Ticket.Properties.Dictionary){IssuedUtc context.Ticket.Properties.IssuedUtc,ExpiresUtc DateTime.UtcNow.AddYears(1)};var refreshTokenTicket new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);//_refreshTokens.TryAdd(guid, context.Ticket);_refreshTokens.TryAdd(guid, refreshTokenTicket);// consider storing only the hash of the handlecontext.SetToken(guid);return Task.FromResultobject(null);}public Task ReceiveAsync(AuthenticationTokenReceiveContext context){AuthenticationTicket ticket;if (_refreshTokens.TryRemove(context.Token, out ticket)){context.SetTicket(ticket);}return Task.FromResultobject(null);}public void Create(AuthenticationTokenCreateContext context){throw new NotImplementedException();}public void Receive(AuthenticationTokenReceiveContext context){throw new NotImplementedException();}
}以上两段代码我就不做过多解释请自行走读。
紧接着我们在Api目录下创建OAuthOptions类用来配置OAuth认证。 public class OAuthOptions
{/// summary/// Gets or sets the server options./// /summary/// valueThe server options./valueprivate static OAuthAuthorizationServerOptions _serverOptions;/// summary/// Creates the server options./// /summary/// returnsOAuthAuthorizationServerOptions./returnspublic static OAuthAuthorizationServerOptions CreateServerOptions(){if (_serverOptions null){var provider IocManager.Instance.ResolveSimpleAuthorizationServerProvider();var refreshTokenProvider IocManager.Instance.ResolveSimpleRefreshTokenProvider();_serverOptions new OAuthAuthorizationServerOptions{TokenEndpointPath new PathString(/oauth/token),Provider provider,RefreshTokenProvider refreshTokenProvider,AccessTokenExpireTimeSpan TimeSpan.FromSeconds(30),AllowInsecureHttp true};}return _serverOptions;}
}从中我们可以看出主要配置了以下几个属性
TokenEndpointPath 用来指定请求token的路由Provider用来指定创建token的ProviderRefreshTokenProvider用来指定刷新token的ProviderAccessTokenExpireTimeSpan 用来指定token过期时间这里我们指定了30s是为了demo 如何刷新token。AllowInsecureHttp用来指定是否允许http连接。
创建上面三个类之后我们需要回到Web项目的Startup类中配置使用集成的OAuth2.0代码如下 public void Configuration(IAppBuilder app)
{//第一步配置跨域访问app.UseCors(CorsOptions.AllowAll);app.UseOAuthBearerAuthentication(AccountController.OAuthBearerOptions);//第二步使用OAuth密码认证模式app.UseOAuthAuthorizationServer(OAuthOptions.CreateServerOptions());//第三步使用Abpapp.UseAbp();//省略其他代码
}其中配置跨越访问时我们需要安装Microsoft.Owin.CorsNuget包。
至此Abp集成OAuth的工作完成了。
4.2. 申请OAuth token
我们在Abp集成OAuth配置的申请token的路由是/oauth/token所以我们将用户凭证post到这个路由即可申请token public async Taskstring GetOAuth2Token()
{Uri uri new Uri(baseUrl oAuthTokenUrl);var handler new HttpClientHandler() { AutomaticDecompression DecompressionMethods.None };using (var client new HttpClient(handler)){client.BaseAddress uri;client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(application/json));var content new FormUrlEncodedContent(new Dictionarystring, string(){{grant_type, password},{username, user },{password, pwd },{client_id, app },{client_secret, app},});//获取token保存到cookie并设置token的过期日期 var result await client.PostAsync(uri, content);string tokenResult await result.Content.ReadAsStringAsync();var tokenObj (JObject)JsonConvert.DeserializeObject(tokenResult);string token tokenObj[access_token].ToString();string refreshToken tokenObj[refresh_token].ToString();long expires Convert.ToInt64(tokenObj[expires_in]);this.Response.SetCookie(new HttpCookie(access_token, token));this.Response.SetCookie(new HttpCookie(refresh_token, refreshToken));this.Response.Cookies[access_token].Expires Clock.Now.AddSeconds(expires);return tokenResult;}
}在这段代码中我们指定的grant_type password这说明我们使用的是OAuth提供的密码认证模式。其中{client_id, app }, {client_secret, app}搞过微信公众号开发的应该对这个很熟悉用来指定客户端的身份和密钥这边我们直接写死。 通过OAuth的请求的token主要包含四部分
token令牌refreshtoken刷新令牌expires_intoken有效期token_type令牌类型我们这里是Bearer
为了演示方便我们直接把token信息直接写入到cookie中实际项目中建议写入数据库。
4.3. 刷新token
如果我们的token过期了怎么办咱们可以用refresh_token来重新获取token。 public async Taskstring GetOAuth2TokenByRefreshToken(string refreshToken)
{Uri uri new Uri(baseUrl oAuthTokenUrl);var handler new HttpClientHandler() { AutomaticDecompression DecompressionMethods.None, UseCookies true };using (var client new HttpClient(handler)){client.BaseAddress uri;client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(application/json));var content new FormUrlEncodedContent(new Dictionarystring, string(){{grant_type, refresh_token},{refresh_token, refreshToken},{client_id, app },{client_secret, app},});//获取token保存到cookie并设置token的过期日期 var result await client.PostAsync(uri, content);string tokenResult await result.Content.ReadAsStringAsync();var tokenObj (JObject)JsonConvert.DeserializeObject(tokenResult);string token tokenObj[access_token].ToString();string newRefreshToken tokenObj[refresh_token].ToString();long expires Convert.ToInt64(tokenObj[expires_in]);this.Response.SetCookie(new HttpCookie(access_token, token));this.Response.SetCookie(new HttpCookie(refresh_token, newRefreshToken));this.Response.Cookies[access_token].Expires Clock.Now.AddSeconds(expires);return tokenResult;}
}这段代码较直接使用用户名密码申请token的差别主要在参数上{grant_type, refresh_token},{refresh_token, refreshToken}。
4.4. 使用token访问webapi
有了token访问webapi就很简单了。 public async TaskActionResult SendRequestWithOAuth2Token()
{var token Request.Cookies[access_token]?.Value;if (token null){//throw new Exception(token已过期);string refreshToken Request.Cookies[refresh_token].Value;var tokenResult await GetOAuth2TokenByRefreshToken(refreshToken);var tokenObj (JObject)JsonConvert.DeserializeObject(tokenResult);token tokenObj[access_token].ToString();}_abpWebApiClient.RequestHeaders.Add(new NameValue(Authorization, Bearer token));return await GetUserList(baseUrl webapiUrl);
}这段代码中我们首先从cookie中取回access_token若access_token为空说明token过期我们就从cookie中取回refresh_token重新申请token。然后构造一个Authorization将token信息添加到请求头即可访问目标webapi。
5. 总结
本文介绍了三种不同的认证方式进行访问webapi并举例说明。文章不可能面面俱到省略了部分代码请直接参考源码。若有纰漏之处也欢迎大家留言指正。 本文主要参考自以下文章使用OAuth打造webapi认证服务供自己的客户端使用ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)Token Based Authentication using ASP.NET Web API 2, Owin, and Identity 作者圣杰 链接https://www.jianshu.com/p/d14733432dc2 来源简书 著作权归作者所有。商业转载请联系作者获得授权非商业转载请注明出处。