Projekt

Obecné

Profil

« Předchozí | Další » 

Revize c349bab3

Přidáno uživatelem Lukáš Vlček před asi 2 roky(ů)

Backend - JWT Authentication done

Zobrazit rozdíly:

.gitignore
479 479

  
480 480
# Android studio 3.1+ serialized cache file
481 481
.idea/caches/build_file_checksums.ser
482
.idea
Backend/Backend/Authentication/AuthorizationAttribute.cs
1
using Core.Entities;
2
using Microsoft.AspNetCore.Authorization;
3
using Microsoft.AspNetCore.Mvc;
4
using Microsoft.AspNetCore.Mvc.Filters;
5

  
6
namespace RestAPI.Authentication;
7

  
8
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
9
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
10
{
11
    public void OnAuthorization(AuthorizationFilterContext context)
12
    {
13
        // skip authorization if action is decorated with [AllowAnonymous] attribute
14
        var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
15
        if (allowAnonymous)
16
            return;
17

  
18
        // authorization
19
        var user = (User?) context.HttpContext.Items["User"];
20
        if (user == null)
21
            context.Result = new JsonResult(new {message = "Unauthorized"})
22
                {StatusCode = StatusCodes.Status401Unauthorized};
23
    }
24
}
Backend/Backend/Controllers/AuthController.cs
1
using System.Net;
2
using System.Security.Authentication;
3
using Core.Entities;
4
using Core.Services;
5
using Microsoft.AspNetCore.Authorization;
6
using Microsoft.AspNetCore.Mvc;
7
using Models.Authentication;
8
using RestAPI.Controllers.Common;
9

  
10
namespace RestAPI.Controllers;
11

  
12
public class AuthController : CommonControllerBase
13
{
14
    private readonly IAuthService authService;
15

  
16
    public AuthController(IAuthService authService)
17
    {
18
        this.authService = authService;
19
    }
20

  
21

  
22
    [AllowAnonymous]
23
    [HttpPost("/auth/login")]
24
    [ProducesResponseType((int) HttpStatusCode.OK, Type = typeof(LoginResponse))]
25
    [ProducesResponseType((int) HttpStatusCode.Forbidden)]
26
    public ActionResult<LoginResponse> Login([FromBody] LoginRequest loginData)
27
    {
28
        try
29
        {
30
            var loginResponse = authService.Login(loginData.Username, loginData.Password);
31
            if (loginResponse == null)
32
            {
33
                return Forbid();
34
            }
35
            else
36
            {
37
                return Ok(loginResponse);
38
            }
39
        }
40
        catch (InvalidCredentialException)
41
        {
42
            return Forbid();
43
        }
44
        catch (Exception)
45
        {
46
            return Problem();
47
        }
48
    }
49

  
50
    [HttpGet("/auth/test")]
51
    [ProducesResponseType((int) HttpStatusCode.OK, Type = typeof(User))]
52
    [ProducesResponseType((int) HttpStatusCode.Forbidden)]
53
    public ActionResult<User> TestLogged([FromServices] User loggedUser)
54
    {
55
        return loggedUser;
56
    }
57
}
Backend/Backend/Controllers/Common/CommonControllerBase.cs
1
using Microsoft.AspNetCore.Mvc;
2
using RestAPI.Authentication;
3

  
4
namespace RestAPI.Controllers.Common;
5

  
6
[ApiController]
7
[Authorize]
8
public abstract class CommonControllerBase : ControllerBase
9
{
10
}
Backend/Backend/Middleware/JwtMiddleware.cs
1
using Core.Authentication;
2
using Core.Services;
3
using Microsoft.Extensions.Options;
4

  
5
namespace RestAPI.Middleware;
6

  
7
public class JwtMiddleware
8
{
9
    private readonly RequestDelegate _next;
10
    private readonly JwtConfig _jwtConfig;
11

  
12
    public JwtMiddleware(RequestDelegate next, IOptions<JwtConfig> jwtConfigOptions)
13
    {
14
        _next = next;
15
        _jwtConfig = jwtConfigOptions.Value;
16
    }
17

  
18
    public async Task Invoke(HttpContext context, IUserService userService, IJwtUtils jwtUtils)
19
    {
20
        var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
21
        var userId = jwtUtils.ValidateJwtToken(token);
22
        if (userId != null)
23
        {
24
            // attach user to context on successful jwt validation
25
            context.Items["User"] = userService.GetUserById(userId.Value);
26
        }
27

  
28
        await _next(context);
29
    }
30
}
Backend/Backend/Program.cs
1 1
using System.Text.Json.Serialization;
2
using Core.Authentication;
2 3
using Core.Contexts;
4
using Core.Entities;
3 5
using Core.Seeding;
4 6
using Core.Services;
7
using Microsoft.AspNetCore.Authentication.JwtBearer;
8
using Microsoft.EntityFrameworkCore;
5 9
using Microsoft.OpenApi.Models;
10
using RestAPI.Middleware;
6 11
using Serilog;
7 12

  
8

  
9 13
var builder = WebApplication.CreateBuilder(args);
10 14

  
11 15
// Logging
......
28 32
        new OpenApiInfo {Title = "AnnotationTool", Description = "KIV/ASWI ZČU Plzeň, 2022", Version = "0.1.1"});
