LoginSignup
0
1

More than 3 years have passed since last update.

つくるオーオース MVC編

Last updated at Posted at 2020-03-22

はじめに

この記事では.NET Core 3.1をつかってオーオースを学習する方法を書いてみます。共通関数を追加しました。(2020/4/21)

実行環境

下記バージョンで動作確認しています。
- MacOS
- .NET Core 3.1

% dotnet --version
3.1.101

学習方針

コマンドプロンプトから実行する事で、Windows、Linuxにおいてもそのままできると思います。

% mkdir myop
% cd myop
% dotnet new mvc --auth individual

必要なツールをインストールします。

% dotnet tool install --global dotnet-ef
% dotnet tool install --global dotnet-aspnet-codegenerator
% dotnet tool list --global

必要なパッケージをインストールします。

% dotnet add package Microsoft.EntityFrameworkCore.Sqlite
% dotnet add package Microsoft.EntityFrameworkCore.Design
% dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
% dotnet add package Microsoft.EntityFrameworkCore.SqlServer
% dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
% dotnet add package Microsoft.AspNetCore.Identity.UI
% dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
% dotnet add package Microsoft.Extensions.Options

Clients、Tokens、Codesモデルを作成します。

Models/Tables.cs
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace myop.Models
{
//  public class ApplicationDbContext : IdentityDbContext
//  {
//        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) {}
  public class myopContext : DbContext
  {
        public DbSet<Client> Clients { get; set; }
        public DbSet<Token> Tokens { get; set; }
        public DbSet<Code> Codes { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder options)
            => options.UseSqlite("Data Source=app.db");
  }

  public class Client
  {
    [Key]
    [DisplayName("client_id")]
    public string ClientId { get; set; }

    [DisplayName("client_secret")]
    public string ClientSecret { get; set; }

    [DisplayName("access_type")]
    public string AccessType { get; set; }

    [DisplayName("redirect_uris")]
    public string RedirectUris { get; set; }

    [DisplayName("grant_types")]
    public string GrantTypes { get; set; }

    [DisplayName("allowed_scope")]
    public string AllowedScope { get; set; }

    [DisplayName("client_name")]
    public string ClientName { get; set; }

    [DisplayName("auth_method")]
    public string AuthMethod { get; set; }

    [DisplayName("iat")]
    public DateTime Iat { get; set; }
  }

  public class Token
  {
    [Key]
    [DisplayName("user_id")]
    public string UserId { get; set; }

    [DisplayName("access_token")]
    public string AccessToken { get; set; }

    [DisplayName("client_id")]
    public string ClientId { get; set; }

    [DisplayName("refresh_token")]
    public string RefreshToken { get; set; }

    [DisplayName("scope")]
    public string Scope { get; set; }

    [DisplayName("iat")]
    public DateTime Iat { get; set; }
  }

  public class Code
  {
    [Key]
    [DisplayName("code")]
    public string CodeId { get; set; }

    [DisplayName("user_id")]
    public string UserId { get; set; }

    [DisplayName("client_id")]
    public string ClientId { get; set; }

    [DisplayName("nonce")]
    public string Nonce { get; set; }

    [DisplayName("iat")]
    public DateTime Iat { get; set; }
  }
}

モデルからデータベースを生成します。今回データベースにはSQLiteを使います。

% dotnet ef migrations add InitialCreate --context myopContext
% dotnet ef database update --context myopContext

テスト用なのでポート番号は5000のみで良いと思います。

Properties/launchSetting.json
{
  "iisSettings": {
    "windowsAuthentication": false, 
    "anonymousAuthentication": true, 
    "iisExpress": {
      "applicationUrl": "http://localhost:38239",
      "sslPort": 44320
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "myop": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

スキャフォールドでデータベースを確認してみます。

% dotnet aspnet-codegenerator controller -name ClientsController -m Client -dc myopContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries

% dotnet aspnet-codegenerator controller -name TokensController -m Token -dc myopContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries

% dotnet aspnet-codegenerator controller -name CodesController -m Code -dc myopContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries

ソースコードを一部修正して実行します。これでClients、Tokens、CodesがMVCで登録できます。
http://localhost:5000/Clients/
http://localhost:5000/Tokens/
http://localhost:5000/Codes/

Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.EntityFrameworkCore;
using myop.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace myop
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlite(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddControllersWithViews().AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.IgnoreNullValues = true;
            });
           services.AddRazorPages();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });
        }
    }
}
Models/Tables.cs
using System;
using System.IO;
using System.ComponentModel;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.Security.Cryptography;
using System.IdentityModel.Tokens.Jwt;

