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 ヘッダーは次のとおりです。
何らかの理由で、応答に 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 です。これは、チェーンのどこかでステータス コードが変更されたことを意味します。
すべての質問に答える必要があるとは思わないでください。どんな助けでも大歓迎です!