29 33
});
30 34

  
35
// JWT authentication
36
var tokenValidationParams = JwtUtils.GetTokenValidationParameters(builder.Configuration);
37
builder.Services.AddSingleton(tokenValidationParams);
38
builder.Services.Configure<JwtConfig>(builder.Configuration.GetSection("JwtConfig"));
39
builder.Services.AddAuthentication(options =>
40
    {
41
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
42
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
43
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
44
    })
45
    .AddJwtBearer(jwt =>
46
        {
47
            jwt.SaveToken = true;
48
            jwt.TokenValidationParameters = tokenValidationParams;
49
        }
50
    );
51

  
52
builder.Services.AddHttpContextAccessor();
53
builder.Services.AddScoped<User>(provider =>
54
    ((User?) provider.GetRequiredService<IHttpContextAccessor>().HttpContext?.Items["User"]));
55

  
31 56
// Database
32 57
builder.Services.AddDbContext<DatabaseContext>();
33 58

  
......
57 82
    .AllowAnyMethod()
58 83
    .AllowCredentials());
59 84

  
85
// JWT Authentication
86
app.UseMiddleware<JwtMiddleware>();
87

  
88
/*
89
app.UseAuthentication();
60 90
app.UseAuthorization();
91
*/
92

  
61 93

  
62 94
app.MapControllers();
63 95

  
......
87 119
        // In production we seed administrator 
88 120
        Seeder.SeedProduction(context);
89 121
    }
90

  
91 122
}
92 123

  
93
app.Run();
124
app.Run();
Backend/Backend/RestAPI.csproj
9 9
  </PropertyGroup>
10 10

  
11 11
  <ItemGroup>
12
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.3" />
12 13
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
13 14
      <PrivateAssets>all</PrivateAssets>
14 15
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
......
22 23

  
23 24
  <ItemGroup>
24 25
    <ProjectReference Include="..\Core\Core.csproj" />
26
    <ProjectReference Include="..\Models\Models.csproj" />
25 27
  </ItemGroup>
26 28

  
27 29
  <ItemGroup>
Backend/Backend/appsettings.json
6 6
    }
7 7
  },
8 8
  "AllowedHosts": "*",
9
  "ConnectionString": "Host=localhost:5432;Database=dbo;Username=myuser;Password=password"
9
  "ConnectionString": "Host=localhost:5432;Database=dbo;Username=myuser;Password=password",
10
  "JwtConfig": {
11
    "Secret": "CJgvc9BtwJjnCExzKLB2ndtxERW3YMhqMMPNrBZuTJaskPqRVfZQHRUnbRHH9Ekp5zsaTSeM73vQSKvq48w2u8jTnKUn7Uhk2zBeD9wLz48ssKMppNC9HDTXSk6RWP5PtXfCayJ67mbUWF6aknnteLg4sDKzqYeNYjyw8Jk6",
12
    "Issuer": "0x00.server"
13
  }
