diff --git a/README.zh-CN.md b/README.zh-CN.md
index 92d3243..367b1d3 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -33,7 +33,7 @@
### 简介
-MiniAuth 一个轻量 ASP.NET Core Identity Web 后台管理插件
+MiniAuth 一个轻量 ASP.NET Core Identity Web 后台管理中间插件
「一行代码」为「新、旧项目」 添加 Identity 系统跟用户、权限管理后台 Web UI
@@ -55,21 +55,19 @@ MiniAuth 一个轻量 ASP.NET Core Identity Web 后台管理插件
### 特点
-- 兼容 : Based on JWT, Cookie, Session 只要符合 .NET identity 规格都支持。
-- 简单 : 拔插设计,API、MVC、Razor Page 等,都能开箱即用
+- 兼容 : 支持 .NET identity Based on JWT, Cookie, Session 等
+- 简单 : 拔插设计,API、MVC、Razor Page 等开箱即用
+- 支持多数据库 : 支持符合 Oracle, SQL Server, MySQL etc.
+- 渐进、非侵入式 : 不影响现有数据库、项目结构
- 多平台 : 支持 Linux, macOS 环境
-- 支持多数据库 : 符合 Identity EF Core 规格的数据库都支持
-- 渐进、非侵入式 : 预设不会影响现有数据库结构,如有类似组织、部门需求在渐进添加
### 安装
从 [NuGet](https://www.nuget.org/packages/MiniAuth) 安装套件
-```
+```cmd
dotnet add package MiniAuth
-// or
-NuGet\Install-Package MiniAuth
```
@@ -84,7 +82,7 @@ NuGet\Install-Package MiniAuth
{
var builder = WebApplication.CreateBuilder(args);
- builder.Services.AddMiniAuth();
+ builder.Services.AddMiniAuth(); // <= ❗❗❗
var app = builder.Build();
app.Run();
diff --git a/src/MiniAuth.IdentityAuth/MiniAuth.IdentityAuth.csproj b/src/MiniAuth.IdentityAuth/MiniAuth.IdentityAuth.csproj
index 0ca70e6..f9fd0a9 100644
--- a/src/MiniAuth.IdentityAuth/MiniAuth.IdentityAuth.csproj
+++ b/src/MiniAuth.IdentityAuth/MiniAuth.IdentityAuth.csproj
@@ -35,17 +35,17 @@
-
+
-
+
-
+
diff --git a/src/MiniAuth.IdentityAuth/MiniAuthIdentityEndpoints.cs b/src/MiniAuth.IdentityAuth/MiniAuthIdentityEndpoints.cs
index 8a5608c..dd1b2bf 100644
--- a/src/MiniAuth.IdentityAuth/MiniAuthIdentityEndpoints.cs
+++ b/src/MiniAuth.IdentityAuth/MiniAuthIdentityEndpoints.cs
@@ -3,17 +3,21 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
using MiniAuth;
using MiniAuth.IdentityAuth.Helpers;
using MiniAuth.IdentityAuth.Models;
using System;
using System.Collections.Concurrent;
+using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Security.Claims;
@@ -38,7 +42,7 @@ TDbContext _dbContext
{
await OkResult(context, _endpointCache.Values.OrderByDescending(o => o.Id).ToJson());
})
- .RequireAuthorization(new AuthorizeAttribute() { Roles= "miniauth-admin" });
+ .RequireAuthorization(new AuthorizeAttribute() { Roles = "miniauth-admin" });
endpoints.MapGet("/miniauth/logout", async (HttpContext context
, SignInManager signInManager
, IOptions identityOptionsAccessor
@@ -55,6 +59,7 @@ TDbContext _dbContext
endpoints.MapPost("/miniauth/login", async (HttpContext context
, TDbContext _dbContext
, SignInManager signInManager
+ , UserManager _userManager
) =>
{
JsonDocument bodyJson = await GetBodyJson(context);
@@ -62,19 +67,69 @@ TDbContext _dbContext
var userName = root.GetProperty("username");
var password = root.GetProperty("password");
var remember = root.GetProperty("remember");
- var result = await signInManager.PasswordSignInAsync(userName, password, remember, lockoutOnFailure: false);
- if (result.Succeeded)
+
+ if (MiniAuth.MiniAuthOptions.AuthenticationType == MiniAuthOptions.AuthType.BearerJwt)
{
- var newToken = Guid.NewGuid().ToString();
- //context.Response.Cookies.Append("X-MiniAuth-Token", newToken);
- await OkResult(context, $"{{\"X-MiniAuth-Token\":\"{newToken}\"}}");
- //await OkResult(context, "".ToJson(code: 200, message: ""));
+ var user = await _dbContext.Users.FirstOrDefaultAsync(f => f.UserName == userName);
+ if (!(user != null && await _userManager.CheckPasswordAsync((TIdentityUser)user, password)))
+ {
+ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ return;
+ }
+ var claims = new List
+ {
+ new Claim(ClaimTypes.Name, user.UserName),
+ new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
+ };
+ var userRoles = _dbContext.UserRoles.Where(w => w.UserId == user.Id).Select(s => s.RoleId).ToArray();
+ foreach (var userRole in userRoles)
+ {
+ claims.Add(new Claim(ClaimTypes.Role, userRole));
+ }
+ var jwtToken = new JwtSecurityTokenHandler().WriteToken(CreateToken(claims, MiniAuthOptions.TokenExpiresIn));
+ var result = new
+ {
+ tokenType = "Bearer",
+ accessToken = jwtToken,
+ expiresIn = MiniAuthOptions.TokenExpiresIn,
+ //refreshToken = refreshToken
+ };
+ /*
+e.g.
+{
+ "ok": true,
+ "code": 200,
+ "message": null,
+ "data": {
+ "tokenType": "Bearer",
+ "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTgxMTkzMzh9.I-tm9436GEXyETgUSzL7KeX5RvyN8X_4rLAKLDMZnZk",
+ "expiresIn": 900
+ }
+}
+ */
+
+ await OkResult(context, result.ToJson());
return;
}
else
{
- context.Response.StatusCode = StatusCodes.Status401Unauthorized;
- return;
+ var result = await signInManager.PasswordSignInAsync(userName, password, remember, lockoutOnFailure: false);
+ if (result.Succeeded)
+ {
+ var newToken = Guid.NewGuid().ToString();
+ var jsonResult = new
+ {
+ token = newToken,
+ expiration = null as DateTime?
+ };
+ await OkResult(context, jsonResult.ToJson());
+ return;
+ }
+ else
+ {
+ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ return;
+ }
}
});
}
@@ -96,7 +151,7 @@ TDbContext _dbContext
};
}));
await OkResult(context, roles.ToJson());
- }).RequireAuthorization(new AuthorizeAttribute() { Roles= "miniauth-admin" });
+ }).RequireAuthorization(new AuthorizeAttribute() { Roles = "miniauth-admin" });
endpoints.MapPost("/miniauth/api/saveRole", async (HttpContext context
, TDbContext _dbContext
@@ -152,7 +207,7 @@ TDbContext _dbContext
await _dbContext.SaveChangesAsync();
await OkResult(context, "".ToJson(code: 200, message: ""));
- }).RequireAuthorization(new AuthorizeAttribute() { Roles= "miniauth-admin" });
+ }).RequireAuthorization(new AuthorizeAttribute() { Roles = "miniauth-admin" });
endpoints.MapPost("/miniauth/api/deleteRole", async (HttpContext context
@@ -169,7 +224,7 @@ TDbContext _dbContext
await _dbContext.SaveChangesAsync();
}
await OkResult(context, "".ToJson(code: 200, message: ""));
- }).RequireAuthorization(new AuthorizeAttribute() { Roles= "miniauth-admin" });
+ }).RequireAuthorization(new AuthorizeAttribute() { Roles = "miniauth-admin" });
endpoints.MapPost("/miniauth/api/getUsers", async (HttpContext context
@@ -215,7 +270,7 @@ TDbContext _dbContext
});
var totalItems = _dbContext.Users.Count();
await OkResult(context, new { users = userVo, totalItems }.ToJson());
- }).RequireAuthorization(new AuthorizeAttribute() { Roles= "miniauth-admin" });
+ }).RequireAuthorization(new AuthorizeAttribute() { Roles = "miniauth-admin" });
endpoints.MapPost("/miniauth/api/deleteUser", async (HttpContext context
, TDbContext _dbContext
@@ -231,7 +286,7 @@ TDbContext _dbContext
await _dbContext.SaveChangesAsync();
}
await OkResult(context, "".ToJson(code: 200, message: ""));
- }).RequireAuthorization(new AuthorizeAttribute() { Roles= "miniauth-admin" });
+ }).RequireAuthorization(new AuthorizeAttribute() { Roles = "miniauth-admin" });
endpoints.MapPost("/miniauth/api/saveUser", async (HttpContext context
, TDbContext _dbContext
@@ -356,7 +411,7 @@ TDbContext _dbContext
await _dbContext.SaveChangesAsync();
await OkResult(context, "".ToJson(code: 200, message: ""));
}
- }).RequireAuthorization(new AuthorizeAttribute() { Roles= "miniauth-admin" });
+ }).RequireAuthorization(new AuthorizeAttribute() { Roles = "miniauth-admin" });
endpoints.MapPost("/miniauth/api/resetPassword", async (HttpContext context
, TDbContext _dbContext
@@ -393,7 +448,7 @@ TDbContext _dbContext
{
await OkResult(context, "".ToJson(code: 404, message: "User not found"));
}
- }).RequireAuthorization(new AuthorizeAttribute() { Roles= "miniauth-admin" });
+ }).RequireAuthorization(new AuthorizeAttribute() { Roles = "miniauth-admin" });
endpoints.MapGet("/miniauth/api/getUserInfo", async (HttpContext context
, TDbContext _dbContext
@@ -437,7 +492,17 @@ TDbContext _dbContext
}
}
}
+ private JwtSecurityToken CreateToken(List claims,int expires)
+ {
+ var secretkey = MiniAuthOptions.IssuerSigningKey;
+ var credentials = new SigningCredentials(secretkey, SecurityAlgorithms.HmacSha256);
+ var token = new JwtSecurityToken(
+ expires: DateTime.Now.AddSeconds(expires),
+ signingCredentials: credentials
+ );
+ return token;
+ }
private static string GetNewPassword()
{
return $"{Guid.NewGuid().ToString().Substring(0, 10).ToUpper()}@{Guid.NewGuid().ToString().Substring(0, 5)}";
@@ -456,6 +521,7 @@ private static async Task OkResult(HttpContext context, string result, string co
context.Response.StatusCode = StatusCodes.Status200OK;
context.Response.ContentType = contentType;
context.Response.ContentLength = result != null ? Encoding.UTF8.GetByteCount(result) : 0;
+
await context.Response.WriteAsync(result).ConfigureAwait(false);
}
}
diff --git a/src/MiniAuth.IdentityAuth/MiniAuthIdentityServiceExtensions.cs b/src/MiniAuth.IdentityAuth/MiniAuthIdentityServiceExtensions.cs
index b268d24..2d26dc5 100644
--- a/src/MiniAuth.IdentityAuth/MiniAuthIdentityServiceExtensions.cs
+++ b/src/MiniAuth.IdentityAuth/MiniAuthIdentityServiceExtensions.cs
@@ -1,11 +1,14 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
@@ -71,28 +74,36 @@ public static IServiceCollection AddMiniAuth();
}
- if (MiniAuthOptions.AuthenticationType == MiniAuthOptions.AuthType.Jwt)
+ if (MiniAuthOptions.AuthenticationType == MiniAuthOptions.AuthType.BearerJwt)
{
- throw new NotImplementedException("Jwt is not implemented yet");
- //services
- // .AddAuthentication()
- // .AddCookie()
- // .AddJwtBearer(options =>
- // {
- // options.IncludeErrorDetails = true;
- // options.TokenValidationParameters = new TokenValidationParameters
- // {
- // ValidateIssuer = true,
- // ValidateAudience = true,
- // ValidateLifetime = true,
- // ValidateIssuerSigningKey = true,
- // ValidIssuer = "practical aspnetcore",
- // ValidAudience = "https://localhost:5001/",
- // IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is custom key for practical aspnetcore sample"))
- // };
- // });
- //.AddDefaultTokenProviders()
- //.AddEntityFrameworkStores();
+
+ services.AddIdentity()
+ .AddEntityFrameworkStores()
+ .AddDefaultTokenProviders();
+
+ services.AddAuthentication(options =>
+ {
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
+
+ })
+ .AddJwtBearer(options =>
+ {
+ options.IncludeErrorDetails = true;
+
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
+ RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
+ ValidateIssuer = true,
+ ValidIssuer = "User",
+ ValidateAudience = false,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = false,
+ IssuerSigningKey =MiniAuth.MiniAuthOptions.IssuerSigningKey
+ };
+ });
}
}
else
diff --git a/src/MiniAuth.IdentityAuth/MiniAuthOptions.cs b/src/MiniAuth.IdentityAuth/MiniAuthOptions.cs
index ccfbaaa..5fc3e36 100644
--- a/src/MiniAuth.IdentityAuth/MiniAuthOptions.cs
+++ b/src/MiniAuth.IdentityAuth/MiniAuthOptions.cs
@@ -1,4 +1,8 @@
-namespace MiniAuth
+using Microsoft.Extensions.Configuration;
+using Microsoft.IdentityModel.Tokens;
+using System.Text;
+
+namespace MiniAuth
{
public class MiniAuthOptions
{
@@ -8,8 +12,13 @@ public class MiniAuthOptions
public enum AuthType
{
Cookie,
- Jwt
+ BearerJwt
}
public static AuthType AuthenticationType = AuthType.Cookie;
+ public static SecurityKey IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is miniauth key for demo"));
+ ///
+ /// Seconds
+ ///
+ public static int TokenExpiresIn = 15*60;
}
}
diff --git a/tests/TestAspNetCoreApiAot/TestAspNetCoreApiAot.sln b/tests/TestAspNetCoreApiAot/TestAspNetCoreApiAot.sln
new file mode 100644
index 0000000..168c0d3
--- /dev/null
+++ b/tests/TestAspNetCoreApiAot/TestAspNetCoreApiAot.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.8.34330.188
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestAspNetCoreApiAot", "TestAspNetCoreApiAot\TestAspNetCoreApiAot.csproj", "{597416A4-A732-48F6-8140-1FAD0FA7D520}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {597416A4-A732-48F6-8140-1FAD0FA7D520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {597416A4-A732-48F6-8140-1FAD0FA7D520}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {597416A4-A732-48F6-8140-1FAD0FA7D520}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {597416A4-A732-48F6-8140-1FAD0FA7D520}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {FEC23E2F-E0CB-43E0-9757-B84473B7A3E4}
+ EndGlobalSection
+EndGlobal
diff --git a/tests/TestBearer/TestBearer.sln b/tests/TestBearer/TestBearer.sln
new file mode 100644
index 0000000..c62101c
--- /dev/null
+++ b/tests/TestBearer/TestBearer.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.34928.147
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestBearer", "TestBearer\TestBearer.csproj", "{A6C7F301-9148-4F47-8AA0-0E64B2CEB745}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniAuth.IdentityAuth", "..\..\src\MiniAuth.IdentityAuth\MiniAuth.IdentityAuth.csproj", "{2DF57D81-261F-470E-9ECE-2C3016371304}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A6C7F301-9148-4F47-8AA0-0E64B2CEB745}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A6C7F301-9148-4F47-8AA0-0E64B2CEB745}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A6C7F301-9148-4F47-8AA0-0E64B2CEB745}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A6C7F301-9148-4F47-8AA0-0E64B2CEB745}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2DF57D81-261F-470E-9ECE-2C3016371304}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2DF57D81-261F-470E-9ECE-2C3016371304}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2DF57D81-261F-470E-9ECE-2C3016371304}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2DF57D81-261F-470E-9ECE-2C3016371304}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {671C9226-4331-4853-9517-F58398DEF2A3}
+ EndGlobalSection
+EndGlobal
diff --git a/tests/TestBearer/TestBearer/Program.cs b/tests/TestBearer/TestBearer/Program.cs
new file mode 100644
index 0000000..5fc8d67
--- /dev/null
+++ b/tests/TestBearer/TestBearer/Program.cs
@@ -0,0 +1,25 @@
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Identity;
+using MiniAuth;
+
+namespace TestBearer
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ MiniAuthOptions.AuthenticationType = MiniAuthOptions.AuthType.BearerJwt;
+ builder.Services.AddMiniAuth();
+
+ var app = builder.Build();
+
+ app.MapGet("/", () => "Hello World!")
+ .RequireAuthorization();
+ ;
+
+ app.Run();
+ }
+ }
+}
diff --git a/tests/TestBearer/TestBearer/Properties/launchSettings.json b/tests/TestBearer/TestBearer/Properties/launchSettings.json
new file mode 100644
index 0000000..54dbbc4
--- /dev/null
+++ b/tests/TestBearer/TestBearer/Properties/launchSettings.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:64331",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:5014",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/tests/TestBearer/TestBearer/TestBearer.csproj b/tests/TestBearer/TestBearer/TestBearer.csproj
new file mode 100644
index 0000000..67e2fa3
--- /dev/null
+++ b/tests/TestBearer/TestBearer/TestBearer.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/TestBearer/TestBearer/appsettings.Development.json b/tests/TestBearer/TestBearer/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/tests/TestBearer/TestBearer/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/tests/TestBearer/TestBearer/appsettings.json b/tests/TestBearer/TestBearer/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/tests/TestBearer/TestBearer/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}