2016-12-22 14 views
11

Я уже спрашивал question, и ответ, который был дан, был прав, но чем дальше я спускаюсь с этой кроличьей дыры, тем больше я понимаю; Я не думаю, что задал правильный вопрос.AngularJs, WebAPI, JWT, с (интегрированной) Аутентификация Windows

Позвольте мне объяснить это самым простым термином, я могу ... У меня есть одностраничное приложение (клиент) AngularJS, которое указывает на сайт asp.net webapi (OWIN) (сервер ресурсов?) И отдельный сервер asp.net «авторизация/аутентификация».

Сервер auth обеспечит аутентификацию и авторизацию для нескольких приложений. Мне нужно иметь возможность использовать атрибут Authorize на сервере ресурсов, а также получить токен из углового. Мне также нужно использовать проверку подлинности Windows (интегрированную) для всего, без имен пользователей или паролей. Информация о претензиях хранится в базе данных и должна быть добавлена ​​в токен.

Я выполнил реализацию авторских прав в стиле SSO в ядре asp.net, используя openiddict с JwtBearerToken и «поток паролей»? И хотел попытаться сделать что-то подобное (токен и т. Д.). У меня есть базовое понимание того, как это работает с моего предыдущего внедрения, но я полностью потерялся, пытаясь понять, как заставить JWT работать с Windows Auth. Ответ на мой предыдущий вопрос дал несколько хороших предложений, но мне с трудом удается понять, как это применимо в этом сценарии.

В настоящее время я пытаюсь получить IdentityServer3 для этого, используя расширения WindowsAuthentication, в основном извлекаемые из образцов. Но я действительно пытаюсь связать это вместе с клиентом и фактически получить что-то работающее. Текущий код клиента и сервера ниже, помните, что я действительно не знаю, насколько это близко к правильному решению.

Клиент:

app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions 
     { 
      AuthenticationMode = AuthenticationMode.Passive, 
      AuthenticationType = "windows", 
      Authority = "http://localhost:21989", 
      ClientId = "mvc.owin.implicit", 
      ClientSecret = "api-secret", 
      RequiredScopes = new[] { "api" } 
     }); 

AuthServer:

app.Map("/windows", ConfigureWindowsTokenProvider); 
app.Use(async (context, next) => 
{ 
    if (context.Request.Uri.AbsolutePath.EndsWith("/token", StringComparison.OrdinalIgnoreCase)) 
      { 
       if (context.Authentication.User == null || 
        !context.Authentication.User.Identity.IsAuthenticated) 
       { 
        context.Response.StatusCode = 401; 
        return; 
       } 
      } 

      await next(); 
     }); 
     var factory = new IdentityServerServiceFactory() 
      .UseInMemoryClients(Clients.Get()) 
      .UseInMemoryScopes(Scopes.Get()); 

     var options = new IdentityServerOptions 
     { 
      SigningCertificate = Certificate.Load(), 
      Factory = factory, 
      AuthenticationOptions = new AuthenticationOptions 
      { 
       EnableLocalLogin = false, 
       IdentityProviders = ConfigureIdentityProviders 
      }, 
      RequireSsl = false 
     }; 

     app.UseIdentityServer(options); 


private static void ConfigureWindowsTokenProvider(IAppBuilder app) 
    { 
     app.UseWindowsAuthenticationService(new WindowsAuthenticationOptions 
     { 
      IdpReplyUrl = "http://localhost:21989", 
      SigningCertificate = Certificate.Load(), 
      EnableOAuth2Endpoint = false 
     }); 
    } 

    private void ConfigureIdentityProviders(IAppBuilder app, string signInAsType) 
    { 
     var wsFederation = new WsFederationAuthenticationOptions 
     { 
      AuthenticationType = "windows", 
      Caption = "Windows", 
      SignInAsAuthenticationType = signInAsType, 
      MetadataAddress = "http://localhost:21989", 
      Wtrealm = "urn:idsrv3" 
     }; 
     app.UseWsFederationAuthentication(wsFederation); 
    } 

EDIT: Я вижу аутентификации конечных точек запросов на "/.well-known/openid-configuration", а также «/.well- известный/jwks ", и у меня есть атрибут Authorize при вызове контроллера, но я не вижу ничего другого, что происходит на стороне auth. Я также добавил внедрение ICustomClaimsProvider в usewindowsauthservice WindowsAuthenticationOptions, но это даже не вызвано.

+0

Если вы используете интегрированную проверку подлинности Windows, зачем вам нужен токен? С помощью windows auth ваш контроллер домена является вашим «поставщиком удостоверений». На вашем «ресурсном сервере» у вас есть доступ к идентификатору текущего пользователя и вы можете использовать атрибуты авторизации для ограничения доступа и т. Д. Может быть, я что-то упустил, но вы, кажется, добавляете много инфраструктуры безопасности, которая берется из окна auth. –

