4. 使用IdentityServer4 实现 基于OIDC 的内部MVC客户端认证
1. 概述
-
上一个实例 中实现了简单的 ResourceOwnerPassword 授权.
-
本例将使用IdentityServer4 实现内部系统的单点登录,让组织内部的MVC 客户端使用认证中心的登录页实现登录,使用的是OAuth2 的隐式授权码模式Implicit AthorizationCode
-
MVC 客户端集成IdentityServer4 认证时,为简单起见,将使用cookie存储客户端凭据;同时启用IdentityServer4 内置的 OpenIdConnect(OIDC)身份认证服务。
2. 关键概念
-
IdentityResource : OIDC 协议中定义的身份认证资源,这种资源具有唯一名称,资源声明后将包含在用户的身份令牌中。参考: https://docs.identityserver.io/en/3.1.0/topics/resources.html
-
IIdentityServerInteractionService : 这是IdentityServer4提供的认证交互接口。
3. 准备MVC Web客户端
新建一个MVCClient 项目,设置访问地址和端口为:http://localhost:5030。
3.1 引用依赖包
<PackageReference Include="IdentityServer4" Version="4.1.1" />
3.2 注册OIDC 服务,并启用认证中间件
// DI容器中注册服务
services.AddAuthentication(opt =>
{
opt.DefaultScheme = "Cookies";
opt.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5010";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
});
// ConfigureService 中启用中间件
app.UseAuthentication();
// 将受保护页面使用【Authorize】 保护起来,访问首页时将重定向到Account/Login
[Authorize]
public class HomeController : Controller
{
…… ……
}
4. 服务端配置
4.1 添加IdentityResource(在server端的Config 类中添加) 和client
public static List<IdentityResource> GetIdentityResources()
{
var result = new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
return result;
}
// 新增一个client
new Client
{
ClientId="mvc",
ClientName="MVC Client",
RequireConsent=false,
AllowedGrantTypes= GrantTypes.Implicit, // 隐式授权码
RedirectUris={"http://localhost:5030/signin-oidc"}, // 这里的回调地址是MVC客户端的地址
PostLogoutRedirectUris={"http://localhost:5030/signout-callback-oidc"},
AllowedScopes= new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
}
// Startup 注册IdentityResource
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources)
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients)
.AddInMemoryApiScopes(Config.GetApiScopes) // 3.1 新增的坑,不加会报invalid_scope
.AddTestUsers(Config.GetTestUsers())
;
5. 测试验证
启动服务端5010,和客户端5030 此时客户端将重定向到5010 的登录页Account/Login
可以输入TestUsers中配置的用户名密码登录
5.1 Account/Login 的登录方法
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl)
{
ViewData["ReturnUrl"] = returnUrl;
var user = _users.FindByUsername(model.UserName);
if (user == null)
{
ModelState.AddModelError(nameof(model.UserName), "User not exists.");
}
else
{
if (_users.ValidateCredentials(model.UserName, model.Password))
{
var props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30))
};
var u = new IdentityServerUser(user.SubjectId) { DisplayName = user.Username };
await Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync(
this.HttpContext,
u,
props);
return Redirect(returnUrl);
}
}
return View();
}