从AngularJs http web api请求重定向到Identity Server登录页面

从AngularJs http web api请求重定向到Identity Server登录页面,angularjs,asp.net-mvc-4,asp.net-web-api,owin,identityserver3,Angularjs,Asp.net Mvc 4,Asp.net Web Api,Owin,Identityserver3,当从Angular的$http服务调用API控制器方法时,我试图重定向到Identity Server的默认登录页面 我的web项目和Identity Server位于不同的项目中,并且具有不同的Startup.cs文件 web项目statupc.cs如下所示 public class Startup { public void Configuration(IAppBuilder app) { AntiForgeryConfig.UniqueClaimTy

当从Angular的$http服务调用API控制器方法时,我试图重定向到Identity Server的默认登录页面

我的web项目和Identity Server位于不同的项目中,并且具有不同的Startup.cs文件

web项目statupc.cs如下所示

 public class Startup
{
     public void Configuration(IAppBuilder app)
     {
         AntiForgeryConfig.UniqueClaimTypeIdentifier = Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Subject;
         JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

         app.UseCookieAuthentication(new CookieAuthenticationOptions
         {
             AuthenticationType = "Cookies",                
         });

         var openIdConfig = new OpenIdConnectAuthenticationOptions
         {
             Authority = "https://localhost:44301/identity",
             ClientId = "baseballStats",
             Scope = "openid profile roles baseballStatsApi",
             RedirectUri = "https://localhost:44300/",
             ResponseType = "id_token token",
             SignInAsAuthenticationType = "Cookies",                 
             UseTokenLifetime = false,
             Notifications = new OpenIdConnectAuthenticationNotifications
             {
                 SecurityTokenValidated = async n =>
                 {
                     var userInfoClient = new UserInfoClient(
                                  new Uri(n.Options.Authority + "/connect/userinfo"),
                                  n.ProtocolMessage.AccessToken);

                     var userInfo = await userInfoClient.GetAsync();

                     // create new identity and set name and role claim type
                     var nid = new ClaimsIdentity(
                        n.AuthenticationTicket.Identity.AuthenticationType,
                         Thinktecture.IdentityServer.Core.Constants.ClaimTypes.GivenName,
                         Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Role);

                     userInfo.Claims.ToList().ForEach(c => nid.AddClaim(new Claim(c.Item1, c.Item2)));

                     // keep the id_token for logout
                     nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));

                     // add access token for sample API
                     nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));

                     // keep track of access token expiration
                     nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));

                     // add some other app specific claim
                     nid.AddClaim(new Claim("app_specific", "some data"));

                     n.AuthenticationTicket = new AuthenticationTicket(
                         nid,
                         n.AuthenticationTicket.Properties);

                     n.Request.Headers.SetValues("Authorization ", new string[] { "Bearer ", n.ProtocolMessage.AccessToken });
                 }
             }
         };

         app.UseOpenIdConnectAuthentication(openIdConfig);

         app.UseResourceAuthorization(new AuthorizationManager());

         app.Map("/api", inner =>
         {
             var bearerTokenOptions = new IdentityServerBearerTokenAuthenticationOptions
             {
                 Authority = "https://localhost:44301/identity",
                 RequiredScopes = new[] { "baseballStatsApi" }                     
             };

             inner.UseIdentityServerBearerTokenAuthentication(bearerTokenOptions);
             var config = new HttpConfiguration();
             config.MapHttpAttributeRoutes();
             inner.UseWebApi(config);
         });                                                 
     }
}
请注意,我添加了一个CorsPolicy条目,以便允许Web应用程序重定向到登录页面。此外,Cors策略公开了位置请求头,因为它包含我想重定向到的url

Web Api控制器方法使用Authorize属性进行保护,如下所示

  [HttpPost]
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [Authorize]
    public PlayerData GetFilteredPlayers(PlayerInformationParameters parameters)
    {
        var playerInformation = composer.Compose<PlayerInformation>().UsingParameters(parameters);

        var players = playerInformation.Players
            .Select(p => new {                    
            p.NameLast,
            p.NameFirst,
            p.Nickname,
            p.BirthCity,
            p.BirthState,
            p.BirthCountry,
            p.BirthDay,
            p.BirthMonth,
            p.BirthYear,
            p.Weight,
            p.Height,
            p.College,
            p.Bats,
            p.Throws,
            p.Debut,
            p.FinalGame
        });

        var playerData = new PlayerData { Players = players, Count = playerInformation.Count, Headers = GetHeaders(players) };            

        return playerData;
    }