10 14
}
Backend/Core/Authentication/JwtConfig.cs
1
namespace Core.Authentication;
2

  
3
public class JwtConfig
4
{
5
    public string Secret { get; set; }
6
    public string Issuer { get; set; }
7
}
8

  
Backend/Core/Authentication/JwtUtils.cs
1
using System.IdentityModel.Tokens.Jwt;
2
using System.Security.Claims;
3
using System.Text;
4
using Core.Entities;
5
using Microsoft.Extensions.Configuration;
6
using Microsoft.Extensions.Options;
7
using Microsoft.IdentityModel.Tokens;
8

  
9
namespace Core.Authentication;
10

  
11
public interface IJwtUtils
12
{
13
    public string GenerateJwtToken(User user, DateTime? expiration = null);
14
    public Guid? ValidateJwtToken(string token);
15
}
16

  
17
public class JwtUtils : IJwtUtils
18
{
19
    private const int EXPIRATION_SECONDS = 8 * 60 * 60; // 8 hod
20

  
21
    private readonly JwtConfig _jwtConfig;
22
    private readonly TokenValidationParameters _tokenValidationParameters;
23

  
24
    public JwtUtils(IOptions<JwtConfig> jwtConfig, TokenValidationParameters tokenValidationParameters)
25
    {
26
        _jwtConfig = jwtConfig.Value;
27
        _tokenValidationParameters = tokenValidationParameters;
28
    }
29

  
30
    public static TokenValidationParameters GetTokenValidationParameters(ConfigurationManager configuration)
31
    {
32
        return new TokenValidationParameters
33
        {
34
            RequireExpirationTime = true,
35
            ValidateIssuerSigningKey = false,
36
            IssuerSigningKey =
37
                new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtConfig:Secret"])),
38
            ValidateIssuer = false,
39
            ValidIssuer = configuration["JwtConfig:Issuer"],
40
            ValidateAudience = false,
41
            ValidateLifetime = true
42
        };
43
    }
44

  
45

  
46
    public string GenerateJwtToken(User user, DateTime? expiration = null)
47
    {
48
        expiration ??= DateTime.Now.AddHours(10);
49

  
50
        var tokenHandler = new JwtSecurityTokenHandler();
51
        var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret);
52
        var tokenDescriptor = new SecurityTokenDescriptor
53
        {
54
            Subject = new ClaimsIdentity(new[] {new Claim("id", user.Id.ToString())}),
55
            Expires = expiration,
56
            SigningCredentials =
57
                new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
58
        };
59
        var token = tokenHandler.CreateToken(tokenDescriptor);
60
        return tokenHandler.WriteToken(token);
61
    }
62

  
63
    public Guid? ValidateJwtToken(string? token)
64
    {
65
        if (token == null)
66
            return null;
67

  
68
        var tokenHandler = new JwtSecurityTokenHandler();
69
        var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret);
70
        try
71
        {
72
            tokenHandler.ValidateToken(token, _tokenValidationParameters, out SecurityToken validatedToken);
73

  
74
            var jwtToken = (JwtSecurityToken) validatedToken;
75
            var userId = Guid.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
76

  
77
            // return user id from JWT token if validation successful
78
            return userId;
79
        }
80
        catch
81
        {
82
            // return null if validation fails
83
            return null;
84
        }
85
    }
86
}
Backend/Core/Core.csproj
14 14
      <PrivateAssets>all</PrivateAssets>
15 15
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16 16
    </PackageReference>
17
    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.10.0" />
17 18
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" />
18 19
    <PackageReference Include="Serilog" Version="2.10.0" />
19 20
    <PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
20 21
    <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
22
    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.10.0" />
23
  </ItemGroup>
24

  
25
  <ItemGroup>
26
    <ProjectReference Include="..\Models\Models.csproj" />
21 27
  </ItemGroup>
