added authentication
This commit is contained in:
parent
5db0933c89
commit
54d19b3567
|
@ -4,6 +4,12 @@
|
|||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
|
|
|
@ -3,11 +3,13 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ValuesController : ControllerBase
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using API.Models;
|
||||
using api.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
|
@ -6,8 +6,10 @@ namespace API.Data
|
|||
public class DataContext : DbContext
|
||||
{
|
||||
public DataContext(DbContextOptions<DataContext> options):base(options) { }
|
||||
|
||||
|
||||
|
||||
public DbSet<Value> Values{get;set;}
|
||||
|
||||
public DbSet<User> Users { get; set;}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using API.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
@ -15,7 +16,23 @@ namespace API.Migrations
|
|||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
|
||||
|
||||
modelBuilder.Entity("API.Models.Value", b =>
|
||||
modelBuilder.Entity("api.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("PasswordHash");
|
||||
|
||||
b.Property<byte[]>("PasswordSalt");
|
||||
|
||||
b.Property<string>("Username");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("api.Models.Value", b =>
|
||||
{
|
||||
b.Property<int>("id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace API.Models
|
||||
namespace api.Models
|
||||
{
|
||||
public class Value
|
||||
{
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using api.Data;
|
||||
using API.Data;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.HttpsPolicy;
|
||||
|
@ -12,6 +15,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace API
|
||||
{
|
||||
|
@ -33,6 +37,24 @@ namespace API
|
|||
services.AddDbContext<DataContext>(x=>x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
|
||||
|
||||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
|
||||
|
||||
//cors support
|
||||
services.AddCors();
|
||||
|
||||
//cria uma instancia para cada request do cliente (mantem instancia entre CORS)
|
||||
services.AddScoped<IAuthRepository, AuthRepository>();
|
||||
|
||||
//autenticação para o token
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options=> {
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
|
||||
ValidateIssuer = false,
|
||||
ValidateAudience= false,
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
@ -48,7 +70,11 @@ namespace API
|
|||
}
|
||||
|
||||
// app.UseHttpsRedirection();
|
||||
app.UseMvc();
|
||||
|
||||
//cores supporte
|
||||
app.UseCors(x=>x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
|
||||
app.UseAuthentication();
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
BIN
API/api.db
BIN
API/api.db
Binary file not shown.
|
@ -1,7 +1,12 @@
|
|||
{
|
||||
"AppSettings":
|
||||
{
|
||||
"Token":"NbTSjGOaBMfweUyCvINv23Tt9jHhiEz2MBcYrYPhd24xWsztIV3bgDGOsWfRJFb8"
|
||||
},
|
||||
"ConnectionStrings":
|
||||
{
|
||||
"DefaultConnection":"Data Source= api.db"
|
||||
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 1,
|
||||
"dgSpecHash": "pWpXdShPoDL3Y6cQrbcQrVzH5rp5FbLY4oDKQxvRGDuuPI75KZj0JxPxF8tay1NT3PBmWY+++45R5z1YQKm7mA==",
|
||||
"dgSpecHash": "C2en1YFh4xIIX8rPmhWxyYZ5XptuwTimwDdsRRRxcbl+IKf3uf5g3+8FID5AKf1Woqqa0VFcMiB2OA/aHHfJCw==",
|
||||
"success": true
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">/Users/henrique/enei2019/API/obj/project.assets.json</ProjectAssetsFile>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">/Users/henrique/enei2019/api/obj/project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/Users/henrique/.nuget/packages/</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/Users/henrique/.nuget/packages/;/usr/local/share/dotnet/sdk/NuGetFallbackFolder</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
|
|
Binary file not shown.
|
@ -1 +1 @@
|
|||
bfbc916619c950582f70677b5e39995f477abab6
|
||||
b790b8f5e973558fd81867f820388b2751d038f2
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using api.Dtos;
|
||||
using api.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace api.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly Data.IAuthRepository repo;
|
||||
private readonly IConfiguration config;
|
||||
public AuthController(Data.IAuthRepository repo, IConfiguration config)
|
||||
{
|
||||
this.config = config;
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
public async Task<IActionResult> Register(UserForRegisterDto UserForRegisterDto)
|
||||
{
|
||||
//validar a request
|
||||
UserForRegisterDto.Username = UserForRegisterDto.Username.ToLower();
|
||||
|
||||
if (await repo.UserExists(UserForRegisterDto.Username))
|
||||
return BadRequest("username already exists");
|
||||
|
||||
var userToCreate = new User
|
||||
{
|
||||
Username = UserForRegisterDto.Username
|
||||
};
|
||||
|
||||
var createUser = await repo.Register(userToCreate, UserForRegisterDto.Password);
|
||||
|
||||
return StatusCode(201);
|
||||
|
||||
}
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login(UserForLoginDto UserForLoginDto)
|
||||
{
|
||||
//verifica se o utilizador existe na base de dados e se consegue fazer login
|
||||
var userFromRepo = await repo.Login(UserForLoginDto.Username.ToLower(), UserForLoginDto.Password);
|
||||
|
||||
//Se não conseguir
|
||||
if (userFromRepo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
//o token vai ter 2 claims, uma vai ser o id e outra vai ser o nome
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, userFromRepo.Id.ToString()),
|
||||
new Claim(ClaimTypes.Name, userFromRepo.Username)
|
||||
};
|
||||
|
||||
//obtem a key na app settings
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetSection("AppSettings:Token").Value));
|
||||
|
||||
//faz hashing da key na app settings
|
||||
var creds= new SigningCredentials(key,SecurityAlgorithms.HmacSha512Signature);
|
||||
|
||||
//criamos um token
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
//data de expiração (atual + 24 horas)
|
||||
Expires = DateTime.Now.AddDays(1),
|
||||
|
||||
//passa as signing credentials definidas em cima
|
||||
SigningCredentials = creds
|
||||
};
|
||||
|
||||
//criamos um token handler
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
|
||||
//em seguida criamos o token
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
|
||||
//devolvemos ao cliente
|
||||
return Ok(new {
|
||||
token= tokenHandler.WriteToken(token)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using api.Models;
|
||||
using API.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace api.Data
|
||||
{
|
||||
public class AuthRepository : IAuthRepository
|
||||
{
|
||||
public AuthRepository(DataContext context)
|
||||
{
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public DataContext Context { get; }
|
||||
|
||||
public async Task<User> Login(string username, string password)
|
||||
{
|
||||
|
||||
var user =await Context.Users.FirstOrDefaultAsync(x=> x.Username== username);
|
||||
|
||||
if(user==null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!VerifyPasswordHash(password,user.PasswordHash,user.PasswordSalt))
|
||||
|
||||
return null;
|
||||
|
||||
|
||||
return user;
|
||||
|
||||
}
|
||||
|
||||
private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
|
||||
{
|
||||
using(var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
|
||||
{
|
||||
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
|
||||
|
||||
for(int i=0; i< computedHash.Length; i++)
|
||||
{
|
||||
if(computedHash[i]!= passwordHash[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
|
||||
{
|
||||
using(var hmac = new System.Security.Cryptography.HMACSHA512())
|
||||
{
|
||||
passwordSalt = hmac.Key;
|
||||
passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
|
||||
}
|
||||
|
||||
}
|
||||
public async Task<User> Register(User user, string Password)
|
||||
{
|
||||
byte[] passwordHash, passwordSalt;
|
||||
|
||||
CreatePasswordHash(Password,out passwordHash, out passwordSalt);
|
||||
|
||||
user.PasswordHash=passwordHash;
|
||||
|
||||
user.PasswordSalt=passwordSalt;
|
||||
|
||||
await Context.Users.AddAsync(user);
|
||||
|
||||
await Context.SaveChangesAsync();
|
||||
|
||||
return user;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> UserExists(string username)
|
||||
{
|
||||
if(await Context.Users.AnyAsync(x=>x.Username== username))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using System.Threading.Tasks;
|
||||
using api.Models;
|
||||
|
||||
namespace api.Data
|
||||
{
|
||||
public interface IAuthRepository
|
||||
{
|
||||
|
||||
Task<User> Register(User user, string Password);
|
||||
|
||||
Task<User> Login(string username, string password);
|
||||
|
||||
Task<bool> UserExists(string username);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace api.Dtos
|
||||
{
|
||||
public class UserForLoginDto
|
||||
{
|
||||
public string Username{get;set;}
|
||||
public string Password{get;set;}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace api.Dtos
|
||||
{
|
||||
public class UserForRegisterDto
|
||||
{
|
||||
[Required]
|
||||
public string Username {get;set;}
|
||||
|
||||
[Required]
|
||||
[StringLength(8,MinimumLength=4,ErrorMessage="You must specify password between 4 and 8 cars")]
|
||||
public string Password{get;set;}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using API.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace API.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20180817200459_AddedUserEntity")]
|
||||
partial class AddedUserEntity
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
|
||||
|
||||
modelBuilder.Entity("api.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("PasswordHash");
|
||||
|
||||
b.Property<byte[]>("PasswordSalt");
|
||||
|
||||
b.Property<string>("Username");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("api.Models.Value", b =>
|
||||
{
|
||||
b.Property<int>("id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.HasKey("id");
|
||||
|
||||
b.ToTable("Values");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace API.Migrations
|
||||
{
|
||||
public partial class AddedUserEntity : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Username = table.Column<string>(nullable: true),
|
||||
PasswordHash = table.Column<byte[]>(nullable: true),
|
||||
PasswordSalt = table.Column<byte[]>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace api.Models
|
||||
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public int Id{get;set;}
|
||||
public string Username {get;set;}
|
||||
public byte[] PasswordHash{get;set;}
|
||||
public byte[] PasswordSalt{get;set;}
|
||||
|
||||
}
|
||||
}
|
|
@ -3,18 +3,8 @@
|
|||
<h1>
|
||||
Welcome to {{title}}!
|
||||
</h1>
|
||||
<img width="300" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyNTAgMjUwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyNTAgMjUwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDojREQwMDMxO30NCgkuc3Qxe2ZpbGw6I0MzMDAyRjt9DQoJLnN0MntmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxnPg0KCTxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iMTI1LDMwIDEyNSwzMCAxMjUsMzAgMzEuOSw2My4yIDQ2LjEsMTg2LjMgMTI1LDIzMCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAJIi8+DQoJPHBvbHlnb24gY2xhc3M9InN0MSIgcG9pbnRzPSIxMjUsMzAgMTI1LDUyLjIgMTI1LDUyLjEgMTI1LDE1My40IDEyNSwxNTMuNCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAxMjUsMzAgCSIvPg0KCTxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMjUsNTIuMUw2Ni44LDE4Mi42aDBoMjEuN2gwbDExLjctMjkuMmg0OS40bDExLjcsMjkuMmgwaDIxLjdoMEwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMQ0KCQlMMTI1LDUyLjF6IE0xNDIsMTM1LjRIMTA4bDE3LTQwLjlMMTQyLDEzNS40eiIvPg0KPC9nPg0KPC9zdmc+DQo=">
|
||||
</div>
|
||||
<h2>Here are some links to help you start: </h2>
|
||||
<ul>
|
||||
<li>
|
||||
<h2><a target="_blank" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
|
||||
</li>
|
||||
<li>
|
||||
<h2><a target="_blank" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
|
||||
</li>
|
||||
<li>
|
||||
<h2><a target="_blank" href="https://blog.angular.io//">Angular blog</a></h2>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<app-value></app-value>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<p>
|
||||
value works!
|
||||
<p *ngFor="let value of values" >
|
||||
|
||||
{{value.id}}/{{value.name}}
|
||||
</p>
|
||||
|
|
|
@ -16,6 +16,10 @@ export class ValueComponent implements OnInit {
|
|||
}
|
||||
|
||||
getValues() {
|
||||
this.values = this.http.get('localhost:5000/api/values');
|
||||
this.http.get('http://localhost:5000/api/values').subscribe(response => {
|
||||
this.values = response;
|
||||
}, error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue