开发单点登录(SSO,Single Sign-On)系统是一个复杂但非常有用的任务,它允许用户在多个应用程序之间使用同一个登录凭证。下面是一个基本的指南,介绍如何开发一个简单的 SSO 系统。我们将涉及一些关键概念和技术实现,但由于 SSO 系统的复杂性,实际实现可能需要根据具体的需求和环境进行调整。
关键概念
- 认证服务器:负责验证用户身份,并发放认证令牌(token)。
- 服务提供者:需要保护的应用程序,依赖认证服务器进行用户认证。
- 认证令牌:可以是 JWT(JSON Web Token),用来携带用户信息和认证状态。
技术选型
- 后端技术:C# 和 ASP.NET Core
- 身份验证协议:OAuth 2.0 或 OpenID Connect
- 令牌类型:JWT
基本流程
- 用户访问服务提供者:用户访问某个应用程序(服务提供者)。
- 重定向到认证服务器:服务提供者将用户重定向到认证服务器进行登录。
- 用户登录:用户在认证服务器上输入凭证进行登录。
- 认证服务器发放令牌:认证成功后,认证服务器发放一个 JWT,并将用户重定向回服务提供者,同时附带令牌。
- 服务提供者验证令牌:服务提供者验证令牌的有效性,并获取用户信息。
示例代码
以下是一个简单的示例,展示了如何使用 ASP.NET Core 创建一个认证服务器和服务提供者。
1. 创建认证服务器
首先,创建一个 ASP.NET Core Web API 项目作为认证服务器。
dotnet new webapi -n AuthServer
Startup.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System.Text;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var key = Encoding.ASCII.GetBytes("your_secret_key_here");
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
AuthController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
[ApiController
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IConfiguration _configuration;
public AuthController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel loginModel)
{
// Verify user credentials (this is just an example, you should use a real user service)
if (loginModel.Username == "test" && loginModel.Password == "password")
{
var token = GenerateJwtToken(loginModel.Username);
return Ok(new { token });
}
return Unauthorized();
}
private string GenerateJwtToken(string username)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
public class LoginModel
{
public string Username { get; set; }
public string Password { get; set; }
}
2. 创建服务提供者
创建一个新的 ASP.NET Core Web 应用程序作为服务提供者。
dotnet new mvc -n ServiceProvider
Startup.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System.Text;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var key = Encoding.ASCII.GetBytes("your_secret_key_here");
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
HomeController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
public class HomeController : Controller
{
[Authorize]
public IActionResult Index()
{
return View();
}
[AllowAnonymous]
public IActionResult Login()
{
return View();
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginModel loginModel)
{
var client = new HttpClient();
var response = await client.PostAsJsonAsync("https://localhost:5001/api/auth/login", loginModel);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<LoginResult>();
Response.Cookies.Append("JwtToken", result.Token, new CookieOptions { HttpOnly = true });
return RedirectToAction("Index");
}
ModelState.AddModelError("", "Invalid login attempt");
return View();
}
}
public class LoginModel
{
public string Username { get; set; }
public string Password { get; set; }
}
public class LoginResult
{
public string Token { get; set; }
}
Views/Home/Login.cshtml
@model LoginModel
<form asp-action="Login" method="post">
<div class="form-group">
<label asp-for="Username"></label>
<input asp-for="Username" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" type="password" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
3. 配置和运行
认证服务器配置
在 appsettings.json
中添加 JWT 配置:
"Jwt": {
"Key": "your_secret_key_here"
}
服务提供者配置
在 appsettings.json
中添加 JWT 配置:
"Jwt": {
"Key": "your_secret_key_here"
}
运行项目
- 启动认证服务器项目。
- 启动服务提供者项目。
访问服务提供者项目的 /Home/Login
页面,输入用户名和密码进行登录。在成功登录后,会将用户重定向到首页,并显示受保护内容。
结论
上述示例展示了如何使用 ASP.NET Core 创建一个简单的 SSO 系统,其中包括一个认证服务器和一个服务提供者。认证服务器负责验证用户并发放 JWT 令牌,服务提供者验证 JWT 令牌并保护受限资源。
请注意,这是一个基本示例,实际应用中需要考虑更多的安全性和功能需求,例如:
- 更安全的存储和处理密钥。
- 使用 HTTPS 确保数据传输安全。
- 添加刷新令牌机制。
- 更复杂的用户验证和授权逻辑。