Przepraszam z góry za pytanie o to, ponieważ nie mam żadnej wiedzy na temat bezpieczeństwa w ogóle, a IdentityServer w szczególności.Konfigurowanie IdentityServer za pomocą aplikacji Asp.Net MVC
Próbuję skonfigurować IdentityServer do zarządzania bezpieczeństwem dla aplikacji ASP.Net MVC.
śledzę tutorial na swojej stronie internetowej: Asp.Net MVC with IdentityServer
Jestem jednak robi coś nieco innego, że mam oddzielny projekt dla tożsamości „Server” części, co prowadzi do 2 Startup.cs plików jeden dla aplikacji i jeden dla Identity Server
dla aplikacji, plik startup.cs wygląda następująco
public class Startup
{
public void Configuration(IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(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,
Constants.ClaimTypes.GivenName,
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);
}
}
});
app.UseResourceAuthorization(new AuthorizationManager());
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44301/identity",
RequiredScopes = new[] { "baseballStatsApi"}
});
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
}
}
dla serwera tożsamości, plik startup.cs jest
public class Startup
{
public void Configuration(IAppBuilder app)
{
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");
}
}
Ja również utworzenie Menedżer autoryzacji
public class AuthorizationManager : ResourceAuthorizationManager
{
public override Task<bool> CheckAccessAsync(ResourceAuthorizationContext context)
{
switch (context.Resource.First().Value)
{
case "Players":
return CheckAuthorization(context);
case "About":
return CheckAuthorization(context);
default:
return Nok();
}
}
private Task<bool> CheckAuthorization(ResourceAuthorizationContext context)
{
switch(context.Action.First().Value)
{
case "Read":
return Eval(context.Principal.HasClaim("role", "LevelOneSubscriber"));
default:
return Nok();
}
}
}
Tak na przykład, jeśli I zdefiniować metodę kontrolera, który jest ozdobiony atrybutu ResourceAuthorize, tak jak
public class HomeController : Controller
{
[ResourceAuthorize("Read", "About")]
public ActionResult About()
{
return View((User as ClaimsPrincipal).Claims);
}
}
Następnie kiedy po raz pierwszy spróbuję uzyskać dostęp do tej metody, nastąpi przekierowanie do domyślnej strony logowania.
Co Nie rozumiem jednak, dlaczego, kiedy zalogować się z użytkownikiem mam zdefiniowanej dla danej aplikacji (patrz poniżej),
public class Users
{
public static List<InMemoryUser> Get()
{
return new List<InMemoryUser>
{
new InMemoryUser
{
Username = "bob",
Password = "secret",
Subject = "1",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Bob"),
new Claim(Constants.ClaimTypes.FamilyName, "Smith"),
new Claim(Constants.ClaimTypes.Role, "Geek"),
new Claim(Constants.ClaimTypes.Role, "LevelOneSubscriber")
}
}
};
}
}
pojawia się błąd 403, błąd okaziciela = "insufficient_scope ".
Czy ktoś może wyjaśnić, co robię źle?
Każda kolejna próba uzyskania dostępu do metody akcji spowoduje zwrócenie tego samego błędu. Wydaje mi się, że zdefiniowany przeze mnie użytkownik ma odpowiednie roszczenia do uzyskania dostępu do tej metody. Jednak kontrola roszczeń następuje tylko raz, kiedy po raz pierwszy spróbuję uzyskać dostęp do tej metody. Po zalogowaniu otrzymuję plik cookie, a kontrola roszczeń nie jest podejmowana podczas kolejnych prób uzyskania dostępu do metody.
Jestem trochę zagubiony i byłbym wdzięczny za pomoc w oczyszczeniu tego.
Z góry dziękuję.
EDIT: oto scoles i klasy klienckie
public static class Scopes
{
public static IEnumerable<Scope> Get()
{
var scopes = new List<Scope>
{
new Scope
{
Enabled = true,
Name = "roles",
Type = ScopeType.Identity,
Claims = new List<ScopeClaim>
{
new ScopeClaim("role")
}
},
new Scope
{
Enabled = true,
Name = "baseballStatsApi",
Description = "Access to baseball stats API",
Type = ScopeType.Resource,
Claims = new List<ScopeClaim>
{
new ScopeClaim("role")
}
}
};
scopes.AddRange(StandardScopes.All);
return scopes;
}
}
i klasy Client
public static class Clients
{
public static IEnumerable<Client> Get()
{
return new[]
{
new Client
{
Enabled = true,
ClientName = "Baseball Stats Emporium",
ClientId = "baseballStats",
Flow = Flows.Implicit,
RedirectUris = new List<string>
{
"https://localhost:44300/"
}
},
new Client
{
Enabled = true,
ClientName = "Baseball Stats API Client",
ClientId = "baseballStats_Api",
ClientSecrets = new List<ClientSecret>
{
new ClientSecret("secret".Sha256())
},
Flow = Flows.ClientCredentials
}
};
}
}
Mam również utworzony atrybut niestandardowy filtr, który używam do określenia, kiedy sprawdza się roszczenia .
public class CustomFilterAttribute : ResourceAuthorizeAttribute
{
public CustomFilterAttribute(string action, params string[] resources) : base(action, resources)
{
}
protected override bool CheckAccess(HttpContextBase httpContext, string action, params string[] resources)
{
return base.CheckAccess(httpContext, action, resources);
}
}
Punkt przerwania trafia tylko na początkowe żądanie do adresu URL.W przypadku kolejnych żądań punkt zatrzymania atrybutu filtru nie jest trafiony, a zatem nie jest przeprowadzana kontrola. Jest to dla mnie zaskakujące, ponieważ zakładałem, że czek musi być dokonywany za każdym razem, gdy żądany jest adres URL.
można dodać scopes.cs i clients.cs na pytanie? Błąd insufficiet_scope oznacza "Żądanie wymaga wyższych uprawnień niż to, które zapewnia token dostępu". – rawel
Witam, dodałem klasy, o które prosiłeś. Może typ zakresu jest błędny? – Locust5304