加入專案檔案。

This commit is contained in:
pc 2024-02-19 16:31:54 +08:00
parent 0422cc08f3
commit 84c75c7d71
18 changed files with 574 additions and 0 deletions

View File

@ -0,0 +1,12 @@
JWTdemo資料夾→JWTdemo.csproj 開啟
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> //新增這兩行
<GenerateDocumentationFile>true</GenerateDocumentationFile> //新增這兩行
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
1.<GenerateDocumentationFile>true</GenerateDocumentationFile>:這個設定告訴編譯器生成 XML 註解檔案。當您的專案編譯時,它會將 XML 註解嵌入到組件中,以供 Swagger 或其他工具使用。
2.<NoWarn>$(NoWarn);1591</NoWarn>:這個設定用來抑制編譯器警告 1591。警告 1591 是指程式碼中的缺少 XML 註解的警告。這裡的設定的意思是告訴編譯器忽略這個特定的警告,因為您已經啟用了 XML 註解生成,而不希望因缺少註解而收到警告。

25
JWTdemo.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34031.279
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWTdemo", "JWTdemo\JWTdemo.csproj", "{4C54D743-8EE0-44C9-8C9D-010A306C4AE7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4C54D743-8EE0-44C9-8C9D-010A306C4AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C54D743-8EE0-44C9-8C9D-010A306C4AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C54D743-8EE0-44C9-8C9D-010A306C4AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C54D743-8EE0-44C9-8C9D-010A306C4AE7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5537568D-28F4-41EA-A9EE-89BAF86E4808}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,6 @@
namespace JWTdemo.Authorization;
[AttributeUsage(AttributeTargets.Method)]
public class AllowAnonymousAttribute : Attribute
{
}

View File

@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using JWTdemo.Entities;
namespace JWTdemo.Authorization;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
// skip authorization if action is decorated with [AllowAnonymous] attribute
var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
if (allowAnonymous)
return;
// authorization
var user = (User?)context.HttpContext.Items["User"];
if (user == null)
{
// not logged in or role not authorized
context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
}
}
}

View File

@ -0,0 +1,23 @@
namespace JWTdemo.Authorization;
public class JwtMiddleware
{
private readonly RequestDelegate _next;
public JwtMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IUserService userService, IJwtUtils jwtUtils)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
var userId = jwtUtils.ValidateJwtToken(token);
if (userId != null)
{
// attach user to context on successful jwt validation
context.Items["User"] = userService.GetById(userId.Value);
}
//var stop = "1";
await _next(context);
}
}

View File

@ -0,0 +1,106 @@
namespace JWTdemo.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using JWTdemo.Entities;
using JWTdemo.Helpers;
public interface IJwtUtils
{
public string GenerateJwtToken(User user);
public int? ValidateJwtToken(string? token);
}
public class JwtUtils : IJwtUtils
{
private readonly AppSettings _appSettings;
public JwtUtils(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
if (string.IsNullOrEmpty(_appSettings.Secret))
throw new Exception("JWT secret not configured");
}
public string GenerateJwtToken(User user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler(); //實例化JWT令牌
var key = Encoding.ASCII.GetBytes(_appSettings.Secret!); //從配置中獲取應用程序密鑰,用於簽名令牌以確保其完整性和安全性
var tokenDescriptor = new SecurityTokenDescriptor //定義令牌格式其包含header(SigningCredentials).payload(expires和subject).signature(簽在header裡面)
{
Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }), //payload
Expires = DateTime.UtcNow.AddDays(7), //payload令牌過期時間
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) //header
};
var token = tokenHandler.CreateToken(tokenDescriptor); //創建JWT令牌
return tokenHandler.WriteToken(token); //將JWT令牌轉為base64編碼
}
public int? ValidateJwtToken(string? token)
{
if (token == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret!);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// return user id from JWT token if validation successful
return userId;
}
catch
{
// return null if validation fails
return null;
}
}
//0523
public bool ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtSecret = "your_jwt_secret"; // JWT 密钥,应与生成令牌时使用的密钥相匹配
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero // 设置为零以确保令牌过期时立即失效
};
try
{
SecurityToken validatedToken;
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
return true;
}
catch
{
return false;
}
}
}

View File

@ -0,0 +1,28 @@
using JWTdemo.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using JWTdemo.Models;
using JWTdemo.Authorization;
namespace JWTdemo.Controllers
{
[Authorize] //有token才能使用class
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly SqlContext _context;
public UserController(SqlContext context)
{
_context = context;
}
/// <summary>
/// 測試註解
/// </summary>
[HttpGet]
public async Task<ActionResult<IEnumerable<JWTdemo.Entities.User>>> Getuser()
{
return await _context.chatuser.ToListAsync();
}
}
}

14
JWTdemo/Entities/User.cs Normal file
View File

