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. 允许Sublime编辑器在Ubuntu上输入中文

    Sublime Text是一款功能非常强大的轻量级代码编辑器,有关功能介绍和使用可以看我另一篇文章的描述http://www.cnblogs.com/jaxu/p/5037547.html 不过,在U ...

  2. DOM访问元素样式和操作元素样式

    在HTML中定义样式的方式有三种:通过<link/>元素包含外部样式表文件(外部样式表).使用<style/>元素定义嵌入式样式(嵌入式样式表).使用style特性定义针对特定 ...

  3. ## Android 6.0 权限申请 ##

    Android 6.0 权限申请 1. 以前的权限申请(sdk<23) 直接在AndroidManifest.xml中申明即可: <uses-permission android:name ...

  4. 【转载】如何自学深度学习技术,大神Yann LeCun亲授建议

    编者按:Quora 上有网友提问:自学机器学习技术,你有哪些建议?(What are your recommendations for self-studying machine learning), ...

  5. HTML5入门以及新标签

    HTML5 学习总结(一)--HTML5入门与新增标签   目录 一.HTML5概要 1.1.为什么需要HTML5 1.2.什么是HTML5 1.3.HTML5现状及浏览器支持 1.4.HTML5特性 ...

  6. Cordova+Asp.net Mvc+GIS跨平台移动应用开发实战1-系统初步搭建(附演示,apk,全部源码)

    1.前言 身处在移动互联网的今天,移动应用开发炙手可热,身为程序猿的我们怎么能错过开发一款我们自己的APP.本人算是一个基于.net的GIS开发入门者(马上就大四啦), 暑假在学校参加GIS比赛有大把 ...

  7. LINQ系列:Linq to Object投影操作符

    投影是指在将序列中的元素转换为一个自定义形式的操作.投影操作符Select和SelectMany用于选择出赋予了适当功能的值.SelectMany操作符可以处理多个集合. LINQ表达式语法: 1. ...

  8. C#设计模式系列:单一职责原则(Single Responsibility Principle)

    1.单一职责原则的核心思想 一个类应该有且只有一个变化的原因. 2.为什么要引入单一职责原则 单一职责原则将不同的职责分离到单独的类,每一个职责都是一个变化的中心.当需求变化时,这个变化将通过更改职责 ...

  9. OpenCASCADE Hidden Line Removal

    OpenCASCADE Hidden Line Removal eryar@163.com Abstract. To provide the precision required in industr ...

  10. Android NDK开发初识

    神秘的Android NDK开发往往众多程序员感到兴奋,但又不知它为何物,由于近期开发应用时,为了是开发的.apk文件不被他人解读(反编译),查阅了很多资料,其中有提到使用NDK开发,怀着好奇的心理, ...