This commit is contained in:
威勝 張 2025-01-19 13:18:10 +08:00
parent 22be1236d5
commit da6bf1a5d8
539 changed files with 15280 additions and 57 deletions

View File

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

View File

@ -0,0 +1,25 @@
namespace TCM_API.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using TCM_API.Entities;
[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,26 @@
namespace TCM_API.Authorization;
using TCM_API.Services;
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,105 @@
namespace TCM_API.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using TCM_API.Entities;
using TCM_API.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();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret!);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", user.id.ToString()) }),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
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,56 @@
namespace WebApi.Controllers;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using NuGet.Common;
using TCM_API.Authorization;
using TCM_API.Models;
using TCM_API.Services;
[ApiController]
[Authorize]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[AllowAnonymous]
[HttpPost("authenticate")]
public IActionResult Authenticate(AuthenticateRequest model)
{
var response = _userService.Authenticate(model);
if (response == null)
return BadRequest(new { message = "Username or password is incorrect" });
// 将令牌添加到响应头中
Response.Headers.Add("Authorization", "Bearer " + response.Token);
// 将令牌保存在Cookie或其他适当的位置
Response.Cookies.Append("token", response.Token);
return Ok(response);
// 重定向到另一个页面
//return RedirectToAction("/Park_spaces/Parking_spaces_total_table");
//return RedirectToAction("Parking_spaces_total_table", "Park_spaces");
}
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
[HttpGet("token")]
public IActionResult Token()
{
return Ok();
}
}

View File

@ -1,33 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace TCM_API.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@ -0,0 +1,15 @@
namespace TCM_API.Entities;
using MessagePack;
using System.Text.Json.Serialization;
public class User
{
public int id { get; set; }
public string? firstname { get; set; }
public string? lastname { get; set; }
public string? username { get; set; }
[JsonIgnore]
public string? password { get; set; }
}

View File

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

View File

@ -0,0 +1,12 @@
namespace TCM_API.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,22 @@
namespace TCM_API.Models;
using TCM_API.Entities;
public class AuthenticateResponse
{
public int id { get; set; }
public string? firstname { get; set; }
public string? lastname { get; set; }
public string? username { get; set; }
public string Token { get; set; }
public AuthenticateResponse(User user, string token)
{
id = user.id;
firstname = user.firstname;
lastname = user.lastname;
username = user.username;
Token = token;
}
}

View File

@ -0,0 +1,6 @@
namespace TCM_API.Models
{
public class TEST_01
{
}
}

View File

@ -1,4 +1,94 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TCM_API.Authorization;
using TCM_API.Helpers;
using System.Configuration;
using TCM_API.Services;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
//在 ASP.NET Core 中啟用 CORS (跨原始來源要求)
builder.Services.AddCors();
// Add services to the container.
builder.Services.AddControllers();
// 連線PostgreSQL資料庫
var connectionString = "Server=leovip125.ddns.net;UserID=postgres;Password=vip125;Database=TCM;port=5432;Search Path=public;CommandTimeout=1800";
builder.Services.AddDbContext<SqlContext>(opt => opt.UseNpgsql(connectionString));
//身分驗證
//add services to DI container
{
var services = builder.Services;
services.AddCors();
services.AddControllers();
// configure strongly typed settings object
services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));
// 配置JWT身份验证
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 = "TCM_API", 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.OperationFilter<SecurityRequirementsOperationFilter>();
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();
}
// Add services to the container. // Add services to the container.
@ -9,17 +99,48 @@ builder.Services.AddSwaggerGen();
var app = builder.Build(); var app = builder.Build();
//身分驗證
// configure HTTP request pipeline
{
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
// custom jwt auth middleware
app.UseMiddleware<JwtMiddleware>();
app.MapControllers();
}
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
{ {
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(); //app.UseSwaggerUI();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "TCM_API");
});
} }
//在 ASP.NET Core 中啟用 CORS (跨原始來源要求)
// Shows UseCors with CorsPolicyBuilder.
app.UseCors(builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.MapControllers(); app.MapControllers();
app.Run(); app.Run();

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration.Json;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using TCM_API.Models;
using TCM_API.Entities;
namespace TCM_API.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<E_table_v> e_table_v { get; set; } = null!;
//public DbSet<Test_0525> test_0525 { get; set; } = null!; //<Test_0330> Model名稱 test_0330 SQL名稱
public DbSet<User> user_table { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
//builder.Entity<Test_0525>().HasKey(o => new { o.user_id }); //Primary Key
builder.Entity<User>().HasKey(o => new { o.id }); //Primary Key
}
}
}

View File

@ -0,0 +1,71 @@
namespace TCM_API.Services;
using Microsoft.EntityFrameworkCore;
using TCM_API.Authorization;
using TCM_API.Entities;
using TCM_API.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.user_table.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.user_table;
}
public User? GetById(int id)
{
return _dbContext.user_table.FirstOrDefault(x => x.id == id);
}
}

View File

@ -9,8 +9,23 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
<Folder Include="Services\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,13 +0,0 @@
namespace TCM_API
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@ -1,4 +1,7 @@
{ {
"AppSettings": {
"Secret": "TCM token test jwt lamiter local 1234567"
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More