7

Tôi đã dành tuần trước tạo API cho ứng dụng MVC hiện có và hiện đang cố gắng bảo mật API cùng với việc làm lại bảo mật bên MVC khi cần .Yêu cầu POST MVC mất tiêu đề Cấp phép - cách sử dụng Mã thông báo API khi đã truy xuất

Hiện tại, ứng dụng MVC được thiết lập để sử dụng cookie ứng dụng qua OWIN/OAuth/Identity. Tôi đã cố gắng kết hợp mã thông báo Bearer rằng API Web được thiết lập để tạo ra bất cứ khi nào thực hiện cuộc gọi đến các phương thức API bị hạn chế, nhưng đã có rất ít thành công cho đến nay - yêu cầu GET hoạt động tốt, nhưng yêu cầu POST sẽ mất tiêu đề Cấp phép khi nhận được API.

Tôi đã tạo Ứng dụng khách SDK đang được ứng dụng MVC sử dụng để thực hiện cuộc gọi tới API và đã thử tổng cộng ba phương pháp đặt tiêu đề Cấp quyền cho bất kỳ lệnh gọi nào đến API, tất cả dường như chỉ làm việc tốt cho các yêu cầu GET, nhưng thất bại hoàn toàn cho bất kỳ yêu cầu POST tôi cần phải thực hiện ...

tôi có thể thiết lập các tiêu đề yêu cầu trong bộ điều khiển MVC:

HttpContext.Request.Headers. Thêm ("Ủy quyền", "Bộ đệm" + phản hồi.AccessToken);

(nơi response.AccessToken là dấu hiệu lấy ra trước đó từ API)
tôi có thể thiết lập các tiêu đề yêu cầu thông qua một phương pháp khuyến nông trên Client SDK:

_apiclient.SetBearerAuthentication (token.AccessToken)

hoặc tôi có thể đặt tiêu đề Yêu cầu bằng tay trên Client SDK:

_apiClient.Authentication = new AuthenticationHeaderValue ("Bearer, accessToken);

(Trường hợp accessToken là mã thông báo được truy xuất trước đó, được chuyển đến phương thức Khách hàng được gọi).

Tôi có rất ít việc tiếp tục từ điểm này đến những gì đang gây ra sự cố. Điều duy nhất tôi có thể thu được từ trước đến nay là ASP.NET làm cho tất cả các yêu cầu POST gửi yêu cầu đầu tiên với một tiêu đề Mong đợi cho một phản hồi HTTP 100-Continue, sau đó nó sẽ hoàn thành yêu cầu POST thực tế. Tuy nhiên, có vẻ như khi yêu cầu thứ hai này, tiêu đề Cấp quyền không còn hiện diện và do đó thuộc tính Ủy quyền của API sẽ gây ra phản hồi 401-Không được phép thay vì thực sự chạy phương thức API.

Vì vậy, làm cách nào để lấy mã thông báo Bearer mà tôi có thể truy xuất từ ​​API và sử dụng nó trên các yêu cầu tiếp theo, bao gồm các yêu cầu POST khác nhau mà tôi cần thực hiện?

Ngoài ra, cách tốt nhất để lưu mã thông báo này vào chính ứng dụng MVC là gì? Tôi muốn tránh phải vượt qua chuỗi để mọi phương pháp trong ứng dụng có thể cần nó, nhưng tôi cũng đã được đọc mà lưu trữ nó trong một cookie là một ý tưởng rất xấu vì lý do an ninh.

Một vài điểm thêm rằng sẽ được quan tâm ngay lập tức sau khi tôi có được thông qua vấn đề này:

Có sử dụng OAuth Bearer Tokens có nghĩa là tôi không còn có thể sử dụng ApplicationCookies cho ứng dụng MVC?Và/hoặc nó sẽ làm cho mã sau vô ích trong suốt ứng dụng?

User.Identity.GetUserId()

Hiện nay tôi đang buộc vào cho ý kiến ​​ra API của tôi [Duyệt] thuộc tính để tiếp tục với công việc của tôi, mà rõ ràng là không lý tưởng nhưng nó cho phép tôi tạm thời tiếp tục.

file Startup:

MVC:

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
     ConfigureAuth(app); 
    } 

    private void ConfigureAuth(IAppBuilder app) 
    { 
     app.CreatePerOwinContext(ADUIdentityDbContext.Create); 
     app.CreatePerOwinContext<ADUUserManager>(ADUUserManager.Create); 

     app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions 
           { 
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), 
            //This should be set to FALSE before we move to production. 
            AllowInsecureHttp = true, 
            ApplicationCanDisplayErrors = true, 
            TokenEndpointPath = new PathString("/api/token"), 

           }); 

     app.UseCookieAuthentication(new CookieAuthenticationOptions 
            { 
             AuthenticationType = DefaultAuthenticationTypes.ExternalBearer, 
             CookieName = "ADU", 
             ExpireTimeSpan = TimeSpan.FromHours(2), 
             LoginPath = new PathString("/Account/Login"), 
             SlidingExpiration = true, 

            }); 
    } 
} 

