Skip to content

Commit

Permalink
Support option for identity JwtBearer #153
Browse files Browse the repository at this point in the history
  • Loading branch information
shps951023 committed Jun 11, 2024
1 parent cae4602 commit ebca211
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 51 deletions.
16 changes: 7 additions & 9 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

### 简介

MiniAuth 一个轻量 ASP.NET Core Identity Web 后台管理插件
MiniAuth 一个轻量 ASP.NET Core Identity Web 后台管理中间插件

「一行代码」为「新、旧项目」 添加 Identity 系统跟用户、权限管理后台 Web UI

Expand All @@ -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
```


Expand All @@ -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();
Expand Down
6 changes: 3 additions & 3 deletions src/MiniAuth.IdentityAuth/MiniAuth.IdentityAuth.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
<!--<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />-->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.0" />
<!--<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />-->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0" />
<!--<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />-->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
Expand Down
98 changes: 82 additions & 16 deletions src/MiniAuth.IdentityAuth/MiniAuthIdentityEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,7 +42,7 @@ TDbContext _dbContext
{
await OkResult(context, _endpointCache.Values.OrderByDescending<RoleEndpointEntity, string>(o => o.Id).ToJson());
})
.RequireAuthorization(new AuthorizeAttribute() { Roles= "miniauth-admin" });
.RequireAuthorization(new AuthorizeAttribute() { Roles = "miniauth-admin" });
endpoints.MapGet("/miniauth/logout", async (HttpContext context
, SignInManager<TIdentityUser> signInManager
, IOptions<IdentityOptions> identityOptionsAccessor
Expand All @@ -55,26 +59,77 @@ TDbContext _dbContext
endpoints.MapPost("/miniauth/login", async (HttpContext context
, TDbContext _dbContext
, SignInManager<TIdentityUser> signInManager
, UserManager<TIdentityUser> _userManager
) =>
{
JsonDocument bodyJson = await GetBodyJson(context);
var root = bodyJson.RootElement;
var userName = root.GetProperty<string>("username");
var password = root.GetProperty<string>("password");
var remember = root.GetProperty<bool>("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<Claim>
{
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;
}
}
});
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -437,7 +492,17 @@ TDbContext _dbContext
}
}
}
private JwtSecurityToken CreateToken(List<Claim> 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)}";
Expand All @@ -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);
}
}
53 changes: 32 additions & 21 deletions src/MiniAuth.IdentityAuth/MiniAuthIdentityServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -71,28 +74,36 @@ public static IServiceCollection AddMiniAuth<TDbContext, TIdentityUser, TIdentit
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<TDbContext>();
}
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<TDbContext>();

services.AddIdentity<TIdentityUser, TIdentityRole>()
.AddEntityFrameworkStores<TDbContext>()
.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
Expand Down
13 changes: 11 additions & 2 deletions src/MiniAuth.IdentityAuth/MiniAuthOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
namespace MiniAuth
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace MiniAuth
{
public class MiniAuthOptions
{
Expand All @@ -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"));
/// <summary>
/// Seconds
/// </summary>
public static int TokenExpiresIn = 15*60;
}
}
25 changes: 25 additions & 0 deletions tests/TestAspNetCoreApiAot/TestAspNetCoreApiAot.sln
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit ebca211

Please sign in to comment.