我之前问过question并且给出的答案是正确的,但是我越往下走到这个兔子洞,我越发现;我不认为我问的是正确的问题。
让我用最简单的术语解释一下我能...我有一个AngularJS单页面应用程序(客户端),它指向一个asp.net webapi(OWIN)站点(资源服务器?),还有一个单独的asp.net“authorization / authentiation”服务器。
auth服务器将为多个应用程序提供身份验证和授权。我需要能够在资源服务器中使用Authorize属性,以及从angular获取令牌。我还需要对所有内容使用Windows身份验证(集成),没有用户名或密码。声明信息存储在数据库中,需要添加到令牌中。
我使用带有JwtBearerToken的openiddict和'密码流?'在asp.net核心中完成了SSO样式声明授权实现。并且想尝试做类似的事情(令牌等)。我基本了解了我以前的工作方式是如何工作的,但是我完全不知道如何让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);
}
编辑:我看到auth端点请求“/.well-known/openid-configuration”以及“/.well-known/jwks”,并且我在正在调用的控制器操作上有Authorize属性,但我没有看到认证方面发生的任何其他事情。我还在usewindowsauthservice WindowsAuthenticationOptions中添加了一个ICustomClaimsProvider实现,但它甚至都没有被调用。
答案 0 :(得分:6)
我使用带有JwtBearerToken和密码流的openiddict在asp.net核心中完成了SSO样式声明授权实现?'
如果您使用OpenIddict和Windows身份验证,使用OAuth2 / OpenID Connect隐式流(这是JS应用程序最合适的流程)实现起来非常容易,而无需任何WS-Federation代理: / p>
启动配置:
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);
}
}
在使用ASOS的OWIN / Katana版本(OpenIddict背后的OpenID Connect服务器中间件)的旧版ASP.NET应用程序中可以实现类似的方案:
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应用程序不同。您可以查看此示例,了解如何使用oidc-client JS库实现它:https://github.com/openiddict/openiddict-samples/tree/master/samples/ImplicitFlow/AureliaApp
答案 1 :(得分:2)
所以最终这里的重点是通过数据库中的声明增加对现有ClaimsPrincipal的声明,并希望能够在javascript中使用JWT。我无法使用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身份验证)来获取自定义&#34;声明&#34;的用户列表。来自数据库。 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);
这适用于我的目的,Web api端点可在WebAPI实例和AngularJS应用程序中使用。然而,它显然不理想。我真的更愿意使用真正的&#39;身份验证/授权过程。我毫不犹豫地说这是问题的答案,但这是我能想出的最好的解决方案。