+0

Потому что мне нужно увеличить требования к токену, предоставленному пользовательскими требованиями из базы данных. В принципе, мне нужно сопоставить некоторые члены группы AD и добавить некоторые претензии из db, используя группы AD. Существует конечная точка WebAPI, для которой необходимо выполнить аутсорсинг некоторых действий, но мне также необходимо использовать маркеры и заявки на клиентской (угловой) стороне приложения. У меня есть некоторый js-код, который декодирует токены JWT для получения претензий, поэтому я могу интерактивно предоставить некоторую авторизацию на уровне поверхности (угловой ui-router), если это имеет смысл? – Brandon

+0

У меня также есть пользовательский атрибут AuthorizeAttribute, который берет строку с именем имени, которое разрешено для доступа к этой конечной точке. Принцип cliams, кажется, прав. – Brandon

ответ

1

Так что в конечном итоге все дело в том, чтобы увеличить требования к существующей ClaimsPrincipal с претензиями из базы данных и, надеюсь, будет способный использовать JWT в javascript. Мне не удалось заставить это работать с помощью IdentityServer3. Я закончил свой собственный рудиментарный вариант, выполнив IAuthenticationFilter и IAuthorizationFilter, используя атрибут действий для предоставления имени заявки.

Сначала атрибут authorize ничего не делает, кроме как принять имя требования о том, что пользователь должен получить доступ к действию.

public class AuthorizeClaimAttribute : Attribute 
{ 
    public string ClaimValue; 
    public AuthorizeClaimAttribute(string value) 
    { 
     ClaimValue = value; 
    } 
} 

Затем фильтр Авторизация, который ничего не делает, кроме проверки, чтобы узнать, имеет ли пользователь требование от атрибута.

public class AuthorizeClaimFilter : AuthorizeAttribute, IAuthorizationFilter 
{ 
    private readonly string _claimValue; 

    public AuthorizeClaimFilter(string claimValue) 
    { 
     _claimValue = claimValue; 
    } 

    public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken) 
    {    
     var p = actionContext.RequestContext.Principal as ClaimsPrincipal; 

     if(!p.HasClaim("process", _claimValue)) 
      HandleUnauthorizedRequest(actionContext); 

     await Task.FromResult(0); 
    } 

    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) 
    { 
     actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden); 
    } 

} 

В аутентификации фильтр, который вызывает WebAPI конечную точку (которая использует проверку подлинности Windows), чтобы получить список пользователей пользовательских «претензий» из базы данных. WebAPI - это просто стандартный экземпляр webapi, ничего особенного.

public class ClaimAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter 
{ 
    public ClaimAuthenticationFilter() 
    { 
    } 

    public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken) 
    { 

     if (context.Principal != null && context.Principal.Identity.IsAuthenticated) 
     { 
      var windowsPrincipal = context.Principal as WindowsPrincipal; 
      var handler = new HttpClientHandler() 
      { 
       UseDefaultCredentials = true 
      }; 

      HttpClient client = new HttpClient(handler); 
      client.BaseAddress = new Uri("http://localhost:21989");// to be stored in config 

      var response = await client.GetAsync("/Security"); 
      var contents = await response.Content.ReadAsStringAsync(); 
      var claimsmodel = JsonConvert.DeserializeObject<List<ClaimsModel>>(contents); 

      if (windowsPrincipal != null) 
      { 
       var name = windowsPrincipal.Identity.Name; 
       var identity = new ClaimsIdentity(); 


       foreach (var claim in claimsmodel) 
       { 
        identity.AddClaim(new Claim("process", claim.ClaimName)); 
       } 

       var claimsPrincipal = new ClaimsPrincipal(identity); 
       context.Principal = claimsPrincipal; 
      } 
     } 
     await Task.FromResult(0); 
    } 

    public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken) 
    { 
     var challenge = new AuthenticationHeaderValue("Negotiate"); 
     context.Result = new ResultWithChallenge(challenge, context.Result); 
     await Task.FromResult(0); 
    } 
} 

Фильтры привязаны к атрибуту, используя мою инфраструктуру DI (в этом случае ninject).

this.BindHttpFilter<AuthorizeClaimFilter>(FilterScope.Action) 
      .WhenActionMethodHas<AuthorizeClaimAttribute>() 
     .WithConstructorArgumentFromActionAttribute<AuthorizeClaimAttribute>("claimValue", o => o.ClaimValue); 

Это работает для моих целей, и веб-апите конечную точку расходного как в случае WebAPI и в приложении AngularJS. Однако он, очевидно, НЕ идеален. Я бы предпочел использовать «реальные» процессы аутентификации/авторизации. Я стесняюсь сказать, что это ответ на вопрос, но это единственное решение, которое я мог придумать, когда мне пришлось что-то сделать.

