OiDc 可以说是 OAuth 的改造版, 在最初的 OAuth 中, 我们需要先请求一下认证服务器获取下 Access_token, 然后根据 Access_token 去 Get 资源服务器, 况且 OAuth1 和 2 完全不兼容, 易用性差, 而 OIDC 可以在登陆的时候就把信息返回给你, 不需要你在请求一下资源服务器. 下面我们根据 Oidc 来做一个单点登录.
新建三个项目 (.NET Core Mvc) 两个 Client(端口 5001,5002), 一个 Server(5000), 首先在 Server 中添加 IdentityServer4 的引用.
在 Server 中 Config.cs 用于模拟配置.
- public class Config
- {
- public static IEnumerable<ApiResource> GetApiResource()
- {
- return new List<ApiResource>
- {
- new ApiResource("api","My Api App")
- };
- }
- public static IEnumerable<Client> GetClients()
- {
- return new List<Client>
- {
- new Client()
- {
- ClientId = "mvc",
- AllowedGrantTypes = GrantTypes.Implicit,
- ClientSecrets ={
- new Secret("secret".Sha256())
- },
- RequireConsent = false,
- RedirectUris = {"http://localhost:5001/signin-oidc",
- "http://localhost:5002/signin-oidc" } ,
- PostLogoutRedirectUris = {"http://localhost:5001/signout-callback-oidc" ,
- "http://localhost:5002/signout-callback-oidc" },
- AllowedScopes = {
- IdentityServerConstants.StandardScopes.Profile,
- IdentityServerConstants.StandardScopes.OpenId
- }
- }
- };
- }
- public static List<TestUser> GetTestUsers()
- {
- return new List<TestUser>
- {
- new TestUser()
- {
- SubjectId = "10000",
- Username = "zara",
- Password = "112233"
- }
- };
- }
- public static IEnumerable<IdentityResource> GetIdentityResources()
- {
- return new List<IdentityResource>
- {
- new IdentityResources.OpenId(),
- new IdentityResources.Profile(),
- new IdentityResources.Email()
- };
- }
- }
GetClient 方法中字段为 RedirectUris 是登陆成功返回的地址, 并且我们采用隐式模式(因为只是传统 web 中传递 Access_Token),RequireConsent 是否出现同意授权页面, 这个我们后续再细说. 写完 Config.cs 后, 我们需要依赖注入到 IdentityServer 中.
- public void ConfigureServices(IServiceCollection services)
- {
- services.Configure<CookiePolicyOptions>(options =>
- {
- // This lambda determines whether user consent for non-essential cookies is needed for a given request.
- options.CheckConsentNeeded = context => true;
- options.MinimumSameSitePolicy = SameSiteMode.None;
- });
- //config to identityServer Services
- services.AddIdentityServer()
- .AddDeveloperSigningCredential()
- .AddInMemoryClients(Config.GetClients())
- .AddTestUsers(Config.GetTestUsers())
- .AddInMemoryIdentityResources(Config.GetIdentityResources())
- .AddInMemoryApiResources(Config.GetApiResource());
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- }
在 Configure 中添加代码 App.UseIdentityServer(); . 我们还需要添加一个登陆页面, 名为 Account.cshtml.
- @{
- ViewData["Title"] = "Index";
- }
- <h2>Index</h2>
- @using mvcWebFirstSolucation.Models;
- @model LoginVM;
- <div class="row">
- <div class="col-md-4">
- <section>
- <form method="post" asp-controller="Account" asp-action="Login" asp-route-returnUrl="@ViewData["returnUrl"]">
- <h4>Use a local to log in .</h4>
- <hr />
- <div class="from-group">
- <label asp-for="UserName"></label>
- <input asp-for="UserName" class="form-control">
- <span asp-validation-for="UserName" class="text-danger"></span>
- </div>
- <div class="from-group">
- <label asp-for="PassWord"></label>
- <input asp-for="PassWord" type="password" class="form-control">
- <span asp-validation-for="UserName" class="text-danger"></span>
- </div>
- <div class="from-group">
- <button type="submit" class="btn btn-default">log in </button>
- </div>
- </form>
- </section>
- </div>
- </div>
- @section Scripts
- {
- @await HTML.PartialAsync("_ValidationScriptsPartial")
- }
在控制器中我们写一个构造函数, 用于将 IdentityServer.Text 给我们封装好的对象传过来, 这个对象是我们在 Config.cs 中添加的用户信息, 也就是 GetClients 的返回值, 全都在 TestUserStore 中. 其中还有一个提供好的方法, 来给我们用, 如果验证通过我们直接跳转到了传递过来的 ReturnUrl.
- public class AccountController : Controller
- {
- private readonly TestUserStore _users;
- public AccountController(TestUserStore ussotre)
- {
- _users = ussotre;
- }
- [HttpGet]
- [Route("/Account/Login")]
- public IActionResult Index(string ReturnUrl = null)
- {
- ViewData["returnUrl"] = ReturnUrl;
- return View();
- }
- private IActionResult RediretToLocal(string returnUrl)
- {
- if (Url.IsLocalUrl(returnUrl))
- {
- return Redirect(returnUrl);
- }
- return RedirectToAction(nameof(HomeController.Index),"Home");
- }
- [HttpPost]
- public async Task<IActionResult> Login(LoginVM vm,string returnUrl = null)
- {
- if (ModelState.IsValid)
- {
- ViewData["returnUrl"] = returnUrl;
- var user = _users.FindByUsername(vm.UserName);
- if (user==null)
- {
- ModelState.AddModelError(nameof(LoginVM.UserName), "userName is exists");
- }
- else
- {
- if(_users.ValidateCredentials(vm.UserName, vm.PassWord))
- {
- var props = new AuthenticationProperties
- {
- IsPersistent = true,
- ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30))
- };
- await Microsoft.AspNetCore.Http
- .AuthenticationManagerExtensions
- .SignInAsync( HttpContext, user.SubjectId, user.Username, props );
- return RediretToLocal(returnUrl);
- }
- ModelState.AddModelError(nameof(LoginVM.PassWord), "Wrong Password");
- }
- }
- return View();
- }
- }
这个时候最基本的服务端已经配置成功了, 我们开始配置受保护的客户端吧.
在客户端中我们不需要引入 IdentityServer, 因为我们只是去请求服务端然后看看 cookies 有没有在而已, 所以我们只需要给受保护的客户端的 API 做好安全判断就好.
在受保护的控制器中添加 [Authorize] 标识. 然后再 Startup.cs 中添加安全验证. 并且在 Configure 中 use 下 App.UseAuthentication();
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddAuthentication(options =>
- {
- options.DefaultScheme = "Cookies";
- options.DefaultChallengeScheme = "oidc";
- }).AddCookie("Cookies").AddOpenIdConnect("oidc", options => {
- options.SignInScheme = "Cookies";
- options.Authority = "http://localhost:5000";
- options.RequireHttpsMetadata = false;
- options.ClientId = "mvc";
- options.ClientSecret = "secret";
- options.SaveTokens = true;
- });
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- }
在首页中最好遍历下 Claims 对象, 这个是通过 OIDC 直接给我们返回回来的.(最后另一个客户端也这么干!)
- <div>
- @foreach (var claim in User.Claims)
- {
- <dl>
- <dt>@claim.Type</dt>
- <dd>@claim.Value</dd>
- </dl>
- }
- </div>
现在我们启动项目看一下效果吧.
来源: https://www.cnblogs.com/ZaraNet/p/11136468.html