ABP目前的认证方式有两种,一种是基于Cookie的登录认证,一种是基于token的登录认证。使用Cookie的认证方式一般在PC端用得比较多,使用token的认证方式一般在移动端用得比较多。ABP自带的Token认证方式通过UseOAuthBearerAuthentication启用的,既然已经自带了Token的认证方式,为什么还要使用OAuth2呢?使用此方式是无法实现Token的刷新的,Token过期后必须通过用户名和密码重新登录,这样客户端会弹出登录框让用户登录,用户体验不是很好,当然也可以在客户端存储用户名和密码,发现Token过期后,在后台自动登录,这样用户也是不知道的,只是存在账号安全问题(其实这些都不是问题,主要原因是使用OAuth2后B格更高)。下面我们来看一下怎么在ABP中使用OAuth2:

1.到ABP的官网上下载一个自动生成的项目模板

2.添加OAuth相关的代码

  a) 添加一个SimpleAuthorizationServerProvider类,用于验证客户端和用户名密码,网上能够找到类似的代码,直接拿来修改一下就可以

作者:loyldg 出处:http://www.cnblogs.com/loyldg/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。如有问题,可以邮件:loyldg@126.com 联系我,非常感谢。
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider, ITransientDependency
{
/// <summary>
/// The _user manager
/// </summary>
private readonly UserManager _userManager; public SimpleAuthorizationServerProvider(UserManager userManager)
{
_userManager = userManager;
} public override async 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") == &&
string.CompareOrdinal(clientSecret, "app") == ;
if (isValidClient)
{
context.OwinContext.Set("as:client_id", clientId);
context.Validated(clientId);
}
else
{
context.SetError("invalid client");
}
} 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 async 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;
} // 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);
} private async Task<AbpUserManager<Tenant, Role, User>.AbpLoginResult> GetLoginResultAsync(OAuthGrantResourceOwnerCredentialsContext context, string usernameOrEmailAddress, string password, string tenancyName)
{
var loginResult = await _userManager.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"), 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.Resolve<ILocalizationService>().L(name, args);
} }

  b)添加一个SimpleRefreshTokenProvider类,用于刷新Token

public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider, ITransientDependency
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>(); public async 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 token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddYears()
};
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties); //_refreshTokens.TryAdd(guid, context.Ticket);
_refreshTokens.TryAdd(guid, refreshTokenTicket); // consider storing only the hash of the handle
context.SetToken(guid);
} public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
} public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
} public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}

  c)添加OAuth的配置信息

/// <summary>
/// Class OAuthOptions.
/// </summary>
public class OAuthOptions
{
/// <summary>
/// Gets or sets the server options.
/// </summary>
/// <value>The server options.</value>
private static OAuthAuthorizationServerOptions _serverOptions; /// <summary>
/// Creates the server options.
/// </summary>
/// <returns>OAuthAuthorizationServerOptions.</returns>
public static OAuthAuthorizationServerOptions CreateServerOptions()
{
if (_serverOptions == null)
{
var provider = IocManager.Instance.Resolve<SimpleAuthorizationServerProvider>();
var refreshTokenProvider = IocManager.Instance.Resolve<SimpleRefreshTokenProvider>();
_serverOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/oauth/token"),
Provider = provider,
RefreshTokenProvider = refreshTokenProvider,
AccessTokenExpireTimeSpan = TimeSpan.FromDays(),
AllowInsecureHttp = true
};
}
return _serverOptions;
}
}

  d)在.web项目里添加启用OAuth的代码,在Startup类的Configure方法里添加如下代码

    app.UseOAuthAuthorizationServer(OAuthOptions.CreateServerOptions());

3.编写测试服务,用于测试

 /// <summary>
