重定向到Angular App后缺少声明

时间:2019-03-01 13:31:36

标签: angular identityserver4 oidc

我们已经设置了一个身份服务器以用于我们所有的身份验证和授权。我们一直在网上跟踪大多数示例,但也许我们遗漏了一些东西。

我正在分享下面的所有内容,但是如果您需要查看其他内容,请问。

预期的流量/行为:

  1. 用户转到Web应用程序(Angular)
  2. Web应用程序确定用户是否已通过身份验证,如果未通过身份验证,则重定向到Auth Server
  3. Auth Server执行其操作,用户最终进入“登录”页面
  4. 用户登录
  5. 如果登录成功,则Auth Service登录用户并将声明分配给Identity。
  6. Auth Server重定向回到Web App
  7. Web App应该能够查看令牌中的声明,特别是寻找organization_idorganization_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;
        }
    }

客户(数据库记录) Client (DB Record)

身份资源(数据库记录) Identity Resource (DB Record)

。众所周知的/ 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/",
   };

0 个答案:

没有答案