abp集成IdentityServer4和單點登錄


在abp開發的系統后,需要使用這個系統作單點登錄,及其他項目登錄賬號依靠abp開發的系統。在官方文檔上只找到作為登錄服務Identity Server Integration,但是host項目卻無法使用登錄服務生成的Token獲取數據。所有的搜索結果包括abp的issue都是說去看identity server4的文檔。我比較笨,文檔看了還是不會。好在最后還是試出來了。


創建登錄中心項目

  1. 到官網下載一個最新的模板項目,項目類型自選(我們項目用的vue,所以我選擇的vue項目,.net core3.x)。保證可以運行起來並正常登錄。
  2. 右鍵src目錄添加一個asp.net core web 空項目,在項目中添加Startup文件夾,把Startup.cs和Program.cs移動到Startup文件夾,並修改這兩個文件的命名空間增加Startup。不然會有命名空間和類名沖突。
  3. 在nuget添加Abp.ZeroCore.IdentityServer4、Abp、Abp.Castle.Log4Net等引用,添加Web.Core、EntityFrameworkCore項目引用
    IdentityServernuget引用
  4. 在Startup文件加新增xxxModule文件,初始化登錄中心項目,因為這個項目要用到abp的模塊所以要添加module
using Abp.Ids4;
using Abp.Ids4.Configuration;
using Abp.Modules;
using Abp.Reflection.Extensions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;

namespace Abp.Ids4.Server.Startup
{
    [DependsOn(
       typeof(Ids4WebCoreModule))]
    public class AbpIds4ServerModule: AbpModule
    {
        private readonly IWebHostEnvironment _env;
        private readonly IConfigurationRoot _appConfiguration;

        public AbpIds4ServerModule(IWebHostEnvironment env)
        {
            _env = env;
            _appConfiguration = env.GetAppConfiguration();
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(typeof(AbpIds4ServerModule).GetAssembly());
        }
    }
}

  1. 在Startup文件加新增AuthConfigurer.cs文件,你也可以直接從IdentityServerDemo項目復制文件過來,但是記得修改命名空間
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Abp.Authorization;
using Abp.Ids4;
using Abp.Runtime.Security;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;

namespace Abp.Ids4.Server.Startup
{
    public static class AuthConfigurer
    {
        /// <summary>
        /// Configures the specified application.
        /// </summary>
        /// <param name="app">The application.</param>
        /// <param name="configuration">The configuration.</param>
        public static void Configure(IServiceCollection services, IConfiguration configuration)
        {
            var authenticationBuilder = services.AddAuthentication();

            if (bool.Parse(configuration["Authentication:JwtBearer:IsEnabled"]))
            {
                authenticationBuilder.AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        // The signing key must match!
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(configuration["Authentication:JwtBearer:SecurityKey"])),

                        // Validate the JWT Issuer (iss) claim
                        ValidateIssuer = true,
                        ValidIssuer = configuration["Authentication:JwtBearer:Issuer"],

                        // Validate the JWT Audience (aud) claim
                        ValidateAudience = true,
                        ValidAudience = configuration["Authentication:JwtBearer:Audience"],

                        // Validate the token expiry
                        ValidateLifetime = true,

                        // If you want to allow a certain amount of clock drift, set that here
                        ClockSkew = TimeSpan.Zero
                    };

                    options.Events = new JwtBearerEvents
                    {
                        OnMessageReceived = QueryStringTokenResolver
                    };
                });
            }

            IdentityModelEventSource.ShowPII = true;
            authenticationBuilder.AddIdentityServerAuthentication("Bearer", options =>
            {
                options.Authority = configuration["IdentityServer:Authority"];
                options.ApiName = configuration["IdentityServer:ApiName"];
                options.ApiSecret = configuration["IdentityServer:ApiSecret"];
                options.RequireHttpsMetadata = false;
            });
        }

        /* This method is needed to authorize SignalR javascript client.
         * SignalR can not send authorization header. So, we are getting it from query string as an encrypted text. */
        private static Task QueryStringTokenResolver(MessageReceivedContext context)
        {
            if (!context.HttpContext.Request.Path.HasValue ||
                !context.HttpContext.Request.Path.Value.StartsWith("/signalr"))
            {
                //We are just looking for signalr clients
                return Task.CompletedTask;
            }

            var qsAuthToken = context.HttpContext.Request.Query["enc_auth_token"].FirstOrDefault();
            if (qsAuthToken == null)
            {
                //Cookie value does not matches to querystring value
                return Task.CompletedTask;
            }

            //Set auth token from cookie
            context.Token = SimpleStringCipher.Instance.Decrypt(qsAuthToken, AppConsts.DefaultPassPhrase);
            return Task.CompletedTask;
        }
    }
}

  1. 修改Startup文件,因為有部分文件在Web.Core項目中,但是還沒有添加進來,所以現在編譯會報錯,先忽略