6

Я выполнил реализацию авторизации в стиле SSO в ядре asp.net, используя openiddict с JwtBearerToken и «поток паролей»?

Если вы должны были использовать OpenIddict с проверкой подлинности Windows, было бы довольно легко реализовать с помощью неявного потока OAuth2/OpenID Connect (который является наиболее подходящим поток для приложения JS), без необходимости каких-либо WS-Federation прокси: конфигурация

запуска: контроллер

public void ConfigureServices(IServiceCollection services) 
{ 
    // Register the OpenIddict services. 
    services.AddOpenIddict(options => 
    { 
     // Register the Entity Framework stores. 
     options.AddEntityFrameworkCoreStores<ApplicationDbContext>(); 

     // Register the ASP.NET Core MVC binder used by OpenIddict. 
     // Note: if you don't call this method, you won't be able to 
     // bind OpenIdConnectRequest or OpenIdConnectResponse parameters. 
     options.AddMvcBinders(); 

     // Enable the authorization endpoint. 
     options.EnableAuthorizationEndpoint("/connect/authorize"); 

     // Enable the implicit flow. 
     options.AllowImplicitFlow(); 

     // During development, you can disable the HTTPS requirement. 
     options.DisableHttpsRequirement(); 

     // Register a new ephemeral key, that is discarded when the application 
     // shuts down. Tokens signed using this key are automatically invalidated. 
     // This method should only be used during development. 
     options.AddEphemeralSigningKey(); 
    }); 

    // Note: when using WebListener instead of IIS/Kestrel, the following lines must be uncommented: 
    // 
    // services.Configure<WebListenerOptions>(options => 
    // { 
    //  options.ListenerSettings.Authentication.AllowAnonymous = true; 
    //  options.ListenerSettings.Authentication.Schemes = AuthenticationSchemes.Negotiate; 
    // }); 
} 

Авторизация:

public class AuthorizationController : Controller 
{ 
    // Warning: extreme caution must be taken to ensure the authorization endpoint is not included in a CORS policy 
    // that would allow an attacker to force a victim to silently authenticate with his Windows credentials 
    // and retrieve an access token using a cross-domain AJAX call. Avoiding CORS is strongly recommended. 

    [HttpGet("~/connect/authorize")] 
    public async Task<IActionResult> Authorize(OpenIdConnectRequest request) 
    { 
     // Retrieve the Windows principal: if a null value is returned, apply an HTTP challenge 
     // to allow IIS/WebListener to initiate the unmanaged integrated authentication dance. 
     var principal = await HttpContext.Authentication.AuthenticateAsync(IISDefaults.Negotiate); 
     if (principal == null) 
     { 
      return Challenge(IISDefaults.Negotiate); 
     } 

     // Note: while the principal is always a WindowsPrincipal object when using Kestrel behind IIS, 
     // a WindowsPrincipal instance must be manually created from the WindowsIdentity with WebListener. 
     var ticket = CreateTicket(request, principal as WindowsPrincipal ?? new WindowsPrincipal((WindowsIdentity) principal.Identity)); 

     // Immediately return an authorization response without displaying a consent screen. 
     return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); 
    } 

    private AuthenticationTicket CreateTicket(OpenIdConnectRequest request, WindowsPrincipal principal) 
    { 
     // Create a new ClaimsIdentity containing the claims that 
     // will be used to create an id_token, a token or a code. 
     var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); 

     // Note: the JWT/OIDC "sub" claim is required by OpenIddict 
     // but is not automatically added to the Windows principal, so 
     // the primary security identifier is used as a fallback value. 
     identity.AddClaim(OpenIdConnectConstants.Claims.Subject, principal.GetClaim(ClaimTypes.PrimarySid)); 

     // Note: by default, claims are NOT automatically included in the access and identity tokens. 
     // To allow OpenIddict to serialize them, you must attach them a destination, that specifies 
     // whether they should be included in access tokens, in identity tokens or in both. 

     foreach (var claim in principal.Claims) 
     { 
      // In this sample, every claim is serialized in both the access and the identity tokens. 
      // In a real world application, you'd probably want to exclude confidential claims 
      // or apply a claims policy based on the scopes requested by the client application. 
      claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, 
            OpenIdConnectConstants.Destinations.IdentityToken); 

      // Copy the claim from the Windows principal to the new identity. 
      identity.AddClaim(claim); 
     } 

     // Create a new authentication ticket holding the user identity. 
     return new AuthenticationTicket(
      new ClaimsPrincipal(identity), 
      new AuthenticationProperties(), 
      OpenIdConnectServerDefaults.AuthenticationScheme); 
    } 
} 

