Asp.Net MVC接入Identity Server4 全记录
Asp.Net MVC接入Identity Server4 全记录
当前环境
Net Core 2.2+ //建议使用Net Core 3.0
Asp.Net Framework 4.6.2+
Visual Studio 2019//如果使用Net Core 3.0,你可能需要预览版
新增空的解决方案
- 打开命令行。执行
dotnet new sln -n SsoTest
建立空白解决方案。
新增Identity Server4 服务端【本文不讨论服务端配置问题】
新增项目并添加到解决方案里
打开命令行或者powershell
1
2
3
4
5
6
7
8
9
10# 新增IdentityServer4模板
dotnet new -i IdentityServer4.Templates
# 新建项目
dotnet new is4empty -n IdentityServer
# 添加到解决方案
dotnet sln add .\IdentityServer\IdentityServer.csproj
# 进入项目目录
cd IdentityServer
# 添加UI
dotnet new is4ui修改Config.cs文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;
using System.Collections.Generic;
using System.Security.Claims;
namespace IdentityServer
{
public static class Config
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResources.Phone(),
};
}
public static List<TestUser> GetUsers()
{
return new List<TestUser> {
new TestUser {
SubjectId = "1",
Username = "alice",
Password = "password",
Claims = new []
{
new Claim("name", "Alice"),
new Claim("website", "https://alice.com")
}
},
new TestUser {
SubjectId = "2",
Username = "bob",
Password = "password",
Claims = new []
{
new Claim("name", "Bob"),
new Claim("website", "https://bob.com")
}
}
};
}
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[] {
new ApiResource ("api1", "My API")
};
}
public static IEnumerable<Client> GetClients()
{
var secret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256();
return new Client[] {
new Client {
ClientId = "mvc",
ClientName = "MVC Client",
ClientSecrets = {
new Secret (secret)
},
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
// where to redirect to after login
RedirectUris = { "http://localhost:5001/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5001/signout-callback-oidc" },
AllowedScopes = new List<string> {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}};
}
}
}修改Startup.cs,取消注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42public IHostingEnvironment Environment { get; }
public Startup(IHostingEnvironment environment)
{
Environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
// uncomment, if you want to add an MVC-based UI
services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
if (Environment.IsDevelopment())
{
builder.AddDeveloperSigningCredential();
}
else
{
throw new Exception("need to configure key material");
}
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// uncomment if you want to support static files
app.UseStaticFiles();
app.UseIdentityServer();
// uncomment, if you want to add an MVC-based UI
app.UseMvcWithDefaultRoute();
}新开一个命令行或者powershell窗口,运行服务端
1
2
3
4
5
6
# 还原nuget,编译
dotnet restore
dotnet build
# 运行
dotnet run
新增一个空的MVC5项目
- 打开解决方案SsoTest.sln
- 在解决方案上右键->添加->新建项目,创建MVC5项目,名为SSOTest.Client
配置MVC5接入Identity Server
修改Client项目属性,指定web端口为5001
打开包控制台,安装nuget包
1
2
3
4Install-Package IdentityModel -Version 3.10.10
Install-Package Microsoft.Owin.Security.Cookies
Install-Package Microsoft.Owin.Security.OpenIdConnect
Install-Package Microsoft.Owin.Host.SystemWeb新增OWIN的Startup.cs文件
修改为Startup.cs文件.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103using System;
using System.Net.Http;
using System.Security.Claims;
using System.Web.Helpers;
using IdentityModel;
using IdentityModel.Client;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
[ ]
namespace SSOTest.Client
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
public void ConfigureAuth(IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = "sub";
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
CookieHttpOnly = true
});
var secret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".ToSha256();
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "http://localhost:5000",
ClientId = "mvc",
ClientSecret = secret,
RedirectUri = "http://localhost:5001/signin-oidc",
PostLogoutRedirectUri = "http://localhost:5001/signout-oidc",
ResponseType = "code id_token",
Scope = "openid profile",
RequireHttpsMetadata = false,
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest()
{
Address = "http://localhost:5000",
Policy ={
RequireHttps = false
}
});
if (disco.IsError) throw new Exception(disco.Error);
// use the code to get the access and refresh token
var tokenResponse = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest()
{
Address = disco.TokenEndpoint,
ClientId = "mvc",
ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0",
Code = n.Code,
RedirectUri = n.RedirectUri
});
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoResponse = await client.GetUserInfoAsync(new UserInfoRequest()
{
Address = disco.UserInfoEndpoint,
Token = tokenResponse.AccessToken
});
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn).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, "name", "role"),
n.AuthenticationTicket.Properties);
//TODO 本地USER同步
foreach (var item in userInfoResponse.Claims)
{
if (item.Type == "sub")
{
}
}
}
}
});
}
}
}打开Controllers/HomeController.cs
在About
这个Action上加特性[Authorize]
运行Client项目,访问 http://localhost:5001/Home/About
源码下载
on the github