/// 测试接口
/// </summary>
public interface ITestAppService : IApplicationService
{
/// <summary>
/// 获取测试信息,可以匿名访问
/// </summary>
/// <returns>返回测试信息</returns>
string GetTestInfo1(); /// <summary>
/// 访问此API需要用户名密码正确才行
/// </summary>
/// <returns></returns>
List<TestDto> GetTestInfo2();
}
public class TestAppService :ApplicationService, ITestAppService
{
public string GetTestInfo1()
{
return DateTime.Now.ToShortTimeString();
} [AbpAuthorize]
public List<TestDto> GetTestInfo2()
{
var list = new List<TestDto>();
for (int i = ; i < ; i++)
{
var dto = new TestDto
{
Id = i + ,
Name = "name" + i
}; list.Add(dto);
} return list;
}
}

4.测试

  a) 登录,需要传递的参数如下:

grant_type:该值固定为password
client_id:客户id
client_secret:客户密钥
username:用户名
password:密码

  如果已经将client_id和client_secret放到Header里,则不需要传递client_id和client_secret,后台先从Header里解析,如果没有找到,则从请求的参数里查找,但是为了更符合标准,推荐将client_id和client_secret放到Header里,服务端获取client_id和client_secret对应代码如下:

    if (!context.TryGetBasicCredentials(out clientId, out clientSecret))

{

context.TryGetFormCredentials(out clientId, out clientSecret);

}

  登录传递的参数信息和登录成功后返回的信息如下:

   b) 刷新Token,需要传递的参数   

grant_type:refresh_token
refresh_token:通过登录获取到的refresh_token
client_id:客户id
client_secret:客户密钥

  和登录一样,client_id和client_secret推荐放到Header里

  刷新传递的参数信息和登录成功后返回的信息如下:

  c) 通过Token访问受保护的API时,需要在Header里添加对应的Token,格式化如下:

  Authorization: Bearer access_token 将access_token替换为对应的值即可

  access_token正确时访问api,返回的信息如下:

access_token不正确或者过期后调用受保护的API返回的信息如下:

5.问题总结

  1. 登录成功后需要将登录后的Identity放到ticket里面,否则使用获取到的access_token访问受保护的API时,会提示用户未登录
  2. 不要在.Api项目的Module里添加如下代码(网上有些使用OAuth的例子里添加了如下代码),添加了该代码后就只能使用Token的方式进行登录认证了,Cookie的认证方式会失效,最终的效果就是网站后台输入了正确的用户名和密码也没法登录。
     Configuration.Modules.AbpWebApi().HttpConfiguration.SuppressDefaultHostAuthentication();
  3. 如果要支持多租户登录,需要将对应参数传递过去,可以直接放到QueryString里面
  4. 除了以上3点,其他和不在ABP里使用OAuth2是一样的

