6

Angular の $http サービスから API コントローラー メソッドを呼び出すときに、Identity Server のデフォルトのログイン ページにリダイレクトしようとしています。

私の Web プロジェクトと Identity Server は別のプロジェクトにあり、別の Startup.cs ファイルを持っています。

Web プロジェクト Statup.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);
         });                                                 
     }
}

アプリの残りの部分は OpenIdConnect を使用しているのに対し、API はベアラー トークン認証で保護されていることがわかります。

Identity Server の Startup.cs クラスは

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var policy = new System.Web.Cors.CorsPolicy
        {
            AllowAnyOrigin = true,
            AllowAnyHeader = true,
            AllowAnyMethod = true,
            SupportsCredentials = true
        };

        policy.ExposedHeaders.Add("Location");
        app.UseCors(new CorsOptions
        {
            PolicyProvider = new CorsPolicyProvider
            {
                PolicyResolver = context => Task.FromResult(policy)
            }
        });
        app.Map("/identity", idsrvApp =>
        {
            idsrvApp.UseIdentityServer(new IdentityServerOptions
            {
                SiteName = "Embedded IdentityServer",
                SigningCertificate = LoadCertificate(),

                Factory = InMemoryFactory.Create(
                    users: Users.Get(),
                    clients: Clients.Get(),
                    scopes: Scopes.Get())
            });
        });            
    }

    X509Certificate2 LoadCertificate()
    {
        return new X509Certificate2(
            string.Format(@"{0}\bin\Configuration\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
    }
}

Web アプリがログイン ページにリダイレクトできるようにするために、CorsPolicy エントリを追加したことに注意してください。さらに、リダイレクト先の URL が含まれているので、Cors ポリシーは Location リクエスト ヘッダーを公開します。

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;
    }

以下に示すように、Angular ファクトリは $http を呼び出します。

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;
    }
}});

この呼び出しが発生すると、応答ステータスは 200 になり、データではログイン ページの html が返されます。

さらに、Chrome の [ネットワーク] タブで、応答にログイン ページの URL を含む Location ヘッダーがあることを確認できます。ただし、http インターセプターを設定すると、Accept ヘッダーが JavaScript に渡されていることがわかります。

Chrome のネットワーク タブに表示される http ヘッダーは次のとおりです。

HTTP ヘッダー

何らかの理由で、応答に Access-Control-Allow-Origin ヘッダーがありません。

だから私は次の質問があります:

angularクライアントコードで応答のLocationヘッダーにアクセスしてリダイレクトする方法はありますか?

認証エラーがあったことを知るために、サーバーに 200 ではなく 401 を送信させるにはどうすればよいですか?

これを行うためのより良い方法はありますか?もしそうなら、どのように?

ご協力いただきありがとうございます!

編集:

カスタムの AuthorizeAttribute を追加して、フィルターから返される 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;
    }

このことから 2 つのことがわかりました。1 つ目は、X-Requested-With ヘッダーが、クライアント側の $http サービスによって生成されたリクエストに含まれていないことです。さらに、base メソッドによって返される最終的な http ステータスは 401 - Unauthorized です。これは、チェーンのどこかでステータス コードが変更されたことを意味します。

すべての質問に答える必要があるとは思わないでください。どんな助けでも大歓迎です!

4

1 に答える 1