22 28

  
23 29
</Project>
Backend/Core/Services/AuthService/AuthService.cs
1
using Core.Authentication;
2
using Core.Entities;
3
using Models.Authentication;
4

  
5
namespace Core.Services;
6

  
7
public class AuthService : IAuthService
8
{
9
    private readonly IUserService userService;
10
    private readonly IJwtUtils jwtUtils;
11

  
12
    public AuthService(IUserService userService, IJwtUtils jwtUtils)
13
    {
14
        this.userService = userService;
15
        this.jwtUtils = jwtUtils;
16
    }
17

  
18
    public LoginResponse? Login(string username, string password)
19
    {
20
        var user = userService.CheckUsernamePassword(username, password);
21

  
22
        if (user == null)
23
        {
24
            // user with given credentials not found
25
            return null;
26
        }
27

  
28
        var expiration = DateTime.Now.AddHours(12);
29
        var token = jwtUtils.GenerateJwtToken(user, expiration);
30

  
31
        var loginResult = new LoginResponse(true, token, expiration);
32

  
33
        return loginResult;
34
    }
35
}
Backend/Core/Services/AuthService/IAuthService.cs
1
using Core.Entities;
2
using Models.Authentication;
3

  
4
namespace Core.Services;
5

  
6
public interface IAuthService
7
{
8
    public LoginResponse? Login(string username, string password);
9
}
Backend/Core/Services/Registration.cs
1
using Microsoft.AspNetCore.Builder;
1
using Core.Authentication;
2
using Microsoft.AspNetCore.Builder;
2 3
using Microsoft.Extensions.DependencyInjection;
3
using System;
4
using System.Collections.Generic;
5
using System.Linq;
6
using System.Text;
7
using System.Threading.Tasks;
8 4

  
9 5
namespace Core.Services
10 6
{
......
13 9
        public static void RegisterServices(WebApplicationBuilder builder)
14 10
        {
15 11
            builder.Services.AddScoped<IUserService, UserServiceEF>();
12
            builder.Services.AddScoped<IAuthService, AuthService>();
13
            
14
            builder.Services.AddScoped<IJwtUtils, JwtUtils>();
16 15
        }
17 16
    }
18
}
17
}
Backend/Core/Services/UserService/IUserService.cs
12 12
    {
13 13
        public User? CreateUser(string username, string name, string surname, string password, ERole role);
14 14
        public User? GetUserByUsername(string username);
15
        public User? GetUserByGuid(Guid id);
15
        public User? GetUserById(Guid id);
16 16
        public User UpdateUser(User user, string? username = null, string? name = null, string? surname = null, ERole? role = null);
17 17
        public User ChangePassword(User user, string newPassword);
18 18
        public User? CheckUsernamePassword(string username, string password);
Backend/Core/Services/UserService/UserServiceEF.cs
44 44
                User u = _databaseContext.Users.First(u => u.Username == username);
45 45
                if (!BCrypt.Net.BCrypt.Verify(password, u.Password))
46 46
                {
47
                    _logger.Information($"Password for user {username} don't match.");
47
                    _logger.Information($"Password for user {username} doesn't match.");
48 48
                    return null;
49 49
                }
50 50
                return u;
......
93 93
            }
94 94
        }
95 95

  
96
        public User? GetUserByGuid(Guid id)
96
        public User? GetUserById(Guid id)