using System;
using Abp.AspNetCore;
using Abp.AspNetCore.Mvc.Antiforgery;
using Abp.Castle.Logging.Log4Net;
using Abp.Dependency;
using Abp.Ids4.Configuration;
using Abp.Ids4.Identity;
using Abp.Ids4.Web.Core.IdentityServer;
using Abp.Json;
using Castle.Facilities.Logging;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json.Serialization;

namespace Abp.Ids4.Server.Startup
{
    public class Startup
    {
        private readonly IConfigurationRoot _appConfiguration;

        public Startup(IWebHostEnvironment env)
        {
            _appConfiguration = env.GetAppConfiguration();
        }
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews(
                   options =>
                   {
                       options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
                   }
               ).AddNewtonsoftJson(options =>
               {
                   options.SerializerSettings.ContractResolver = new AbpMvcContractResolver(IocManager.Instance)
                   {
                       NamingStrategy = new CamelCaseNamingStrategy()
                   };
               });

            IdentityRegistrar.Register(services);
            IdentityServerRegistrar.Register(services, _appConfiguration);
            AuthConfigurer.Configure(services, _appConfiguration);

            // Configure Abp and Dependency Injection
            return services.AddAbp<AbpIds4ServerModule>(
                // Configure Log4Net logging
                options => options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                    f => f.UseAbpLog4Net().WithConfig("log4net.config")
                )
            );
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseAbp(); //Initializes ABP framework.
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
            {
                app.UseJwtTokenMiddleware();
                app.UseIdentityServer();
            }
            app.UseStaticFiles();
            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }
    }
}

  1. 從Web.Core項目中復制appsettings.json和log4net.config到IdentityServer項目,在appsettings.json文件中增加IdentityServer4配置
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",

  "ConnectionStrings": {
    "Default": "Server=localhost\\sqlexpress; Database=Ids4Db; Trusted_Connection=True;"
  },

  "Authentication": {
    "Facebook": {
      "IsEnabled": "false",
      "AppId": "",
      "AppSecret": ""
    },
    "Google": {
      "IsEnabled": "false",
      "ClientId": "",
      "ClientSecret": ""
    },
    "JwtBearer": {
      "IsEnabled": "false",
      "SecurityKey": "Ids4_C421AAEE0D126E5C",
      "Issuer": "Ids4",
      "Audience": "Ids4"
    }
  },
  "IdentityServer": {
    "IsEnabled": "true",
    "Authority": "http://localhost:5000",
    "ApiName": "default-api",
    "ApiSecret": "secret",
    "Clients": [
      {
        "ClientId": "client",
        "AllowedGrantTypes": [
          "password",
          "client_credentials"
        ],
        "ClientSecrets": [
          {
            "Value": "def2e777-5d42-4edc-a84a-30136c340e13"
          }
        ],
        "AllowedScopes": [
          "default-api",
          "openid",
          "profile",
          "email"
        ]
      },
      {
        "ClientId": "mvc_implicit",
        "ClientName": "MVC Client",
        "AllowedGrantTypes": [ "implicit" ],
        "RedirectUris": [
          "http://localhost:5002/signin-oidc"
        ],
        "PostLogoutRedirectUris": [
          "http://localhost:5002/signout-callback-oidc"
        ],
        "AllowedScopes": [
          "openid",
          "profile",
          "email",
          "default-api"
        ],
        "AllowAccessTokensViaBrowser": true
      }
    ]
  }
}

最終項目結構如下:
IdentityServer

修改Web.Core項目

從IdentityServerDemo項目復制IdentityServer目錄和文件到xxx.Web.Core項目,修改文件中的命名空間和當前項目對應。修改IdentityServerRegistrar文件中的dbcontext,把直接引用dbcontext實例改成引用接口,如下:

public static void Register(IServiceCollection services, IConfigurationRoot configuration)
{
    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
        .AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
        .AddInMemoryClients(IdentityServerConfig.GetClients(configuration))
--      .AddAbpPersistedGrants<IdentityServerDemoDbContext>()
++      .AddAbpPersistedGrants<IAbpPersistedGrantDbContext>()
        .AddAbpIdentityServer<User>();
}

EntityFrameworkCore項目及其他修改

  1. 按照Identity Server Integration文檔修改EntityFrameworkCore項目和nuget添加引用,同時把項目因為沒有引用包報錯的添加引用。現在運行IdentityServer項目從connect/token中獲取到token了,但是這個token還不能用。即使按照IdentityServerDemo配置了也用不了,IdentityServerDemo中實際上每個web項目都是登錄中心。

  2. 修改Web.Host項目的appsettings.json