Подобный сценарий может быть реализован в унаследованной ASP.NET приложений с использованием версии Owin/Катана из ASOS, сервер промежуточного слоя OpenID Connect за OpenIddict:

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
     app.UseOpenIdConnectServer(options => 
     { 
      // Register a new ephemeral key, that is discarded when the application 
      // shuts down. Tokens signed using this key are automatically invalidated. 
      // This method should only be used during development. 
      options.SigningCredentials.AddEphemeralKey(); 

      // Enable the authorization endpoint. 
      options.AuthorizationEndpointPath = new PathString("/connect/authorize"); 

      // During development, you can disable the HTTPS requirement. 
      options.AllowInsecureHttp = true; 

      // Implement the ValidateAuthorizationRequest event to validate the response_type, 
      // the client_id and the redirect_uri provided by the client application. 
      options.Provider.OnValidateAuthorizationRequest = context => 
      { 
       if (!context.Request.IsImplicitFlow()) 
       { 
        context.Reject(
         error: OpenIdConnectConstants.Errors.UnsupportedResponseType, 
         description: "The provided response_type is invalid."); 

        return Task.FromResult(0); 
       } 

       if (!string.Equals(context.ClientId, "spa-application", StringComparison.Ordinal)) 
       { 
        context.Reject(
         error: OpenIdConnectConstants.Errors.InvalidClient, 
         description: "The provided client_id is invalid."); 

        return Task.FromResult(0); 
       } 

       if (!string.Equals(context.RedirectUri, "http://spa-app.com/redirect_uri", StringComparison.Ordinal)) 
       { 
        context.Reject(
         error: OpenIdConnectConstants.Errors.InvalidClient, 
         description: "The provided redirect_uri is invalid."); 

        return Task.FromResult(0); 
       } 

       context.Validate(); 

       return Task.FromResult(0); 
      }; 

      // Implement the HandleAuthorizationRequest event to return an implicit authorization response. 
      options.Provider.OnHandleAuthorizationRequest = context => 
      { 
       // Retrieve the Windows principal: if a null value is returned, apply an HTTP challenge 
       // to allow IIS/SystemWeb to initiate the unmanaged integrated authentication dance. 
       var principal = context.OwinContext.Authentication.User as WindowsPrincipal; 
       if (principal == null) 
       { 
        context.OwinContext.Authentication.Challenge(); 
        return Task.FromResult(0); 
       } 

       // Create a new ClaimsIdentity containing the claims that 
       // will be used to create an id_token, a token or a code. 
       var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationType); 

       // Note: the JWT/OIDC "sub" claim is required by OpenIddict 
       // but is not automatically added to the Windows principal, so 
       // the primary security identifier is used as a fallback value. 
       identity.AddClaim(OpenIdConnectConstants.Claims.Subject, principal.GetClaim(ClaimTypes.PrimarySid)); 

       // Note: by default, claims are NOT automatically included in the access and identity tokens. 
       // To allow OpenIddict to serialize them, you must attach them a destination, that specifies 
       // whether they should be included in access tokens, in identity tokens or in both. 

       foreach (var claim in principal.Claims) 
       { 
        // In this sample, every claim is serialized in both the access and the identity tokens. 
        // In a real world application, you'd probably want to exclude confidential claims 
        // or apply a claims policy based on the scopes requested by the client application. 
        claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, 
              OpenIdConnectConstants.Destinations.IdentityToken); 

        // Copy the claim from the Windows principal to the new identity. 
        identity.AddClaim(claim); 
       } 

       context.Validate(identity); 

       return Task.FromResult(0); 
      }; 
     }); 
    } 
} 

Клиентский код не должен отличаться от любого другого приложения JS с помощью неявного потока. Вы можете взглянуть на этот образец, чтобы узнать, как его можно реализовать с помощью библиотеки JS oidc-client: https://github.com/openiddict/openiddict-samples/tree/master/samples/ImplicitFlow/AureliaApp

+0

Я действительно ценю усилия, которые вы вложили в этот ответ, но OpenIddict, к сожалению, только для .NET Core, которого у меня сейчас нет. :( – Brandon

+1

@Brandon woops, извините, я читал, что ваш сервер авторизации был отдельным приложением, и я ошибочно пришел к выводу, что вы можете использовать для него ASP.NET Core. Хотя я бы определенно рекомендовал использовать ASP.NET Core, поскольку он предлагает * * намного лучше ** рассказ для проверки подлинности Windows, вы можете реализовать что-то подобное с версией ASOS OWIN/Katana, промежуточным ПО сервера OpenID Connect позади OpenIddict: https://www.nuget.org/packages/Owin.Security.OpenIdConnect. Server. Я обновил свой ответ, чтобы включить крошечный образец того, как это сделать. – Pinpoint

+0

Я бы хотел, чтобы я мог. Я использовал ASP.NET Core для нескольких других проектов, и мне это нравится. Вне моего контроля в этом случае, к сожалению. – Brandon

Смежные вопросы