完整源代码下载地址:http://files.cnblogs.com/files/loyldg/UsingOAuth2InABP.src.rar

ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)的更多相关文章

  1. 基于OWIN WebAPI 使用OAuth授权服务【客户端验证授权(Resource Owner Password Credentials Grant)】

    适用范围 前面介绍了Client Credentials Grant ,只适合客户端的模式来使用,不涉及用户相关.而Resource Owner Password Credentials Grant模 ...

  2. OAuth2.0学习(1-6)授权方式3-密码模式(Resource Owner Password Credentials Grant)

    授权方式3-密码模式(Resource Owner Password Credentials Grant) 密码模式(Resource Owner Password Credentials Grant ...

  3. 使用Resource Owner Password Credentials Grant授权发放Token

    对应的应用场景是:为自家的网站开发手机 App(非第三方 App),只需用户在 App 上登录,无需用户对 App 所能访问的数据进行授权. 客户端获取Token: public string Get ...

  4. 基于 IdentityServer3 实现 OAuth 2.0 授权服务【密码模式(Resource Owner Password Credentials)】

    密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码.客户端使用这些信息,向"服务商提供商"索要授权 ...

  5. IdentityServer4 之 Resource Owner Password Credentials 其实有点尴尬

    前言 接着IdentityServer4的授权模式继续聊,这篇来说说 Resource Owner Password Credentials授权模式,这种模式在实际应用场景中使用的并不多,只怪其太开放 ...

  6. 不要使用Resource Owner Password Credentials

    不要使用Resource Owner Password Credentials 文章链接在这里 前言 最近公司项目在做一些重构,因为公司多个业务系统各自实现了一套登录逻辑,比较混乱.所以,现在需要做一 ...

  7. asp.net core IdentityServer4 实现 resource owner password credentials(密码凭证)

    前言 OAuth 2.0默认四种授权模式(GrantType) 授权码模式(authorization_code) 简化模式(implicit) 密码模式(resource owner passwor ...

  8. IdentityServer4之Resource Owner Password Credentials(资源拥有者密码凭据许可)

    IdentityServer4之Resource Owner Password Credentials(资源拥有者密码凭据许可) 参考 官方文档:2_resource_owner_passwords ...

  9. Oauth2.0(六):Resource Owner Password Credentials 授权和 Client Credentials 授权

    这两种简称 Password 方式和 Client 方式吧,都只适用于应用是受信任的场景.一个典型的例子是同一个企业内部的不同产品要使用本企业的 Oauth2.0 体系.在有些情况下,产品希望能够定制 ...

随机推荐

  1. java stopwatch 功能

    C#中有一个stopwatch的功能,主要是用来监测程序执行时间的.java之前一直都在用如下方式完成: public static void main(String[] args) { long s ...

  2. MVC与WebForm的区别

    在初步了解MVC后,发现很多人对于MVC和三层架构开发概念上会有很大的混淆,所以把这两天的学习笔记整理一下,分享给自己的同学们.同时也做一个小Demo,让没有接触过MVC开发的同学,能对MVC有一个简 ...

  3. iOS-App上架流程

    前言:作为一名IOS开发者,把开发出来的App上传到App Store是必须的.下面就来详细介绍下具体流程. 1.打开苹果开发者中心:https://developer.apple.com 打开后点击 ...

  4. Nginx重写

    一.location匹配 1.分类:(1)正则location:~,~*(2)普通location:=,^~,@,无2.匹配规则:(1) =    精确匹配.如果找到,停止搜索(2) ^~    普通 ...

  5. json、javaBean、xml互转的几种工具介绍

    json.javaBean.xml互转的几种工具介绍 转载至:http://blog.csdn.net/sdyy321/article/details/7024236 工作中经常要用到Json.Jav ...

  6. WPF入门教程系列十五——WPF中的数据绑定(一)

    使用Windows Presentation Foundation (WPF) 可以很方便的设计出强大的用户界面,同时 WPF提供了数据绑定功能.WPF的数据绑定跟Winform与ASP.NET中的数 ...

  7. Ajax_05之跨域请求

    1.跨域请求: Cross Domain Request:跨域名的HTTP请求,浏览器从某个域名下的资源访问了另一域名下的另一资源(协议.域名或是端口号不同): ①浏览器允许跨域请求的情形:  < ...

  8. 有关binlog的那点事(二)(mysql5.7.13)

    上次,我们仅仅把binlog做了一个概述,并没有去深入探索(1)binlog file究竟是怎么构成的?(2)binlog file的单元binlog events是怎么构成的?(3)我们能不能伪造出 ...

  9. android studio上的基本动画实现(第一篇)

    hello,各位小伙伴们,在很多小伙伴们刚刚开始学习android的时候,常常会有一些project里面需要有一些基本动画的插入,那么具体是要怎么实现呢?我们接下一起分析一下在android中的几种基 ...

  10. OpenJDK 编译-Linux环境

    说明:笔者是在Ubuntu 16.04虚拟机中编译 OpenJDK 8 源码下载 http://download.java.net/openjdk/jdk8/ 推荐直接下载openjdk-8-src- ...