API

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
     HttpConfiguration config = new HttpConfiguration(); 

     config.DependencyResolver = new NinjectResolver(new Ninject.Web.Common.Bootstrapper().Kernel); 

     WebApiConfig.Register(config); 

     ConfigureOAuth(app); 
     app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); 

     app.UseWebApi(config); 
    } 

    public void ConfigureOAuth(IAppBuilder app) 
    { 
     app.CreatePerOwinContext(ADUIdentityDbContext.Create); 
     app.CreatePerOwinContext<ADUUserManager>(ADUUserManager.Create); 

     OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions() 
     { 
      AllowInsecureHttp = true, 
      TokenEndpointPath = new PathString("/api/token"), 
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), 
      Provider = new SimpleAuthorizationServerProvider(), 
     }; 

     //token generation 
     app.UseOAuthAuthorizationServer(oAuthServerOptions); 
     app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); 
    } 
} 


public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider 
{ 
    private IUserBusinessLogic _userBusinessLogic; 

    /// <summary> 
    /// Creates the objects necessary to initialize the user business logic field and initializes it, as this cannot be done by dependency injection in this case. 
    /// </summary> 
    public void CreateBusinessLogic() 
    { 
     IUserRepository userRepo = new UserRepository(); 
     IGeneratedExamRepository examRepo = new GeneratedExamRepository(); 
     IGeneratedExamBusinessLogic examBLL = new GeneratedExamBusinessLogic(examRepo); 
     _userBusinessLogic = new UserBusinessLogic(userRepo, examBLL); 
    } 

    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); } 

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) 
    { 
     context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); 

     //create a claim for the user 
     ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType); 
     identity.AddClaim(new Claim("sub", user.Id)); 
     context.Validated(identity); 
    } 
} 

Trả lời

1

Sau một thỏa thuận tốt thời gian làm việc trên các khía cạnh khác của pro ject, thực hiện các tính năng khác đã thực sự giải quyết điều này dễ dàng hơn - hiện nay có một Trình xử lý đáp ứng bao gồm một phần của API và một phần của Trình xử lý đó lưu tất cả các Tiêu đề từ các Yêu cầu gửi đến và thêm chúng vào các Phản hồi gửi đi. Tôi tin rằng điều này cho phép bên ASP.NET MVC của ứng dụng gửi tiêu đề Cấp phép một lần nữa sau khi yêu cầu 200-OK được gửi ban đầu.

tôi đã sửa đổi thẩm định của tôi để tận dụng vai trò, nhưng tôi sẽ cố gắng để loại trừ mã mà vì nó không nên có liên quan ở đây:

MVC Startup.cs:

public class Startup 
{ 
    public void Configuration(IAppBuilder app) { ConfigureAuth(app); } 

    /// <summary> 
    ///  Configures authentication settings for OAuth. 
    /// </summary> 
    /// <param name="app"></param> 
    private void ConfigureAuth(IAppBuilder app) 
    { 
     app.CreatePerOwinContext(ADUIdentityDbContext.Create); 
     app.CreatePerOwinContext<ADUUserManager>(ADUUserManager.Create); 

     app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); 

     app.UseCookieAuthentication(new CookieAuthenticationOptions 
            { 
             AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, 
             CookieName = "ADU", 
             ExpireTimeSpan = TimeSpan.FromHours(2), 
             LoginPath = new PathString("/Account/Login"), 
             SlidingExpiration = true 
            }); 
    } 
} 

đâu nó được sử dụng (AccountController):

private async Task CreateLoginCookie(AuthorizationToken response, User result) 
    { 
     //Create the claims needed to log a user in 
     //(uses UserManager several layers down in the stack) 
     ClaimsIdentity cookieIdent = await _clientSDK.CreateClaimsIdentityForUser(response.AccessToken, result, true).ConfigureAwait(false); 

     if (cookieIdent == null) throw new NullReferenceException("Failed to create claims for cookie."); 
     cookieIdent.AddClaim(new Claim("AuthToken", response.AccessToken)); 

     AuthenticationProperties authProperties = new AuthenticationProperties(); 
     authProperties.AllowRefresh = true; 
     authProperties.IsPersistent = true; 
     authProperties.IssuedUtc = DateTime.Now.ToUniversalTime(); 

     IOwinContext context = HttpContext.GetOwinContext(); 
     AuthenticateResult authContext = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); 

     if (authContext != null) 
      context.Authentication.AuthenticationResponseGrant = new AuthenticationResponseGrant(cookieIdent, authContext.Properties); 

     //Wrapper methods for IOwinContext.Authentication.SignOut()/SignIn() 
     SignOut(); 
     SignIn(authProperties, cookieIdent); 
    } 