namespace myop.Models
{
  public static class Util
  {
    public static bool ByteArraysEqual(byte[] a, byte[] b)
    {
      if (a == null && b == null)
      {
        return true;
      }
      if (a == null || b == null || a.Length != b.Length)
      {
        return false;
      }
      var areSame = true;
      for (var i = 0; i < a.Length; i++)
      {
        areSame &= (a[i] == b[i]);
      }
      return areSame;
    }

    public static bool PasswordEqual(string PasswordHash, string Password)
    {
      byte[] buffer4;
      byte[] src = Convert.FromBase64String(PasswordHash);
      byte[] dst = new byte[0x10];
      Buffer.BlockCopy(src, 0x0D, dst, 0, 0x10);
      byte[] buffer3 = new byte[0x20];
      Buffer.BlockCopy(src, 0x1D, buffer3, 0, 0x20);
      using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(Password, dst, 0x2710, HashAlgorithmName.SHA256))
      {
        buffer4 = bytes.GetBytes(0x20);
      }
      return ByteArraysEqual(buffer3, buffer4);
    }

    public static string GetAtHash(string random)
    {
      SHA256Managed hashstring = new SHA256Managed();
      byte[] bytes = Encoding.Default.GetBytes(random);
      byte[] hash = hashstring.ComputeHash(bytes);
      Byte[] sixteen_bytes = new Byte[16];
      Array.Copy(hash, sixteen_bytes, 16);
      return Convert.ToBase64String(sixteen_bytes).Trim('=');
    }

    public static string GetIdToken(Claim[] claims, string client_id)
    {
      var pemStr = System.IO.File.ReadAllText(@"./private.pem");
      var base64 = pemStr
      .Replace("-----BEGIN RSA PRIVATE KEY-----", string.Empty)
      .Replace("-----END RSA PRIVATE KEY-----", string.Empty)
      .Replace("\r\n", string.Empty)
      .Replace("\n", string.Empty);
      var der = Convert.FromBase64String(base64);
      var rsa = RSA.Create();
      rsa.ImportRSAPrivateKey(der, out _);
      var key = new RsaSecurityKey(rsa);
      key.KeyId = "testkey";
      var creds = new SigningCredentials(key, SecurityAlgorithms.RsaSha256);
      var jwtHeader = new JwtHeader(creds);
      var jwtPayload = new JwtPayload(
      issuer: "http://localhost:5000/op",
        audience: client_id,
        claims: claims,
        notBefore: DateTime.Now,
        expires: DateTime.Now.AddMinutes(600),
        issuedAt: DateTime.Now
      );
      var jwt = new JwtSecurityToken(jwtHeader, jwtPayload);
      return new JwtSecurityTokenHandler().WriteToken(jwt);
    }
  }

  public class ApplicationDbContext : IdentityDbContext
  {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) {}
//  public class myopContext : DbContext
//  {
        public DbSet<Client> Clients { get; set; }
        public DbSet<Token> Tokens { get; set; }
        public DbSet<Code> Codes { get; set; }
//        protected override void OnConfiguring(DbContextOptionsBuilder options)
//            => options.UseSqlite("Data Source=app.db");
  }
  public class Client
  {
    [Key]
    [DisplayName("client_id")]
    public string ClientId { get; set; }

    [DisplayName("client_secret")]
    public string ClientSecret { get; set; }

    [DisplayName("access_type")]
    public string AccessType { get; set; }

    [DisplayName("redirect_uris")]
    public string RedirectUris { get; set; }

    [DisplayName("grant_types")]
    public string GrantTypes { get; set; }

    [DisplayName("allowed_scope")]
    public string AllowedScope { get; set; }

    [DisplayName("client_name")]
    public string ClientName { get; set; }

    [DisplayName("auth_method")]
    public string AuthMethod { get; set; }

    [DisplayName("iat")]
    public DateTime Iat { get; set; }
  }

  public class Token
  {
    [Key]
    [DisplayName("user_id")]
    public string UserId { get; set; }

    [DisplayName("access_token")]
    public string AccessToken { get; set; }

    [DisplayName("client_id")]
    public string ClientId { get; set; }

    [DisplayName("refresh_token")]
    public string RefreshToken { get; set; }

    [DisplayName("scope")]
    public string Scope { get; set; }

    [DisplayName("iat")]
    public DateTime Iat { get; set; }
  }

  public class Code
  {
    [Key]
    [DisplayName("code")]
    public string CodeId { get; set; }

    [DisplayName("user_id")]
    public string UserId { get; set; }

    [DisplayName("client_id")]
    public string ClientId { get; set; }

    [DisplayName("nonce")]
    public string Nonce { get; set; }

    [DisplayName("iat")]
    public DateTime Iat { get; set; }
  }
}

