我们已经设置了一个身份服务器以用于我们所有的身份验证和授权。我们一直在网上跟踪大多数示例,但也许我们遗漏了一些东西。
我正在分享下面的所有内容,但是如果您需要查看其他内容,请问。
预期的流量/行为:
organization_id
和organization_short_name
实际流量/行为: 1.用户转到Web App(角度) 2. Web App确定用户是否已通过身份验证,如果未通过身份验证,则重定向到Auth Server 3.身份验证服务器执行其操作,用户最终进入“登录”页面 4.用户登录 5.如果登录成功,则Auth Service登录用户并将声明分配给Identity。 (已对此进行验证) 6. Auth Server重定向回Web App 7. Web App只能看到标准声明(见下文)
由于我已将 AlwaysIncludeUserClaimsInIdToken 设置为true,所以我希望所有用户声明都会出现。
我还想指出,我的确将response_type设置为id_token token
,但仅使用id_token
就具有与现在相同的结果。返回值提供了访问(承载)令牌。
退回的索赔
{"sid":"a994edfc711e0729e133acbcc9cde367","sub":"6f1803fc-125e-4920-557d-08d695d06a94","auth_time":1551446274,"idp":"local","username":"me@example.com","role":"Support","amr":["pwd"]}
AccountsController.cs
[ValidateAntiForgeryToken]
[HttpPost("login", Name = nameof(PostLogin))]
public async Task<IActionResult> PostLogin(LoginViewModel model)
{
if (ModelState.IsValid)
{
var user = await _memberManager.FindByEmailAsync(model.UserName);
if (user != null && !await _memberManager.IsLockedOutAsync(user))
{
if (await _memberManager.CheckPasswordAsync(user, model.Password))
{
if (!await _memberManager.IsEmailConfirmedAsync(user))
{
await _memberManager.ResetAccessFailedCountAsync(user);
if (await _memberManager.GetTwoFactorEnabledAsync(user))
{
_logger.LogInformation(
"Login attempt for `{username}` successful but has 2FA/2SA Enabled.",
user.UserName);
var validProviders = await _memberManager.GetValidTwoFactorProvidersAsync(user);
if (validProviders.Contains(_memberManager.Options.Tokens.AuthenticatorTokenProvider))
{
//await HttpContext.SignInAsync(IdentityConstants.TwoFactorUserIdScheme)
}
}
if (user.Organizations.Count == 0)
{
// TODO : Build Page to inform member they have not associated organizations
}
else if (user.Organizations.Count > 1)
{
// TODO : Build Page to let member select organization they want to login into
}
_claimsPrincipalFactory.Organization = user.Organizations.FirstOrDefault()?.Organization;
await HttpContext.SignOutAsync();
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);
var principal = await _claimsPrincipalFactory.CreateAsync(user);
await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, principal);
//var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password,
// model.RememberMe, true);
await _memberManager.RecordLoginAttempt(user.Id, LoginStatusType.Success);
_logger.LogInformation("Login attempt for `{username}` successful.", user.UserName);
if (string.IsNullOrWhiteSpace(model.ReturnUrl)) return View("UserClaims");
return Redirect(model.ReturnUrl);
}
await _memberManager.RecordLoginAttempt(user.Id, LoginStatusType.Failure);
_logger.LogInformation("Login attempt for `{username}` but email is unconfirmed.",
user.UserName);
ModelState.AddModelError("", _localizer["UnconfirmedEmail"]);
}
else
{
await _memberManager.RecordLoginAttempt(user.Id, LoginStatusType.Failure);
await _memberManager.AccessFailedAsync(user);
if (await _memberManager.IsLockedOutAsync(user))
_logger.LogInformation(
"Login attempt for `{username}` but account is locked out until {lockout}",
user.UserName, user.LockoutEnd);
}
}
else
{
_logger.LogWarning("Login attempt for unknown username: {username}", model.UserName);
ModelState.AddModelError("", _localizer["InvalidUsernameOrPassword"]);
}
}
return View("Login", model);
}
MemberClaimsPrinciplaFactory.cs
public class MemberClaimsPrincipalFactory : UserClaimsPrincipalFactory<Member>
{
private readonly MemberManager _memberManager;
private readonly OrganizationRoleManager _organizationRoleManager;
public MemberClaimsPrincipalFactory(MemberManager userManager,
OrganizationRoleManager organizationRoleManager,
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, optionsAccessor)
{
_memberManager = userManager;
_organizationRoleManager = organizationRoleManager;
}
public Organization Organization { get; set; }
#region Overrides of UserClaimsPrincipalFactory<Member>
public async Task<ClaimsPrincipal> CreateAsync(Member user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
var id = await GenerateClaimsAsync(user);
return new ClaimsPrincipal(id);
}
#endregion
protected async Task<ClaimsIdentity> GenerateClaimsAsync(Member user)
{
if (Organization == null) throw new NullReferenceException("Organization must have value.");
var identity = await base.GenerateClaimsAsync(user);
// Add Organization Details
identity.AddClaim(new Claim("organization_id", Organization.Id.ToString()));
identity.AddClaim(new Claim("organization_short_name", Organization.ShortName));
// Add Username
identity.AddClaim(new Claim("username", user.UserName));
// Adding Role(s) and Role Claim(s)
var roleIds = await _memberManager.GetOrganizationRoleIds(user);
foreach (var roleId in roleIds)
{
var orgRole = await _organizationRoleManager.GetRoleById(Organization.Id, roleId);
identity.AddClaim(new Claim("role", orgRole.Name));
var roleClaims = await _organizationRoleManager.GetClaimsByRoleId(orgRole.Id);
identity.AddClaims(roleClaims);
}
// Adding Member Claim(s)
var claims = await _memberManager.GetClaimsAsync(user);
foreach (var claim in claims)
identity.AddClaim(new Claim(claim.Type.ToLower(), claim.Value));
return identity;
}
}
。众所周知的/ openid配置
{
issuer: "http://localhost:5000",
jwks_uri: "http://localhost:5000/.well-known/openid-configuration/jwks",
authorization_endpoint: "http://localhost:5000/connect/authorize",
token_endpoint: "http://localhost:5000/connect/token",
userinfo_endpoint: "http://localhost:5000/connect/userinfo",
end_session_endpoint: "http://localhost:5000/connect/endsession",
check_session_iframe: "http://localhost:5000/connect/checksession",
revocation_endpoint: "http://localhost:5000/connect/revocation",
introspection_endpoint: "http://localhost:5000/connect/introspect",
device_authorization_endpoint: "http://localhost:5000/connect/deviceauthorization",
frontchannel_logout_supported: true,
frontchannel_logout_session_supported: true,
backchannel_logout_supported: true,
backchannel_logout_session_supported: true,
scopes_supported: [
"openid",
"email",
"profile",
"organization",
"auth-api",
"offline_access"
],
claims_supported: [
"sub",
"email_verified",
"email",
"zoneinfo",
"birthdate",
"gender",
"website",
"picture",
"profile",
"locale",
"preferred_username",
"middle_name",
"given_name",
"family_name",
"name",
"nickname",
"updated_at",
"organization_id",
"organization_short_name"
],
grant_types_supported: [
"authorization_code",
"client_credentials",
"refresh_token",
"implicit",
"password",
"urn:ietf:params:oauth:grant-type:device_code"
],
response_types_supported: [
"code",
"token",
"id_token",
"id_token token",
"code id_token",
"code token",
"code id_token token"
],
response_modes_supported: [
"form_post",
"query",
"fragment"
],
token_endpoint_auth_methods_supported: [
"client_secret_basic",
"client_secret_post"
],
subject_types_supported: [
"public"
],
id_token_signing_alg_values_supported: [
"RS256"
],
code_challenge_methods_supported: [
"plain",
"S256"
]
}
auth.service.ts
import { Injectable } from '@angular/core';
import { UserManager, UserManagerSettings, User } from 'oidc-client';
import { fail } from 'assert';
import { environment } from '../environments/environment';
import { UserProfile } from '../models/userprofile';
@Injectable()
export class AuthService {
private manager: UserManager = new UserManager(getClientSettings());
private user: User = null;
constructor() {
this.manager.getUser().then(user => {
this.user = user;
});
//console.log("auth.service.constructor.user=" + this.user);
//https://github.com/IdentityModel/oidc-client-js/wiki
//EVENTS - begin
this.manager.events.addSilentRenewError(function (e) {
console.log("silent renew error", e.message);
});
this.manager.events.addAccessTokenExpired(function () {
if (environment.debug_mode) {
console.log("token has expired...");
}
//creates a loop
//window.location.href = "/logoff/";
});
this.manager.events.addUserLoaded(function () {
if (environment.debug_mode) {
console.log("user has been loaded, redirect to cases...");
}
window.location.href = "/cases/";
});
//EVENTS - end
}
isLoggedIn(): boolean {
return this.user != null && !this.user.expired;
}
getUser(): any {
return this.user;
}
isUserExpired(): boolean {
if (this.user != null)
return this.user.expired
else
return true;
}
getAccessToken(): string {
return this.user.access_token;
}
getTokenType(): string {
return this.user.token_type;
}
getClaims(): any {
return this.user.profile;
}
getScopes(): any {
return this.user.scopes;
}
startAuthentication(): Promise<void> {
return this.manager.signinRedirect();
}
completeAuthentication(): Promise<void> {
return this.manager.signinRedirectCallback().then(user => {
this.user = user;
});
}
hasPermission(permission: string, category: string, subcategory: string): boolean {
let rtn: boolean = false;
if (this.user != null) {
let userProfile: UserProfile = this.getClaims();
let claim: string = category + "__" + subcategory;
let profile: string = userProfile[claim];
if (profile == undefined) {
return false;
}
if (environment.debug_mode) {
//console.log("hasPermission, profile: " + profile);
}
if (profile.includes(permission)) {
rtn = true;
}
}
return rtn;
}
logout(): Promise<void> {
return this.manager.signoutRedirect();
}
}
export function getClientSettings(): UserManagerSettings {
return {
authority: environment.auth_authority,
client_id: environment.auth_client_id,
redirect_uri: environment.auth_redirect_uri,
post_logout_redirect_uri: environment.auth_post_logout_redirect_uri,
response_type: "id_token",
scope: environment.auth_scope,
filterProtocolClaims: true,
loadUserInfo: true
//metadata: {
// issuer: environment.auth_authority.substring(0, environment.auth_authority.length - 1),
// jwks_uri: environment.auth_authority + '.well-known/openid-configuration/jwks',
// end_session_endpoint: environment.auth_authority + 'connect/endsession',
// authorization_endpoint: environment.auth_authority + 'connect/authorize',
// userinfo_endpoint: environment.auth_authority + 'connect/userinfo'
//}
//automaticSilentRenew: true,
//silent_redirect_uri: 'http://localhost:4200/silent-refresh.html'
};
}
environment.ts
export const environment = {
production: false,
debug_mode: true,
version: "{VERSION}",
auth_authority: (<any>window)._env.auth_authority,
auth_redirect_uri: (<any>window)._env.auth_redirect_uri,
auth_post_logout_redirect_uri: (<any>window)._env.auth_post_logout_redirect_uri,
auth_client_id: "ui-web-app",
auth_scope: "openid organization",
//APIs.
oc_api_members_url: "api/members/",
oc_api_appointments_url: "api/appointments/",
oc_api_centers_url: "api/centers/",
oc_api_organizations_url: "api/organizations/",
oc_api_procedure_protocols_url: "api/procedureprotocols/",
auth_api_organizations_url: "api/organizations/"
};
appConfig.json
{
"auth_authority": "http://localhost:5000/",
"auth_redirect_uri": "http://localhost:4200/auth-callback",
"auth_post_logout_redirect_uri": "http://localhost:4200/",
}
env.js
window._env = {
auth_authority: 'http://localhost:5000/',
auth_redirect_uri: "http://localhost:4200/auth-callback",
auth_post_logout_redirect_uri: "http://localhost:4200/",
};