1. 引言

上一节我们讲解了如何创建微信公众号模块,这一节我们就继续跟进,来讲一讲公众号模块如何与系统进行交互。
微信公众号模块作为一个独立的web模块部署,要想与现有的【任务清单】进行交互,我们要想明白以下几个问题:

  1. 如何进行交互?
    ABP模板项目中默认创建了webapi项目,其动态webapi技术允许我们直接访问appservice作为webapi而不用在webapi层编写额外的代码。所以,自然而然我们要通过webapi与系统进行交互。

  2. 通过webapi与系统进行交互,如何确保安全?
    我们知道暴露的webapi如果不加以授权控制,就如同在大街上裸奔。所以在访问webapi时,我们需要通过身份认证来确保安全访问。

  3. 都有哪几种身份认证方式?
    第一种就是大家熟知的cookie认证方式;
    第二种就是token认证方式:在访问webapi之前,先要向目标系统申请令牌(token),申请到令牌后,再使用令牌访问webapi。Abp默认提供了这种方式;
    第三种是基于OAuth2.0的token认证方式:OAuth2.0是什么玩意?建议先看看OAuth2.0 知多少以便我们后续内容的展开。OAuth2.0认证方式弥补了Abp自带token认证的短板,即无法进行token刷新。

基于这一节,我完善了一个demo,大家可以直接访问http://shengjietest.azurewebsites.net/进行体验。

下面我们就以【通过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 Dictionary<string, string>()
        {
            {"TenancyName", "Default"},
            {"UsernameOrEmailAddress", user},
            {"Password", pwd }
        });        //获取token保存到cookie,并设置token的过期日期                    
        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);
        }
    }
}

这段代码中有几个点需要注意:

  1. 指定HttpClientHandler属性UseCookie = true,使用Cookie;

  2. client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));用来指定接受的返回值;

  3. 使用FormUrlEncodedContent进行传参;

  4. 使用var getCookies = handler.CookieContainer.GetCookies(uri);获取返回的Cookie,并添加到_abpWebApiClient.Cookies的集合中,以便下次直接携带cookie信息访问webapi。

2.2. 携带cookie访问webapi

服务器返回的cookie信息在登录成功后已经填充到_abpWebApiClient.Cookies中,我们只需post一个请求到目标api即可。

public async Task<PartialViewResult> SendRequestBasedCookie(){    await CookieBasedAuth();    return await GetUserList(baseUrl + webapiUrl);
}private async Task<PartialViewResult> GetUserList(string url){    try
    {        var users = await _abpWebApiClient.PostAsync<ListResultDto<UserListDto>>(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 Task<string> GetAbpToken(){    var tokenResult = await _abpWebApiClient.PostAsync<string>(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 Task<PartialViewResult> 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文件夹,添加SimpleAuthorizationServerProviderSimpleRefreshTokenProvider类。
其中SimpleAuthorizationServerProvider用来验证客户端的用户名和密码来颁发token;SimpleRefreshTokenProvider用来刷新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.FromResult<object>(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.Get<string>("as:client_id");            var currentClient = context.ClientId;            // enforce client binding of refresh token
            if (originalClient != currentClient)
            {
                context.Rejected();                return Task.FromResult<object>(null);
            }            // chance to change authentication ticket for refresh token requests
            var 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.FromResult<object>(null);
        }    private async Task<AbpLoginResult<Tenant, 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("Don't 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"),

作者:『圣杰』

出处:http://www.cnblogs.com/sheng-jie/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

http://www.cnblogs.com/sheng-jie/p/6755187.html

延伸阅读

学习、实践。再学习,再实践-Java培训,做最负责任的教育,学习改变命运,软件学习,再就业,大学生如何就业,帮大学生找到好工作,lphotoshop培训,电脑培训,电脑维修培训,移动软件开发培训,网站设计培训,网站建设培训学习、实践。再学习,再实践