一.簡介
本文使用 ASP.NET Core +IdentityServer4來實現SSO單點登錄(注銷沒有實現),我們其中使用Implicit模式和OIDC協議來實現功能,這里先記錄實現核心代碼,然后再來記錄IdentityServer幫我們做了什么(從請求的流程來訴說)。
二.預備工作
建立 ASP.NET Core 三個項目,分別是Client_IdentityServer和Client2_IdentityServer和IdentityServer_SSO
然后修改Window的host文件,注冊三個站點,分別是a.cn b.net c.cn
然后設置三個項目的啟動地址,讓三個項目地址分別對應上面的三個站點,我這里設置兩個客戶端分別是 www.a.cn 和 www.b.net ,服務端為 www.c.cn
舉例
依次按照這種方式來修改剩下的兩個項目啟動地址就可以了。
服務端配置
然后在服務端點開NuGet管理包搜索IdentityServer4進行下載
新建一個Config類 ,用來配置IdentityServer4.
public class config
{
public static IEnumerable<ApiResource> GetApiResource()
{
return new List<ApiResource>()
{
new ApiResource("api","this is api")
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>()
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Phone()
};
}
public static List<TestUser> GetTestUsers()
{
return new List<TestUser>{
new TestUser()
{
SubjectId="123",
Username="Mr.wen",
Password="123465",
Claims=new Claim[]
{
new Claim(ClaimTypes.Role,"管理員")
}
},
new TestUser()
{
SubjectId="456",
Username="123",
Password="123456",
Claims=new Claim[]
{
new Claim(ClaimTypes.Role,"閱覽者")
}
}
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>()
{
new Client()
{
ClientId="mvc_imp",
ClientName="Mvc_Name",
AllowedGrantTypes=GrantTypes.Implicit,
設置是否要授權
//RequireConsent=false,
//指定允許令牌或授權碼返回的地址(URL)
RedirectUris={ "http://www.b.net:5001/signin-oidc","http://www.a.cn:5002/signin-oidc" },
//指定允許注銷后返回的地址(URL),這里寫兩個客戶端
PostLogoutRedirectUris={ "http://www.b.net:5001/signout-callback-oidc","http://www.a.cn:5002/signout-callback-oidc" },
ClientSecrets={new Secret("secret".Sha256())},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
}
};
}
接下來配置服務端的Startup.cs的Configure和ConfigureServices方法
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer(option=>
{
//可以通過此設置來指定登錄路徑,默認的登陸路徑是/account/login
option.UserInteraction.LoginUrl = "/account/login";
})
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResource())
.AddInMemoryClients(Config.GetClients())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddTestUsers(Config.GetTestUsers());
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseIdentityServer();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}"
);
});
}
客戶端配置
配置Startup.cs文件
public void ConfigureServices(IServiceCollection services)
{
//DefaultChallengeScheme的名字要和下面AddOpenIdConnect方法第一個參數名字保持一致
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://www.c.cn:5000";
options.RequireHttpsMetadata = false;
//指定允許服務端返回的地址,默認是new PathString("/signin-oidc")
//如果這里地址進行了自定義,那么服務端也要進行修改
options.CallbackPath = new PathString("/signin-oidc");
//指定用戶注銷后,服務端可以調用客戶端注銷的地址,默認是new PathString("signout-callback-oidc")
options.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
options.ClientId = "mvc_imp";
options.ClientSecret = "secret";
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}"
);
});
}
到這里,總的配置就配好了,接下來是主要實現代碼
服務端
建立AccountController控制器,其中有login方法,在login方法里寫我們登錄的邏輯
[HttpPost]
public async Task<IActionResult> Login(LoginInputModel model)
{
//當登錄提交給后台的model為null,則返回錯誤信息給前台
if (model == null)
{
//這里我只是簡單處理了
return View();
}
//這里同理,當信息不完整的時候,返回錯誤信息給前台
if (string.IsNullOrEmpty(model.Username) || string.IsNullOrEmpty(model.Password))
{
//這里只是簡單處理了
return View();
}
//model.Username == "123" && model.Password == "123456"
//if里面的是驗證賬號密碼,可以用自定義的驗證,
//我這里使用的是TestUserStore的的驗證方法,
if (_users.FindByUsername(model.Username)!=null&&_users.ValidateCredentials(model.Username,model.Password))
{
var user = _users.FindByUsername(model.Username);
//配置Cookie
AuthenticationProperties properties = new AuthenticationProperties()
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30))
};
//使用IdentityServer的SignInAsync來進行注冊Cookie
await HttpContext.SignInAsync(user.SubjectId, model.Username);
//使用IIdentityServerInteractionService的IsValidReturnUrl來驗證ReturnUrl是否有問題
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
return View();
}
return View();
}
客戶端
客戶端的代碼很簡單,即想要驗證的方法或控制器添加一個Authorize特性就可以了
[Authorize]
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
到這里,客戶端和服務端的核心代碼基本結束了,如果想要看程序的UI,或者想要看完整的后台,可以查看我的GitHub
https://github.com/MDZZ3/Identity_SSO
參考
https://www.cnblogs.com/RainingNight/p/oidc-authentication-in-asp-net-core.html