自動生成されたData配下を削除し統合します。

Migrations/myopContextModelSnapshot.cs
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using myop.Models;

namespace myop.Migrations
{
    [DbContext(typeof(ApplicationDbContext))]
    partial class myopContextModelSnapshot : ModelSnapshot
    {
        protected override void BuildModel(ModelBuilder modelBuilder)
        {
#pragma warning disable 612, 618
            modelBuilder
                .HasAnnotation("ProductVersion", "3.1.3");

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
                {
                    b.Property<string>("Id")
                        .HasColumnType("TEXT");

                    b.Property<string>("ConcurrencyStamp")
                        .IsConcurrencyToken()
                        .HasColumnType("TEXT");

                    b.Property<string>("Name")
                        .HasColumnType("TEXT")
                        .HasMaxLength(256);

                    b.Property<string>("NormalizedName")
                        .HasColumnType("TEXT")
                        .HasMaxLength(256);

                    b.HasKey("Id");

                    b.HasIndex("NormalizedName")
                        .IsUnique()
                        .HasName("RoleNameIndex");

                    b.ToTable("AspNetRoles");
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
                {
                    b.Property<int>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("INTEGER");

                    b.Property<string>("ClaimType")
                        .HasColumnType("TEXT");

                    b.Property<string>("ClaimValue")
                        .HasColumnType("TEXT");

                    b.Property<string>("RoleId")
                        .IsRequired()
                        .HasColumnType("TEXT");

                    b.HasKey("Id");

                    b.HasIndex("RoleId");

                    b.ToTable("AspNetRoleClaims");
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
                {
                    b.Property<string>("Id")
                        .HasColumnType("TEXT");

                    b.Property<int>("AccessFailedCount")
                        .HasColumnType("INTEGER");

                    b.Property<string>("ConcurrencyStamp")
                        .IsConcurrencyToken()
                        .HasColumnType("TEXT");

                    b.Property<string>("Email")
                        .HasColumnType("TEXT")
                        .HasMaxLength(256);

                    b.Property<bool>("EmailConfirmed")
                        .HasColumnType("INTEGER");

                    b.Property<bool>("LockoutEnabled")
                        .HasColumnType("INTEGER");

                    b.Property<DateTimeOffset?>("LockoutEnd")
                        .HasColumnType("TEXT");

                    b.Property<string>("NormalizedEmail")
                        .HasColumnType("TEXT")
                        .HasMaxLength(256);

                    b.Property<string>("NormalizedUserName")
                        .HasColumnType("TEXT")
                        .HasMaxLength(256);

                    b.Property<string>("PasswordHash")
                        .HasColumnType("TEXT");

                    b.Property<string>("PhoneNumber")
                        .HasColumnType("TEXT");

                    b.Property<bool>("PhoneNumberConfirmed")
                        .HasColumnType("INTEGER");

                    b.Property<string>("SecurityStamp")
                        .HasColumnType("TEXT");

                    b.Property<bool>("TwoFactorEnabled")
                        .HasColumnType("INTEGER");

                    b.Property<string>("UserName")
                        .HasColumnType("TEXT")
                        .HasMaxLength(256);

                    b.HasKey("Id");

                    b.HasIndex("NormalizedEmail")
                        .HasName("EmailIndex");

                    b.HasIndex("NormalizedUserName")
                        .IsUnique()
                        .HasName("UserNameIndex");

                    b.ToTable("AspNetUsers");
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
                {
                    b.Property<int>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("INTEGER");

                    b.Property<string>("ClaimType")
                        .HasColumnType("TEXT");

                    b.Property<string>("ClaimValue")
                        .HasColumnType("TEXT");

                    b.Property<string>("UserId")
                        .IsRequired()
                        .HasColumnType("TEXT");

                    b.HasKey("Id");

                    b.HasIndex("UserId");

                    b.ToTable("AspNetUserClaims");
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
                {
                    b.Property<string>("LoginProvider")
                        .HasColumnType("TEXT")
                        .HasMaxLength(128);

                    b.Property<string>("ProviderKey")
                        .HasColumnType("TEXT")
                        .HasMaxLength(128);

                    b.Property<string>("ProviderDisplayName")
                        .HasColumnType("TEXT");

                    b.Property<string>("UserId")
                        .IsRequired()
                        .HasColumnType("TEXT");

                    b.HasKey("LoginProvider", "ProviderKey");

                    b.HasIndex("UserId");

                    b.ToTable("AspNetUserLogins");
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
                {
                    b.Property<string>("UserId")
                        .HasColumnType("TEXT");

                    b.Property<string>("RoleId")
                        .HasColumnType("TEXT");

                    b.HasKey("UserId", "RoleId");

                    b.HasIndex("RoleId");

                    b.ToTable("AspNetUserRoles");
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
                {
                    b.Property<string>("UserId")
                        .HasColumnType("TEXT");

                    b.Property<string>("LoginProvider")
                        .HasColumnType("TEXT")
                        .HasMaxLength(128);

                    b.Property<string>("Name")
                        .HasColumnType("TEXT")
                        .HasMaxLength(128);

                    b.Property<string>("Value")
                        .HasColumnType("TEXT");

                    b.HasKey("UserId", "LoginProvider", "Name");

                    b.ToTable("AspNetUserTokens");
                });

            modelBuilder.Entity("myop.Models.Client", b =>
                {
                    b.Property<string>("ClientId")
                        .HasColumnType("TEXT");

                    b.Property<string>("AccessType")
                        .HasColumnType("TEXT");

                    b.Property<string>("AllowedScope")
                        .HasColumnType("TEXT");

                    b.Property<string>("AuthMethod")
                        .HasColumnType("TEXT");

                    b.Property<string>("ClientName")
                        .HasColumnType("TEXT");

                    b.Property<string>("ClientSecret")
                        .HasColumnType("TEXT");

                    b.Property<string>("GrantTypes")
                        .HasColumnType("TEXT");

                    b.Property<DateTime>("Iat")
                        .HasColumnType("TEXT");

                    b.Property<string>("RedirectUris")
                        .HasColumnType("TEXT");

                    b.HasKey("ClientId");

                    b.ToTable("Clients");
                });

            modelBuilder.Entity("myop.Models.Code", b =>
                {
                    b.Property<string>("CodeId")
                        .HasColumnType("TEXT");

                    b.Property<string>("ClientId")
                        .HasColumnType("TEXT");

                    b.Property<DateTime>("Iat")
                        .HasColumnType("TEXT");

                    b.Property<string>("Nonce")
                        .HasColumnType("TEXT");

                    b.Property<string>("UserId")
                        .HasColumnType("TEXT");

                    b.HasKey("CodeId");

                    b.ToTable("Codes");
                });

            modelBuilder.Entity("myop.Models.Token", b =>
                {
                    b.Property<string>("UserId")
                        .HasColumnType("TEXT");

                    b.Property<string>("AccessToken")
                        .HasColumnType("TEXT");

                    b.Property<string>("ClientId")
                        .HasColumnType("TEXT");

                    b.Property<DateTime>("Iat")
                        .HasColumnType("TEXT");

                    b.Property<string>("RefreshToken")
                        .HasColumnType("TEXT");

                    b.Property<string>("Scope")
                        .HasColumnType("TEXT");

                    b.HasKey("UserId");

                    b.ToTable("Tokens");
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
                {
                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
                        .WithMany()
                        .HasForeignKey("RoleId")
                        .OnDelete(DeleteBehavior.Cascade)
                        .IsRequired();
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
                {
                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
                        .WithMany()
                        .HasForeignKey("UserId")
                        .OnDelete(DeleteBehavior.Cascade)
                        .IsRequired();
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
                {
                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
                        .WithMany()
                        .HasForeignKey("UserId")
                        .OnDelete(DeleteBehavior.Cascade)
                        .IsRequired();
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
                {
                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
                        .WithMany()
                        .HasForeignKey("RoleId")
                        .OnDelete(DeleteBehavior.Cascade)
                        .IsRequired();

                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
                        .WithMany()
                        .HasForeignKey("UserId")
                        .OnDelete(DeleteBehavior.Cascade)
                        .IsRequired();
                });

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
                {
                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
                        .WithMany()
                        .HasForeignKey("UserId")
                        .OnDelete(DeleteBehavior.Cascade)
                        .IsRequired();
                });
#pragma warning restore 612, 618
        }
    }
}

次回

つくるオーオース WEBAPI編
https://qiita.com/namikitakeo/items/38be899790cb27a323df

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1