日常生活的交流与学习

首页 新随笔 联系 管理

dotnet-6-basic-authentication-api/Entities/User.cs

namespace WebApi.Entities;

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; }
}

dotnet-6-basic-authentication-api/Services/UserService.cs

namespace WebApi.Services;

using WebApi.Entities;

public interface IUserService
{
    Task<User> Authenticate(string username, string password);
    Task<IEnumerable<User>> GetAll();
}

public class UserService : IUserService
{
    // users hardcoded for simplicity, store in a db with hashed passwords in production applications
    private List<User> _users = new List<User>
    {
        new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" }
    };

    public async Task<User> Authenticate(string username, string password)
    {
        // wrapped in "await Task.Run" to mimic fetching user from a db
        var user = await Task.Run(() => _users.SingleOrDefault(x => x.Username == username && x.Password == password));

        // on auth fail: null is returned because user is not found
        // on auth success: user object is returned
        return user;
    }

    public async Task<IEnumerable<User>> GetAll()
    {
        // wrapped in "await Task.Run" to mimic fetching users from a db
        return await Task.Run(() => _users);
    }
}

dotnet-6-basic-authentication-api/Controllers/UsersController.cs

namespace WebApi.Controllers;

using Microsoft.AspNetCore.Mvc;
using WebApi.Authorization;
using WebApi.Models;
using WebApi.Services;

[Authorize]
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
    private IUserService _userService;

    public UsersController(IUserService userService)
    {
        _userService = userService;
    }

    [AllowAnonymous]
    [HttpPost("authenticate")]
    public async Task<IActionResult> Authenticate([FromBody]AuthenticateModel model)
    {
        var user = await _userService.Authenticate(model.Username, model.Password);

        if (user == null)
            return BadRequest(new { message = "Username or password is incorrect" });

        return Ok(user);
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var users = await _userService.GetAll();
        return Ok(users);
    }
}



dotnet-6-basic-authentication-api/Controllers/CustomersController.cs

namespace WebApi.Controllers;

using Microsoft.AspNetCore.Mvc;
using WebApi.Authorization;
using WebApi.Models;
using WebApi.Services;

[ApiController]
[Route("[controller]")]
public class CustomersController : ControllerBase
{
    private IUserService _userService;

    public CustomersController(IUserService userService)
    {
        _userService = userService;
    }


    [HttpGet]
    public IActionResult Get()
    {
        return Ok("this is customer controller");
    }
}

dotnet-6-basic-authentication-api/Authorization/AllowAnonymousAttribute.cs

namespace WebApi.Authorization;

[AttributeUsage(AttributeTargets.Method)]
public class AllowAnonymousAttribute : Attribute
{ 

}

dotnet-6-basic-authentication-api/Authorization/AuthorizeAttribute.cs

namespace WebApi.Authorization;

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApi.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;

        var user = (User)context.HttpContext.Items["User"];
        if (user == null)
        {
            // not logged in - return 401 unauthorized
            context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };

            // set 'WWW-Authenticate' header to trigger login popup in browsers
            context.HttpContext.Response.Headers["WWW-Authenticate"] = "Basic realm=\"\", charset=\"UTF-8\"";
        }
    }
}

dotnet-6-basic-authentication-api/Authorization/BasicAuthMiddleware.cs

namespace WebApi.Authorization;

using System.Net.Http.Headers;
using System.Text;
using WebApi.Services;

public class BasicAuthMiddleware  
{
    private readonly RequestDelegate _next;

    public BasicAuthMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context, IUserService userService)
    {
        try
        {
            var authHeader = AuthenticationHeaderValue.Parse(context.Request.Headers["Authorization"]);
            var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
            var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':', 2);
            var username = credentials[0];
            var password = credentials[1];

            // authenticate credentials with user service and attach user to http context
            context.Items["User"] = await userService.Authenticate(username, password);
        }
        catch
        {
            // do nothing if invalid auth header
            // user is not attached to context so request won't have access to secure routes
        }

        await _next(context);
    }
}

dotnet-6-basic-authentication-api/Models/AuthenticateModel.cs

namespace WebApi.Models;

using System.ComponentModel.DataAnnotations;

public class AuthenticateModel
{
    [Required]
    public string Username { get; set; }

    [Required]
    public string Password { get; set; }
}

dotnet-6-basic-authentication-api/Middlewares/LoggingMiddleware.cs

public class LoggingMiddleware  
{
    private readonly RequestDelegate _next;

    public LoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }


    public async Task Invoke(HttpContext context)
    {
        Console.WriteLine($"Request path: {context.Request.Path}");
        Console.WriteLine($"Request time: {DateTime.Now}");

        await _next(context);
    }
}

dotnet-6-basic-authentication-api/Program.cs

using WebApi.Authorization;
using WebApi.Services;

var builder = WebApplication.CreateBuilder(args);

// add services to DI container
{
    var services = builder.Services;
    services.AddCors();
    services.AddControllers();
    // configure DI for application services
    services.AddScoped<IUserService, UserService>();
}


var app = builder.Build();


// configure HTTP request pipeline
{
    // global cors policy
    app.UseCors(x => x
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader());
    // custom basic auth middleware
    app
    .UseMiddleware<BasicAuthMiddleware>()
    .UseMiddleware<LoggingMiddleware>();

    app.MapControllers();
}

app.Run("http://localhost:4000");


dotnet-6-basic-authentication-api/global.json

{
  "sdk": {
    "version": "6.0.412"
  }
}

dotnet-6-basic-authentication-api/.gitignore

# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules
jspm_packages
typings

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

# .NET compiled files
bin
obj

dotnet-6-basic-authentication-api/dotnet-6-basic-authentication-api.sln


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "WebApi.csproj", "{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
	GlobalSection(ExtensibilityGlobals) = postSolution
		SolutionGuid = {517F54C7-E702-4E85-B0E6-FD87AE3FAA41}
	EndGlobalSection
EndGlobal

dotnet-6-basic-authentication-api/appsettings.json

{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    }
}

dotnet-6-basic-authentication-api/WebApi.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>
</Project>

dotnet-6-basic-authentication-api/omnisharp.json

{
    "msbuild": {
        "useBundledOnly": true
    }
}
posted on 2024-04-27 21:10  lazycookie  阅读(18)  评论(0编辑  收藏  举报