@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace JWTdemo.Entities
{
public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Username { get; set; }
[JsonIgnore] //這個就是當有人要get這個資料時會自動將其隱藏
public string? Password { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace JWTdemo.Helpers
{
public class AppSettings
{
public string? Secret { get; set; }
}
}

20
JWTdemo/JWTdemo.csproj Normal file
View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.11" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.30.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
namespace JWTdemo.Models;
using System.ComponentModel.DataAnnotations;
public class AuthenticateRequest
{
[Required]
public string? Username { get; set; }
[Required]
public string? Password { get; set; }
}

View File

@ -0,0 +1,20 @@
namespace JWTdemo.Models;
using JWTdemo.Entities;
public class AuthenticateResponse
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Username { get; set; }
public string Token { get; set; }
public AuthenticateResponse(User user, string token)
{
Id = user.Id;
Name = user.Name;
Username = user.Username;
Token = token;
}
}

130
JWTdemo/Program.cs Normal file
View File

@ -0,0 +1,130 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using JWTdemo.Authorization;
using JWTdemo.Helpers;
using System.Configuration;
using System.Reflection;
using JWTdemo.Services;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors();
builder.Services.AddControllers();
// Add services to the container.
//builder.Services.AddControllersWithViews();
//*------------------------------連線PostgreSQL資料庫-----------------------------------------------
var connectionString = "Server=localhost;UserID=postgres;Password=vip125125;Database=postgres;port=5432;";
builder.Services.AddDbContext<SqlContext>(opt => opt.UseNpgsql(connectionString));
//*---------------------------------JWT身分驗證-------------------------------------------------------
{
var services = builder.Services;
services.AddCors();
services.AddControllers();
services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));
var jwtSettings = builder.Configuration.GetSection("AppSettings").Get<AppSettings>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
//ValidIssuer = "your_issuer",
// ValidAudience = "your_audience",
ClockSkew = TimeSpan.Zero,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Secret))
};
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApi_data_value", Version = "v1" });
// Configure Swagger to use JWT authentication
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
// 将JWT令牌作为所有端点的要求添加到Swagger文档
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
// configure DI for application services
services.AddScoped<IJwtUtils, JwtUtils>();
services.AddScoped<IUserService, UserService>();
// 注册 HttpClient 服务
services.AddHttpClient();
}
//*---------------------------創專案就有--------------------------
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//*---------------------------WebAPI註解設定--------------------------
builder.Services.AddSwaggerGen(options =>
{
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});
var app = builder.Build();
//*---------------------------JWT身分驗證------------------------------
{
// global cors policy
//在 ASP.NET Core 中啟用 CORS (跨原始來源要求)
// Shows UseCors with CorsPolicyBuilder.
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
// custom jwt auth middleware
app.UseMiddleware<JwtMiddleware>();
app.MapControllers();
}
//-------------------------Swagger初始化-------------------------------------
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "JWTdemo"); //API註解開啟
});
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:23800",
"sslPort": 44344
}
},
"profiles": {
"JWTdemo": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7079;http://localhost:5246",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
using JWTdemo.Entities;
namespace JWTdemo.Services
{
public class SqlContext : DbContext
{
public SqlContext(DbContextOptions<SqlContext> options) : base(options)
{
//連接PostgreSQL
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
}
public DbSet<User>chatuser { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<User>().HasKey(o => new { o.Id }); //Primary Key
}
}
}

View File

@ -0,0 +1,70 @@
using JWTdemo.Authorization;
using JWTdemo.Services;
using JWTdemo.Entities;
using JWTdemo.Models;
public interface IUserService
{
AuthenticateResponse? Authenticate(AuthenticateRequest model);
IEnumerable<User> GetAll();
User? GetById(int id);
}
public class UserService : IUserService
{
/*
// users hardcoded for simplicity, store in a db with hashed passwords in production applications
private List<User> user_test = new List<User>
{
new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" },
new User { Id = 2, FirstName = "Test", LastName = "User", Username = "admin", Password = "admin" }
};
public DbSet<User> user_test { get; set; } = null!;
public List<User> GetUsers ()
{
return _dbContext.user_test.ToList();
}
*/
private readonly IJwtUtils _jwtUtils;
public UserService(IJwtUtils jwtUtils, SqlContext dbContext)
{
_jwtUtils = jwtUtils;
_dbContext = dbContext;
}
private readonly SqlContext _dbContext;
public AuthenticateResponse? Authenticate(AuthenticateRequest model)
{
var user = _dbContext.chatuser.SingleOrDefault(x => x.Username == model.Username && x.Password == model.Password);
// return null if user not found
if (user == null) return null;
// authentication successful so generate jwt token
var token = _jwtUtils.GenerateJwtToken(user);
return new AuthenticateResponse(user, token);
}
public IEnumerable<User> GetAll()
{
return _dbContext.chatuser;
}
public User? GetById(int id)
{
return _dbContext.chatuser.FirstOrDefault(x => x.Id == id);
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

12
JWTdemo/appsettings.json Normal file
View File

@ -0,0 +1,12 @@
{
"AppSettings": {
"Secret": "Leo token test jwt park spaces lab 124"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}