97 97
        {
98 98
            try
99 99
            {
Backend/Models/Authentication/LoginRequest.cs
1
namespace Models.Authentication;
2

  
3
public class LoginRequest
4
{
5
    public string Username { get; set; }
6
    public string Password { get; set; }
7
}   
Backend/Models/Authentication/LoginResponse.cs
1
namespace Models.Authentication;
2

  
3
public class LoginResponse
4
{
5
    public LoginResponse(bool ok, string token, DateTime expiration)
6
    {
7
        Ok = ok;
8
        Token = token;
9
        Expiration = expiration;
10
    }
11

  
12
    public LoginResponse()
13
    {
14
    }
15

  
16
    public bool Ok { get; set; }
17
    public string Token { get; set; }
18
    public DateTime Expiration { get; set; }
19
}
webapp/.idea/.gitignore
1
# Default ignored files
2
/shelf/
3
/workspace.xml
4
# Editor-based HTTP Client requests
5
/httpRequests/
webapp/.idea/codeStyles/Project.xml
1
<component name="ProjectCodeStyleConfiguration">
2
  <code_scheme name="Project" version="173">
3
    <HTMLCodeStyleSettings>
4
      <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
5
      <option name="HTML_QUOTE_STYLE" value="Single" />
6
      <option name="HTML_ENFORCE_QUOTES" value="true" />
7
    </HTMLCodeStyleSettings>
8
    <JSCodeStyleSettings version="0">
9
      <option name="FORCE_SEMICOLON_STYLE" value="true" />
10
      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
11
      <option name="USE_DOUBLE_QUOTES" value="false" />
12
      <option name="FORCE_QUOTE_STYlE" value="true" />
13
      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
14
      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
15
      <option name="SPACES_WITHIN_IMPORTS" value="true" />
16
    </JSCodeStyleSettings>
17
    <TypeScriptCodeStyleSettings version="0">
18
      <option name="FORCE_SEMICOLON_STYLE" value="true" />
19
      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
20
      <option name="USE_DOUBLE_QUOTES" value="false" />
21
      <option name="FORCE_QUOTE_STYlE" value="true" />
22
      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
23
      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
24
      <option name="SPACES_WITHIN_IMPORTS" value="true" />
25
    </TypeScriptCodeStyleSettings>
26
    <VueCodeStyleSettings>
27
      <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
28
      <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
29
    </VueCodeStyleSettings>
30
    <codeStyleSettings language="HTML">
31
      <option name="SOFT_MARGINS" value="90" />
32
      <indentOptions>
33
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
34
      </indentOptions>
35
    </codeStyleSettings>
36
    <codeStyleSettings language="JavaScript">
37
      <option name="SOFT_MARGINS" value="90" />
38
    </codeStyleSettings>
39
    <codeStyleSettings language="TypeScript">
40
      <option name="SOFT_MARGINS" value="90" />
41
    </codeStyleSettings>
42
    <codeStyleSettings language="Vue">
43
      <option name="SOFT_MARGINS" value="90" />
44
      <indentOptions>
45
        <option name="INDENT_SIZE" value="4" />
46
        <option name="TAB_SIZE" value="4" />
47
      </indentOptions>
48
    </codeStyleSettings>
49
  </code_scheme>
50
</component>
webapp/.idea/codeStyles/codeStyleConfig.xml
1
<component name="ProjectCodeStyleConfiguration">
2
  <state>
3
    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
4
  </state>
5
</component>
webapp/.idea/inspectionProfiles/Project_Default.xml
1
<component name="InspectionProjectProfileManager">
2
  <profile version="1.0">
3
    <option name="myName" value="Project Default" />
4
    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
  </profile>
6
</component>
webapp/.idea/modules.xml
1
<?xml version="1.0" encoding="UTF-8"?>
2
<project version="4">
3
  <component name="ProjectModuleManager">
4
    <modules>
5
      <module fileurl="file://$PROJECT_DIR$/.idea/webapp.iml" filepath="$PROJECT_DIR$/.idea/webapp.iml" />
6
    </modules>
7
  </component>
8
</project>
webapp/.idea/prettier.xml
1
<?xml version="1.0" encoding="UTF-8"?>
2
<project version="4">
3
  <component name="PrettierConfiguration">
4
    <option name="myRunOnSave" value="true" />
5
    <option name="myRunOnReformat" value="true" />
6
  </component>
7
</project>
webapp/.idea/vcs.xml
1
<?xml version="1.0" encoding="UTF-8"?>
2
<project version="4">
3
  <component name="VcsDirectoryMappings">
4
    <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
5
  </component>
6
</project>
webapp/.idea/webapp.iml
1
<?xml version="1.0" encoding="UTF-8"?>
2
<module type="WEB_MODULE" version="4">
3
  <component name="NewModuleRootManager">
4
    <content url="file://$MODULE_DIR$">
5
      <excludeFolder url="file://$MODULE_DIR$/temp" />
6
      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
7
      <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
    </content>
9
    <orderEntry type="inheritedJdk" />
10
    <orderEntry type="sourceFolder" forTests="false" />
11
  </component>
12
</module>

Také k dispozici: Unified diff