Trong lớp SDK, tôi đã tạo một phương thức mà tôi sử dụng để truy cập API của mình để đặt Ủy quyền cho mỗi yêu cầu đi (Tôi muốn tìm ra cách để làm cho điều này trở thành một thuộc tính, nhưng tôi sẽ lo lắng về điều này sau):

private void SetAuthentication() 
    { 
     ClaimsIdentity ident = (ClaimsIdentity)Thread.CurrentPrincipal.Identity; 
     Claim claim; 
     //Both of these methods (Thread.CurrentPrincipal, and ClaimsPrincipal.Current should work, 
     //leaving both in for the sake of example. 
     try 
     { 
      claim = ident.Claims.First(x => x.Type == "AuthToken"); 
     } 
     catch (Exception) 
     { 
      claim = ClaimsPrincipal.Current.Claims.First(x => x.Type == "AuthToken"); 
     } 

     _apiClient.SetBearerAuthentication(claim.Value); 
    } 

API Startup.cs

/// <summary> 
    ///  Configures the settings used by the framework on application start. Dependency Resolver, OAuth, Routing, and CORS 
    ///  are configured. 
    /// </summary> 
    /// <param name="app"></param> 
    public void Configuration(IAppBuilder app) 
    { 
     HttpConfiguration config = new HttpConfiguration(); 

     config.DependencyResolver = new NinjectResolver(new Bootstrapper().Kernel); 

     WebApiConfig.Register(config); 

     ConfigureOAuth(app); 
     app.UseCors(CorsOptions.AllowAll); 

     app.UseWebApi(config); 
    } 

    /// <summary> 
    ///  Configures authentication options for OAuth. 
    /// </summary> 
    /// <param name="app"></param> 
    public void ConfigureOAuth(IAppBuilder app) 
    { 
     app.CreatePerOwinContext(ADUIdentityDbContext.Create); 
     app.CreatePerOwinContext<ADUUserManager>(ADUUserManager.Create); 

     OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions 
                  { 
                   AllowInsecureHttp = true, 
                   TokenEndpointPath = new PathString("/api/token"), 
                   AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), 
                   Provider = new SimpleAuthorizationServerProvider() 
                  }; 

     //token generation 
     app.UseOAuthAuthorizationServer(oAuthServerOptions); 
     app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); 
    } 

SimpleAuthorizationServerProvider cs:

/// <summary> 
    ///  Creates an access bearer token and applies custom login validation logic to prevent invalid login attempts. 
    /// </summary> 
    /// <param name="context"></param> 
    /// <returns></returns> 
    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) 
    { 
     context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); 

     // Performs any login logic required, such as accessing Active Directory and password validation. 
     User user = await CustomLoginLogic(context).ConfigureAwait(false); 

     //If a use was not found, add an error if one has not been added yet 
     if((user == null) && !context.HasError) SetInvalidGrantError(context); 

     //Break if any errors have been set. 
     if (context.HasError) return; 

     //create a claim for the user 
     ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType); 

     //Add some basic information to the claim that will be used for the token. 
     identity.AddClaim(new Claim("Id", user?.Id)); 
     identity.AddClaim(new Claim("TimeOf", DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString())); 

     //Roles auth 
     SetRoleClaim(user, ref identity); 

     context.Validated(identity); 
    } 

Và cuối cùng, chìa khóa rõ ràng rằng kết thúc tốt đẹp tất cả mọi thứ lên với nhau:

public class ResponseWrappingHandler : DelegatingHandler 
{ 
    /// <summary> 
    /// Catches the request before processing is completed and wraps the resulting response in a consistent response wrapper depending on the response returned by the api. 
    /// </summary> 
    /// <param name="request">The request that is being processed.</param> 
    /// <param name="cancellationToken">A cancellation token to cancel the processing of a request.</param> 
    /// <returns></returns> 
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
     HttpResponseMessage response = await base.SendAsync(request, cancellationToken); 

     //Calls Wrapping methods depending on conditions, 
     //All of the Wrapping methods will make a call to PreserveHeaders() 
    } 

    /// <summary> 
    /// Creates a response based on the provided request with the provided response's status code and request headers, and the provided response data. 
    /// </summary> 
    /// <param name="request">The original request.</param> 
    /// <param name="response">The reqsponse that was generated.</param> 
    /// <param name="responseData">The data to include in the wrapped response.</param> 
    /// <returns></returns> 
    private static HttpResponseMessage PreserveHeaders(HttpRequestMessage request, HttpResponseMessage response, object responseData) 
    { 
     HttpResponseMessage newResponse = request.CreateResponse(response.StatusCode, responseData); 

     foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers) 
      newResponse.Headers.Add(header.Key, header.Value); 

     return newResponse; 
    } 

Với tất cả những điều đó tại chỗ dự án của tôi bây giờ có thể sử dụng ủy quyền/xác thực mà không cần bí mật của khách hàng và như vậy (đó là một trong những mục tiêu của chủ nhân của tôi).

Các vấn đề liên quan