{
  "ConnectionStrings": {
    "Default": "Server=localhost\\sqlexpress; Database=Ids4Db; Trusted_Connection=True;"
  },
  "App": {
    "ServerRootAddress": "http://localhost:21022/",
    "ClientRootAddress": "http://localhost:8080/",
    "CorsOrigins": "http://localhost:4200,http://localhost:8080,http://localhost:8081,http://localhost:3000"
  },
  "Authentication": {
    "JwtBearer": {
      "IsEnabled": "true",
      "SecurityKey": "Ids4_C421AAEE0D126E5C",
      "Issuer": "Ids4",
      "Audience": "Ids4"
    }
  },
  "IdentityServer": {
    "IsEnabled": "true",
    "Authority": "http://localhost:5000",
    "ApiName": "default-api",
    "ApiSecret": "secret",
    "ClientId": "client",

    // no interactive user, use the clientid/secret for authentication
    "AllowedGrantTypes": "password",

    // secret for authentication
    "ClientSecret": "def2e777-5d42-4edc-a84a-30136c340e13",

    // scopes that client has access to
    "AllowedScopes": "default-api"
  }
}
  1. Web.Host項目在AuthConfigurer.cs文件的Configure方法中增加如下代碼
var authenticationBuilder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
IdentityModelEventSource.ShowPII = true;
authenticationBuilder
//    .AddIdentityServerAuthentication(JwtBearerDefaults.AuthenticationScheme, options =>
//{
//    options.Authority = configuration["IdentityServer:Authority"];
//    options.ApiName = configuration["IdentityServer:ApiName"];
//    options.ApiSecret = configuration["IdentityServer:ApiSecret"];
//    //options.Audience = configuration["IdentityServer:ApiName"];
//    options.RequireHttpsMetadata = false;
//})
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.Authority = configuration["IdentityServer:Authority"];
    options.RequireHttpsMetadata = false;
    options.Audience = configuration["IdentityServer:ApiName"];
})
;
  1. 修改Web.Host項目中的Startup類
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Castle.Facilities.Logging;
using Abp.AspNetCore;
using Abp.AspNetCore.Mvc.Antiforgery;
using Abp.Castle.Logging.Log4Net;
using Abp.Extensions;
using Abp.Ids4.Configuration;
using Abp.Ids4.Identity;
using Abp.AspNetCore.SignalR.Hubs;
using Abp.Dependency;
using Abp.Json;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Serialization;
using Abp.Ids4.Web.Core.IdentityServer;

namespace Abp.Ids4.Web.Host.Startup
{
    public class Startup
    {
        private const string _defaultCorsPolicyName = "localhost";

        private readonly IConfigurationRoot _appConfiguration;

        public Startup(IWebHostEnvironment env)
        {
            _appConfiguration = env.GetAppConfiguration();
        }

        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            //MVC
            services.AddControllersWithViews(
                options =>
                {
                    options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
                }
            ).AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.ContractResolver = new AbpMvcContractResolver(IocManager.Instance)
                {
                    NamingStrategy = new CamelCaseNamingStrategy()
                };
            });


            IdentityRegistrar.Register(services);
            AuthConfigurer.Configure(services, _appConfiguration);
            //其他代碼
            //...
        }

        public void Configure(IApplicationBuilder app,  ILoggerFactory loggerFactory)
        {
            app.UseAbp(options => { options.UseAbpRequestLocalization = false; }); // Initializes ABP framework.

            app.UseCors(_defaultCorsPolicyName); // Enable CORS!

            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            //app.UseJwtTokenMiddleware();
            if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
            {
                app.UseJwtTokenMiddleware();
            }

            app.UseAbpRequestLocalization();
            //...其他代碼
        }
    }
}

  1. 修改登錄方法從授權中心獲取token,修改Web.Core項目TokenAuthController.cs的Authenticate方法
public async Task<AuthenticateResultModel> Authenticate([FromBody] AuthenticateModel model)
{
    var loginResult = await GetLoginResultAsync(
        model.UserNameOrEmailAddress,
        model.Password,
        GetTenancyNameOrNull()
    );
    if (loginResult.Result != AbpLoginResultType.Success)
    {
        throw new UserFriendlyException("登錄失敗");
    }
    //var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
    var client = new HttpClient();
    var disco = await client.GetDiscoveryDocumentAsync(_appConfiguration["IdentityServer:Authority"]);
    if (disco.IsError)
    {
        throw new UserFriendlyException(disco.Error);
    }
    var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
    {
        Address = disco.TokenEndpoint,
        ClientId = _appConfiguration["IdentityServer:ClientId"],
        ClientSecret = _appConfiguration["IdentityServer:ClientSecret"],

        UserName = model.UserNameOrEmailAddress,
        Password = model.Password,
        Scope = _appConfiguration["IdentityServer:AllowedScopes"],
    });
    if (tokenResponse.IsError)
    {
        throw new UserFriendlyException(tokenResponse.Error);
    }
    var accessToken = tokenResponse.AccessToken;
    return new AuthenticateResultModel
    {
        AccessToken = accessToken,
        EncryptedAccessToken = GetEncryptedAccessToken(accessToken),
        ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
        UserId = loginResult.User.Id
    };
}

至此host項目的登錄獲取的token就是從登錄中心獲取的了,其他客戶端的對接按照使用Identity Server 4建立Authorization Server配置就可以了


源碼:https://gitee.com/XiaoShenXiana/abp_ids4.git
原文

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM