1. 程式人生 > >ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)

ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)

      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/ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線。如有問題,可以郵件:[email protected] 聯絡我,非常感謝。
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") == 0 && string.CompareOrdinal(clientSecret, "app") == 0; 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); } }
View Code

  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(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 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();
        }
    }
View Code

  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(3),
                    AllowInsecureHttp = true                    
                };
            }
            return _serverOptions;
        }
    }
View Code

  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();
    }
View Code
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 = 0; i < 5; i++)
            {
                var dto = new TestDto
                {
                    Id = i + 1,
                    Name = "name" + i
                };

                list.Add(dto);
            }

            return list;
        }
    }
View Code

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是一樣的

相關推薦

ABP使用OAuth2(Resource Owner Password Credentials Grant模式)

      ABP目前的認證方式有兩種,一種是基於Cookie的登入認證,一種是基於token的登入認證。使用Cookie的認證方式一般在PC端用得比較多,使用token的認證方式一般在移動端用得比較多。ABP自帶的Token認證方式通過UseOAuthBearerAuthentication啟用的,既然已經

IdentityServer4之Resource Owner Password Credentials(資源擁有者密碼憑據許可)

cti -m error .text json conf ret logs send .h2cls { background: #6fa833 none repeat scroll 0 0 !important; color: #fff; font-family: "微軟雅

asp.net core IdentityServer4 實現 resource owner password credentials(密碼憑證)

前言 OAuth 2.0預設四種授權模式(GrantType) 授權碼模式(authorization_code) 簡化模式(implicit) 密碼模式(resource owner password credentials) 客戶端模式(client_credentials) 本章主要介紹密碼模式

IdentityServer4 之 Resource Owner Password Credentials 其實有點尷尬

### 前言 接著IdentityServer4的授權模式繼續聊,這篇來說說 Resource Owner Password Credentials授權模式,這種模式在實際應用場景中使用的並不多,只怪其太開放啦,直接在**客戶端**上拿著使用者名稱和密碼就去**授權伺服器**獲取**AccessToken*

解決AndroidNo resource found that matches android:TextAppearance.Material.Widget.Button.Inverse問題

article 需要 技術分享 ppc def lac hat adl repl 如果在剛夠構建Android Studio項目的時候,運行發現,出現沒找到資源的錯誤!找不到com.android.support/appcompat-v7/23.0.1/res/values

WPF在DLL讀取Resource的方法

方法 apt 用戶 for return resources span add () 原文:WPF在DLL中讀取Resource的方法 WPF是個用戶控件,被WinForm調用。而WinForm是在一個DLL類庫中被調用。試了很多方法,都無法將Resource中的圖讀進程

ABP模塊初始化過程(二)

輸出日誌 介紹 sco accept bootstrap 啟動 參考 進行 初始 在上一篇介紹在StartUp類中的ConfigureService()中的AddAbp方法後我們再來重點說一說在Configure()方法中的UserAbp()方法,還是和前面的一樣我

ABP的攔截器之AuditingInterceptor

esc oot on() users resource self ini intercept 日誌文件   在上面兩篇介紹了ABP中的ValidationInterceptor之後,我們今天來看看ABP中定義的另外一種Interceptor即為AuditingInterce

請讀下面的這句繞口令:ResourceManagerResource Estimator框架介紹

spec 分享圖片 eid rect 其中 分時 -s 發布 mvp 歡迎大家前往騰訊雲+社區,獲取更多騰訊海量技術實踐幹貨哦~ 本文由宋超發表於雲+社區專欄 本文首先介紹了Hadoop中的ResourceManager中的estimator service的框架與運行

django Oauth2 實現第三方登陸

這裡我們用的是微博開放平臺,QQ和微信開發平臺稽核比較嚴格 微博設定開發平臺連結 http://open.weibo.com/index.php 微博設定開發平臺測試應用  https://blog.csdn.net/weixin_43335187/article/d

請讀下面的這句繞口令:ResourceManagerResource Estimator框架介紹與演算法剖析

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~ 本文首先介紹了Hadoop中的ResourceManager中的estimator service的框架與執行流程,然後對其中用到的資源估算演算法進行了原理剖析。 一. Resource Estimator Service的出發點與目標   估計作

SpringBootoauth2.0學習之服務端配置快速上手

現在第三方登入的例子數見不鮮。其實在這種示例當中,oauth2.0是使用比較多的一種授權登入的標準。oauth2.0也是從oauth1.0升級過來的。那麼關於oauth2.0相關的概念及其原理,大家可以參考這篇文章,這篇文章中會有更詳細的解釋,下來我們直接進入正題。 1.1、gradle依賴

[CocoaPods]podspec檔案resource和resource_bundle

相信基本上所有的iOS開發同學針對於CocoaPods都不陌生。即便沒有用過,也是久聞大名如雷貫耳。作為Objective-C和Swift中非常流行的依賴管理工具,它擁有超過10000個公有程式庫,通過一份Podfile檔案和pod install命令就能幫助開發者方便的管理工程依賴。

[2018-12-18]ABP的AsyncCrudAppService介紹

前言 自從寫完上次略長的《用ABP入門DDD》後,針對ABP框架的專案模板初始化,我寫了個命令列工具Abp-CLI,其中子命令abplus init可以從github拉取專案模板以初始化專案。自然而然的,又去處理了aspnetboilerplate/module-zero-core-template這個

ABP把EF訪問sql server改為mysql

在EntityFramewor和Web工程中通過nuget新增mysql.data.entity引用, 預設最新的是6.10。4 在web工程的web.config檔案中更改連線字串為mysql, 如下:       

ABP建立資料庫及相關層的入門

     1.在https://aspnetboilerplate.com/Templates 建立一個demo,例如:           2.建立好的demo的工程目錄如下:

關於mysql5.7建立使用者無法登入及grant操作無效問題的處理

今天用mysql建立使用者時,出現了建立完成後的使用者無法登入,授權無效的情況 CREATE USER testIDENTIFIED BY 'test'; mysql> GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIE

Spring靜態方法使用@Resource註解的變數

Spring框架中使用靜態注入 開發中,有些時候可能會工具類的靜態方法,而這個靜態方法中又使用到了@Resource註解後的變數。如果要直接使用 Utils.staticMethod(),專案會

JS元件系列——在ABP封裝BootstrapTable

 前言:關於ABP框架,博主關注差不多有兩年了吧,一直遲遲沒有嘗試。一方面博主覺得像這種複雜的開發框架肯定有它的過人之處,系統的穩定性和健壯性比一般的開源框架肯定強很多,可是另一方面每每想到它繁瑣的封裝和複雜的開發流程就望而卻步,就這樣遲遲沒有行動。最近在專案裡面用到了ABP框架,沒辦法,只有硬著頭皮上了。經

ABP使用Redis Cache(2)

     上一篇講解了如何在ABP中使用Redis Cache,雖然能夠正常的訪問Redis,但是Redis裡的資訊無法同步更新。本文將講解如何實現Redis Cache與實體同步更新。要實現資料的同步更新,我們能夠想到的最基本、最簡單、也是複雜的方法:在每一個增、刪、改的方法裡新增同步快取的程式碼,說它最簡