发生此调用时,响应状态为200,并且在数据中返回登录页面的html

此外,我可以在Chrome的网络选项卡上看到响应有一个带有登录页面url的位置标题。但是,如果我设置了一个http拦截器,我只看到Accept头已经传递给javascript

以下是Chrome的网络选项卡中显示的http标题:

由于某些原因,响应没有访问控制允许源标题

所以我有以下问题:

是否有一种方法可以访问angular客户端代码中响应的Location标头以重定向到它

我如何才能让服务器向我发送401而不是200,以便知道存在身份验证错误

有没有更好的方法来做到这一点,如果有,如何做到

谢谢你的帮助

编辑:

我添加了一个自定义的authorized属性来确定从过滤器返回的http状态代码

自定义筛选代码

 public class BearerTokenAutorizeAttribute : AuthorizeAttribute
{
    private const string AjaxHeaderKey = "X-Requested-With";
    private const string AjaxHeaderValue = "XMLHttpRequest";
    protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var headers = actionContext.Request.Headers;
        if(IsAjaxRequest(headers))
        {
            if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
                actionContext.Response.StatusCode = System.Net.HttpStatusCode.Forbidden;
            else
                actionContext.Response.StatusCode = System.Net.HttpStatusCode.Unauthorized;
        }

        base.HandleUnauthorizedRequest(actionContext);
        var finalStatus = actionContext.Response.StatusCode;
    }

    private bool IsAjaxRequest(HttpRequestHeaders requestHeaders)
    {
        return requestHeaders.Contains(AjaxHeaderKey) && requestHeaders.GetValues(AjaxHeaderKey).FirstOrDefault() == AjaxHeaderValue;
    }
我从中观察到两件事:首先,客户端上的$http服务生成的请求中不包括X-Requested-With头。此外,基本方法返回的最终http状态是401-Unauthorized。这意味着状态代码在链的某个位置发生了更改


请不要觉得你必须回答所有的问题。任何帮助都将不胜感激

您可能已经正确配置了服务器,因为 登录页面html作为对$http调用的响应->它是 应该这样做:

注意,如果响应是重定向,XMLHttpRequest将透明地跟随它,这意味着结果(成功或错误)将由最终响应状态代码决定

您将得到一个200 OK的响应,因为这是最终响应,因为重定向立即被执行,并且它的结果被解析为$http服务结果,响应头也是最终响应的结果


实现所需结果的一种方法-浏览器重定向到登录页面: web api控制器
api/pitchingstats/GetFilteredPlayer
可以返回一个错误响应(401),其中包含一个json负载,该负载包含一个
{redirectUrl:'login page'}
字段或一个可以读取为
response.headers的头,而不是将请求服务器端(从web项目重定向到Identity server)('x-redirect-url')
然后使用
window.location.href=url

类似的逻辑通常在$httpInterceptors中配置,它处理未经授权的访问响应并将它们重定向到登录页面-重定向在客户端进行管理

baseballApp.factory('playerService', function ($http, $q) {
return {
    getPlayerList: function (queryParameters) {
        var deferred = $q.defer();
        $http.post('api/pitchingstats/GetFilteredPlayers', {
            skip: queryParameters.skip,
            take: queryParameters.take,
            orderby: queryParameters.orderby,
            sortdirection: queryParameters.sortdirection,
            filter: queryParameters.filter
        }).success(function (data, status) {
            deferred.resolve(data);
        }).error(function (data, status) {
            deferred.reject(status);
        });

        return deferred.promise;
    }
}});
 public class BearerTokenAutorizeAttribute : AuthorizeAttribute
{
    private const string AjaxHeaderKey = "X-Requested-With";
    private const string AjaxHeaderValue = "XMLHttpRequest";
    protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var headers = actionContext.Request.Headers;
        if(IsAjaxRequest(headers))
        {
            if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
                actionContext.Response.StatusCode = System.Net.HttpStatusCode.Forbidden;
            else
                actionContext.Response.StatusCode = System.Net.HttpStatusCode.Unauthorized;
        }

        base.HandleUnauthorizedRequest(actionContext);
        var finalStatus = actionContext.Response.StatusCode;
    }

    private bool IsAjaxRequest(HttpRequestHeaders requestHeaders)
    {
        return requestHeaders.Contains(AjaxHeaderKey) && requestHeaders.GetValues(AjaxHeaderKey).FirstOrDefault() == AjaxHeaderValue;
    }