IdentityServer4 替换 Asp.net 4.5 MVC5 SimpleMembership 用于身份验证

IdentityServer4 replace Asp.net 4.5 MVC5 SimpleMembership for Authentication

我有一个 Asp.net 4.5 MVC5 应用程序,在应用程序中使用 SimpleMembership 进行身份验证和授权。现在我需要集成以使用外部 IdentityServer4 进行身份验证。我试图将 OWIN 启动文件添加到项目中并在 web.config 中关闭标准 IIS 身份验证。现在我可以被重定向到 IDServer 以进行登录和同意。

进入视图时出现问题:

错误信息:

No user found was found that has the name "". Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: No user found was found that has the name "".

Source Error:

Line 15: @if (Model != null) Line 16: { Line 17:
foreach (var claim in Model) Line 18: { Line 19:

@claim.Type

Source File: e:\GitHub\CRURepoPostBR20190607_IS4\CRU_Build\Suite\Views\Home\IS4Auth.cshtml Line: 17

Stack Trace: [InvalidOperationException: No user found was found that has the name "".]
WebMatrix.WebData.SimpleRoleProvider.GetRolesForUser(String username) +498 System.Web.Security.RolePrincipal.GetRoles() +215 System.Web.Security.d__4.MoveNext() +58
System.Security.Claims.d__51.MoveNext() +253
System.Security.Claims.d__37.MoveNext() +209

我的 IdentityServer 是 IdentityServer4 快速入门示例的克隆。我的 MVC5 应用程序很旧,使用 SimpleMembership 进行身份验证和角色授权。我手动添加了 OWIN Startup.cs 见下面的代码。

我尝试使用 IdentityServer https://demo.identityserver.io 作为 IDServer,但遇到了同样的问题。证明我的IDServer没问题

我试图创建一个简单的单独 Asp.net 4.5 MVC5 项目而不使用 SimpleMembership,它没有收到错误。

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "Cookies"
        });
        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            AuthenticationType = "oidc",
            SignInAsAuthenticationType = "Cookies",
            ClientSecret = "secret",

            //Authority = "http://localhost:5000", //ID Server
            Authority = "https://demo.identityserver.io", //ID Server
            RequireHttpsMetadata = false,
            RedirectUri = "http://localhost:58018/signin-oidc",
            //RedirectUri = "http://localhost:58018/",
            //ClientId = "myOldMvc",
            ClientId = "server.hybrid",

            ResponseType = "id_token code",
            Scope = "openid profile",
        });
        AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
    }
}

这是显示声明的视图:

@model IEnumerable<System.Security.Claims.Claim>

@{
    ViewBag.Title = "IS4Auth";
}

<h2>IS4Auth</h2>

<dl>

    @if (Model != null)
    {
        foreach (var claim in Model)
        {
            <dt>@claim.Type</dt>
            <dd>@claim.Value</dd>
        }
    }
</dl>

这是我的 system.web 和 system.webServer

配置相关部分

<system.web>
    <httpModules>
      <add name="ar.sessionscope" type="Castle.ActiveRecord.Framework.SessionScopeWebModule, Castle.ActiveRecord.web" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
    </httpModules>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" maxRequestLength="1048576" />
    <!--Change for IS4Authentication-->
    <authentication mode="None" />
    <!--<authentication mode="Forms">
      <forms loginUrl="~/Account/Login" timeout="2880" />
    </authentication>-->
    <!--<roleManager enabled="true" defaultProvider="simple">
      <providers>
        <clear />
        <add name="simple" type="WebMatrix.WebData.SimpleRoleProvider,WebMatrix.WebData" />
      </providers>
    </roleManager>
    <membership defaultProvider="simple">
      <providers>
        <clear />
        <add name="simple" type="WebMatrix.WebData.SimpleMembershipProvider,WebMatrix.WebData" enablePasswordReset="true" requiresQuestionAndAnswer="false" />
      </providers>
    </membership>-->
    <pages>
      <namespaces>
        <add namespace="System.Web.Helpers" />
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages" />
      </namespaces>
    </pages>
    <httpHandlers>
      <add verb="*" path="routes.axd" type="AttributeRouting.Web.Logging.LogRoutesHandler, AttributeRouting.Web" />
      <add verb="POST,GET,HEAD" path="elmah" type="Elmah.ErrorLogPageFactory, Elmah" />
    </httpHandlers>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
      <add name="Elmah" verb="POST,GET,HEAD" path="elmah" type="Elmah.ErrorLogPageFactory, Elmah" />
    </handlers>
    <modules>
      <!--Change for IS4Authentication-->
      <remove name="FormsAuthentication" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="X-UA-Compatible" value="IE=Edge" />
      </customHeaders>
    </httpProtocol>

  </system.webServer>

这是添加到 OpenIdConnectAuthenticationOptions 的通知代码

    Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        // use the code to get the access and refresh token
                        var tokenClient = new TokenClient(
                            "http://localhost:5000/connect/token",
                            //"server.hybrid",
                            "myApp",
                            "secret");

                        var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);

                        if (tokenResponse.IsError)
                        {
                            //throw new Exception(tokenResponse.Error);
                            throw new AuthenticationException(tokenResponse.Error);
                        }

                        // use the access token to retrieve claims from userinfo
                        var userInfoClient = new UserInfoClient("http://localhost:5000/connect/userinfo");
                        var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);

                        // create new identity
                        var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                        id.AddClaims(userInfoResponse.Claims);
                        var Name = userInfoResponse.Claims.FirstOrDefault(c => c.Type.Equals("Name", StringComparison.CurrentCultureIgnoreCase)).Value;
                        id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                        id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
                        id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                        id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                        id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

                        n.AuthenticationTicket = new AuthenticationTicket(
                            new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, JwtClaimTypes.Name, JwtClaimTypes.Role),
                            n.AuthenticationTicket.Properties
                        );

                        List<Claim> _claims = new List<Claim>();
                        _claims.AddRange(new List<Claim>
                        {
                            new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Name),
                            new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",Name)
                        });
                    },

                    RedirectToIdentityProvider = n =>
                    {
                        if (n.ProtocolMessage.RequestType != OpenIdConnectRequestType.Logout)
                        {
                            return Task.FromResult(0);
                        }

                        var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                        if (idTokenHint != null)
                        {
                            n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                        }

                        return Task.FromResult(0);
                    }
         }  

感谢d_f的帮助,我的问题已经解决了。 解决方案是使用 Notifications 显式地从 userInfo EndPoint 检索 Claims 并将它们添加到新的 Identity。请参阅 Notifications 中的以上代码 = new OpenIdConnectAuthenticationNotifications{}