امن سازی برنامههای ASP.NET Core توسط IdentityServer 4x - قسمت ششم - کار با User Claims
نویسنده: وحید نصیری
تاریخ: ۱۳۹۷/۰۶/۱۶ ۱۲:۲۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
Claim type: sid - Claim value: f3940d6e58cbb576669ee49c90e22cb1 Claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7 Claim type: http://schemas.microsoft.com/identity/claims/identityprovider - Claim value: local Claim type: http://schemas.microsoft.com/claims/authnmethodsreferences - Claim value: pwd Claim type: given_name - Claim value: Vahid Claim type: family_name - Claim value: N
namespace ImageGallery.MvcClient.WebApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
} • Claim type: sid - Claim value: 91f5a09da5cdbbe18762526da1b996fb • Claim type: sub - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7 • Claim type: idp - Claim value: local • Claim type: given_name - Claim value: Vahid • Claim type: family_name - Claim value: N
namespace ImageGallery.MvcClient.WebApp
{
public void ConfigureServices(IServiceCollection services)
{
// ...
.AddOpenIdConnect("oidc", options =>
{
// ...
options.ClaimActions.Remove("amr");
options.ClaimActions.DeleteClaim("sid");
options.ClaimActions.DeleteClaim("idp");
});
} • Claim type: sub - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7 • Claim type: amr - Claim value: pwd • Claim type: given_name - Claim value: Vahid • Claim type: family_name - Claim value: N
namespace DNT.IDP
{
public static class Config
{
// identity-related resources (scopes)
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Address()
};
} AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address
}, namespace DNT.IDP
{
public static class Config
{
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
// ...
Claims = new List<Claim>
{
// ...
new Claim("address", "Main Road 1")
}
},
new TestUser
{
// ...
Claims = new List<Claim>
{
// ...
new Claim("address", "Big Street 2")
}
}
};
} namespace ImageGallery.MvcClient.WebApp
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
.AddOpenIdConnect("oidc", options =>
{
// ...
options.Scope.Add("address");
// …
options.ClaimActions.DeleteClaim("address");
});
} GET idphostaddress/connect/userinfo Authorization: Bearer R9aty5OPlk
dotnet add package IdentityModel
namespace ImageGallery.MvcClient.WebApp.Controllers
{
[Authorize]
public class GalleryController : Controller
{
public async Task<IActionResult> OrderFrame()
{
var discoveryClient = new DiscoveryClient(_configuration["IDPBaseAddress"]);
var metaDataResponse = await discoveryClient.GetAsync();
var userInfoClient = new UserInfoClient(metaDataResponse.UserInfoEndpoint);
var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
var response = await userInfoClient.GetAsync(accessToken);
if (response.IsError)
{
throw new Exception("Problem accessing the UserInfo endpoint.", response.Exception);
}
var address = response.Claims.FirstOrDefault(c => c.Type == "address")?.Value;
return View(new OrderFrameViewModel(address));
} namespace ImageGallery.MvcClient.ViewModels
{
public class OrderFrameViewModel
{
public string Address { get; } = string.Empty;
public OrderFrameViewModel(string address)
{
Address = address;
}
}
} @model ImageGallery.MvcClient.ViewModels.OrderFrameViewModel
<div class="container">
<div class="h3 bottomMarginDefault">Order a framed version of your favorite picture.</div>
<div class="text bottomMarginSmall">We've got this address on record for you:</div>
<div class="text text-info bottomMarginSmall">@Model.Address</div>
<div class="text">If this isn't correct, please contact us.</div>
</div> <li><a asp-area="" asp-controller="Gallery" asp-action="OrderFrame">Order a framed picture</a></li>
namespace DNT.IDP
{
public static class Config
{
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
//...
Claims = new List<Claim>
{
//...
new Claim("role", "PayingUser")
}
},
new TestUser
{
//...
Claims = new List<Claim>
{
//...
new Claim("role", "FreeUser")
}
}
};
} namespace DNT.IDP
{
public static class Config
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
// ...
new IdentityResource(
name: "roles",
displayName: "Your role(s)",
claimTypes: new List<string>() { "role" })
};
} namespace DNT.IDP
{
public static class Config
{
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
// ...
AllowedScopes =
{
// ...
"roles"
}
// ...
}
};
} options.Scope.Add("roles");
• Claim type: sub - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7 • Claim type: amr - Claim value: pwd • Claim type: given_name - Claim value: Vahid • Claim type: family_name - Claim value: N
options.ClaimActions.MapUniqueJsonKey(claimType: "role", jsonKey: "role");
• Claim type: sub - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7 • Claim type: amr - Claim value: pwd • Claim type: given_name - Claim value: Vahid • Claim type: family_name - Claim value: N • Claim type: role - Claim value: PayingUser
@if(User.IsInRole("PayingUser"))
{
<li><a asp-area="" asp-controller="Gallery" asp-action="OrderFrame">Order a framed picture</a></li>
} options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.GivenName,
RoleClaimType = JwtClaimTypes.Role,
}; namespace ImageGallery.MvcClient.WebApp.Controllers
{
[Authorize]
public class GalleryController : Controller
{
[Authorize(Roles = "PayingUser")]
public async Task<IActionResult> OrderFrame()
{
using Microsoft.AspNetCore.Mvc;
public class AuthorizationController : Controller
{
public IActionResult AccessDenied()
{
return View();
}
} <div class="container">
<div class="h3">Woops, looks like you're not authorized to view this page.</div>
<div>Would you prefer to <a asp-controller="Gallery" asp-action="Logout">log in as someone else</a>?</div>
</div> namespace ImageGallery.MvcClient.WebApp
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
// ...
}).AddCookie("Cookies", options =>
{
options.AccessDeniedPath = "/Authorization/AccessDenied";
})
// ...
An unhandled exception occurred while processing the request. InvalidCastException: Cannot cast Newtonsoft.Json.Linq.JArray to Newtonsoft.Json.Linq.JToken. Newtonsoft.Json.Linq.Extensions.Convert<T, U>(T token) Exception: An error was encountered while handling the remote login. Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync
options.ClaimActions.MapUniqueJsonKey(claimType: "role", jsonKey: "role");
options.ClaimActions.MapJsonKey(claimType: "role", jsonKey: "role"); // for having 2 or more roles
var ownerId = this.User.Claims.FirstOrDefault(claim => claim.Type == "sub")?.Value;
var response = await httpClient.GetAsync($"api/v1/orders/GetOrderByIdAsync?id={ownerId}"); public static IEnumerable<Client> Clients => new List<Client>
{
new Client
{
ClientId = "oidcClient",
ClientName = "Example Client Application",
ClientSecrets = new List<Secret> {new Secret("VQGBtSDEK7tzIzSJyfCYqdHDTQHt7kD2VQ1hHWnY7Dw=".Sha256())}, // change me!
AllowedGrantTypes = GrantTypes.Hybrid,
RedirectUris = new List<string> {"https://localhost:6001/signin-oidc"},
PostLogoutRedirectUris = new List<string>
{
"https://localhost:6001/signout-callback-oidc"
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Address,
"role",
"api.ordering.manager"
},
RequirePkce=false,
AllowOfflineAccess = true
}
}; public static IEnumerable<IdentityResource> Resources => new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResources.Address(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> {"role"}
}
}; foreach (var claim in User.Claims)
{
Debug.WriteLine($"Claim type: {claim.Type} - Claim value: {claim.Value}");
}
تنظیمات کانفیگ :
public static class ConfigTest
{
public static IEnumerable<IdentityResource> Resources => new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Address(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> {"role"}
}
};
public static IEnumerable<ApiResource> ApiResources => new[]
{
new ApiResource
{
Name = "orderingapi",
DisplayName = "Ordering Api",
Description = "Allow the application to access Ordering Api on your behalf",
Scopes = new List<string> { "api.ordering.read", "api.ordering.manager"},
ApiSecrets = new List<Secret> {new Secret("OrderingScopeSecret".Sha256())}, // change me!
UserClaims = new List<string> {"role"}
}
};
public static IEnumerable<ApiScope> ApiScopes => new[]
{
new ApiScope("api.ordering.read", "Read Access to Ordering Api"),
new ApiScope("api.ordering.manager",displayName:"manage Crud operation access to ordering api")
};
public static IEnumerable<Client> Clients => new List<Client>
{
new Client
{
ClientId = "oidcClient",
ClientName = "Example Client Application",
ClientSecrets = new List<Secret> {new Secret("VQGBtSDEK7tzIzSJyfCYqdHDTQHt7kD2VQ1hHWnY7Dw=".Sha256())}, // change me!
AllowedGrantTypes = GrantTypes.Hybrid,
RedirectUris = new List<string> {"https://localhost:6001/signin-oidc"},
PostLogoutRedirectUris = new List<string>
{
"https://localhost:6001/signout-callback-oidc"
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address,
"role",
"api.ordering.manager"
},
RequirePkce=false,
AllowOfflineAccess = true
}
};
public static List<TestUser> Users => new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "meysam",
Password = "123",
Claims = new List<Claim>
{
new Claim(JwtClaimTypes.Name,"meysanm"),
new Claim(JwtClaimTypes.GivenName,"soleymani"),
new Claim(JwtClaimTypes.Email, "meysam@test.com"),
new Claim(JwtClaimTypes.Address,"amol city"),
new Claim(JwtClaimTypes.Role, "admin")
}
},
new TestUser
{
SubjectId = "2",
Username = "ali",
Password = "123",
Claims = new List<Claim>
{
new Claim(JwtClaimTypes.Name,"ali"),
new Claim(JwtClaimTypes.GivenName,"abbasi"),
new Claim(JwtClaimTypes.Address,"tehran city"),
new Claim(JwtClaimTypes.Role,"public")
}
}
};
}
Claim type: http://schemas.microsoft.com/claims/authnmethodsreferences - Claim value: pwd Claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - Claim value: 1 Claim type: auth_time - Claim value: 1651417673 Claim type: http://schemas.microsoft.com/identity/claims/identityprovider - Claim value: local Claim type: name - Claim value: meysanm Claim type: given_name - Claim value: soleymani Claim type: role - Claim value: admin Claim type: http://schemas.microsoft.com/claims/authnmethodsreferences - Claim value: pwd Claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - Claim value: 1 Claim type: auth_time - Claim value: 1651417673 Claim type: http://schemas.microsoft.com/identity/claims/identityprovider - Claim value: local Claim type: name - Claim value: meysanm Claim type: given_name - Claim value: soleymani Claim type: role - Claim value: admin
if (ModelState.IsValid)
{
// find user by username
var user = await _signInManager.UserManager.FindByNameAsync(Input.Username);
// validate username/password using ASP.NET Identity
if (user != null && (await _signInManager.CheckPasswordSignInAsync(user, Input.Password, true)) == SignInResult.Success)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId));
// only set explicit expiration here if user chooses "remember me".
// otherwise we rely upon expiration configured in cookie middleware.
AuthenticationProperties props = null;
if (LoginOptions.AllowRememberLogin && Input.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(LoginOptions.RememberMeLoginDuration)
};
};
// issue authentication cookie with subject ID and username
var isuser = new IdentityServerUser(user.Id)
{
DisplayName = user.UserName
};
await HttpContext.SignInAsync(isuser, props);
if (context != null)
{
if (context.IsNativeClient())
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
return this.LoadingPage(Input.ReturnUrl);
}
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(Input.ReturnUrl);
}
// request for a local page
if (Url.IsLocalUrl(Input.ReturnUrl))
{
return Redirect(Input.ReturnUrl);
}
else if (string.IsNullOrEmpty(Input.ReturnUrl))
{
return Redirect("~/");
}
else
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
}
await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId: context?.Client.ClientId));
ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage);
}