【Unity(C#),Python】API通信勉強メモ③簡易版ログイン機能の実装

##今回やること
Unity側から入力した情報を登録 & ログインする機能を作ります。
前回同様、Flaskでローカルにアプリケーションサーバーを立てて利用します。

【前回】：[【Unity(C#),Python】API通信勉強メモ②Flaskでローカルサーバー立ち上げ](https://qiita.com/OKsaiyowa/items/0f8c0fe030abf0b490ed)

なんもわからんなりの解釈が山盛りなのでマサカリ、オールオッケーです。
**特にセキュリティ面に関してはエンジニアと名乗るのが恥ずかしいくらい疎い**ので
超巨大マサカリで一刀両断してもらってもしっかりと受け止めます。


##実際に作成したもの
![GetHIKAKIN.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/08f029e7-0e6c-7af1-4ed0-59020c718cc3.gif)

ID、パスワードを入力してのアカウント登録が行えて、
ログイン画面で実際にログインっぽいことが可能です。

行っていることのイメージです。

![ログインイメージ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/1d02988b-bb28-cd0f-2d64-30eb6e99c8ca.png)

DBに情報が保存されているので、Editorを閉じてもアカウント情報は消えません。(たぶん)

##Unity側

Unity側が行う処理としては入力した情報をローカルサーバーに送って、
レスポンスに応じてテキストを表示するだけです。

```C#:登録ボタンに関する処理
using System.Collections;
using UnityEngine.Networking;
using UnityEngine;
using System.Text;
using UnityEngine.UI;

public class RegistraionHTTPPost : MonoBehaviour {

    [SerializeField, Header("LogText")]
    Text m_logText;

    [SerializeField, Header("IDInputField")]
    InputField m_idInputField;

    [SerializeField, Header("PassInputField")]
    InputField m_passInputField;

    //接続するURL
    private const string RegistrationURL = "http://localhost:5000/registration";

    //ゲームオブジェクトUI > ButtonのInspector > On Click()から呼び出すメソッド
    public void Registration()
    {
        StartCoroutine(RegistrationCoroutine(RegistrationURL));
    }

    IEnumerator RegistrationCoroutine(string url)
    {
        //POSTする情報
        WWWForm form = new WWWForm();
        form.AddField("user_id", m_idInputField.text, Encoding.UTF8);
        form.AddField("password", m_passInputField.text, Encoding.UTF8);

        //URLをPOSTで用意
        UnityWebRequest webRequest = UnityWebRequest.Post(url, form);
        //UnityWebRequestにバッファをセット
        webRequest.downloadHandler = new DownloadHandlerBuffer();
        //URLに接続して結果が戻ってくるまで待機
        yield return webRequest.SendWebRequest();

        //エラーが出ていないかチェック
        if (webRequest.isNetworkError)
        {
            //通信失敗
            Debug.Log(webRequest.error);
            m_logText.text = "通信エラー";
        }
        else
        {
            //通信成功
            Debug.Log("Post"+" : "+webRequest.downloadHandler.text);
            m_logText.text = webRequest.downloadHandler.text;
        }
    }
}

```

ローカルサーバーに対して情報を送る処理は下記箇所が担っています。
ローカルサーバー側が受け取る情報として
`form`に`user_id`、`password`などをリクエスト情報として追加しています。

```C#:リクエスト時に送る情報
    //POSTする情報
    WWWForm form = new WWWForm();
    form.AddField("user_id", m_idInputField.text, Encoding.UTF8);
    form.AddField("password", m_passInputField.text, Encoding.UTF8);
```

詳細に理解できてはいませんが、
`form`というのはリクエストの種類(POST,GETなど)に加えて、何かしらの情報を渡せるもののようです。
`WWWForm`はPOST専用のクラスです。

##ローカルのアプリケーションサーバー(Flask)

こっちは本当に難しくて、
**そもそも私は何をやればいいんだろう**という状態が長く続いてしんどかったです。

まずは**アカウント情報を登録する上でDB(データベース)というものを利用する必要がある**とわかりました。

##DBって何？

>データベース（英: database, DB）とは、検索や蓄積が容易にできるよう整理された情報の集まり。 通常はコンピュータによって実現されたものを指すが、紙の住所録などをデータベースと呼ぶ場合もある。コンピュータを使用したデータベース・システムでは、データベース管理用のソフトウェアであるデータベース管理システムを使用する場合も多い。

【引用元】：[ウィキペディア（Wikipedia）](https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9)

データベースってのはソフトウェアのことらしいです。
そのデータはどこにあってどういう仕組みで成り立っているのか完全に理解するために深堀りすると、
帰ってこられなくなるって偉い人が言ってたので深くは考えません。
【参考リンク】：[そもそもデータベースって何で出来ていて、どこの何にどう保存されるのでしょうか。。](https://teratail.com/questions/148717)

DBにもいろいろと種類があって、今回利用するのは**RDB(リレーショナルデータベース)**っぽいです。

##SQLAlchemy

>データベースを実際に操作するにはSQLという言語を用いるのですが、それをPython内からやってくれる、というライブラリ

【引用元】：[はじめての Flask #4 ~データベースをSQLAlchemyでいじってみよう~](https://qiita.com/nagataaaas/items/5c7c9ec4813fea85c40c)

だそうです。便利ですね～。今回はこちらを使います。

##実装

いよいよFlask及びDBの実装です。

```python
import hashlib

from flask import *

from sqlalchemy import create_engine, Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

app = Flask(__name__)

engine = create_engine('sqlite:///user.db')
Base = declarative_base()


# DBの設定
class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True, unique=True)
    user_id = Column(String)
    password = Column(String)


Base.metadata.create_all(engine)
SessionMaker = sessionmaker(bind=engine)
session = scoped_session(SessionMaker)


# DBにIDとパスワード登録する
@app.route("/registration", methods=["POST"])
def registration():

    user = request.form["user_id"].strip()
    check_users = session.query(User).filter(User.user_id == user).all()

    if check_users:
        return "そのユーザー名は使用済みです"
    else:
        user = User(user_id=request.form["user_id"], password=str(hashlib.sha256(
            request.form["password"].strip().encode("utf-8")).digest()))
        session.add(user)
        session.commit()
        return str(user.user_id.strip() + "様\nご登録ありがとうございます")

# ログインできるID、パスワードの組合わせかどうかDBを見て照合
@app.route("/login", methods=["POST"])
def login_check():

    user = request.form["user_id"].strip()
    check_users = session.query(User).filter(User.user_id == user).all()

    try:
        for login_user in check_users:
            login_user_pass = login_user.password

        if login_user_pass == str(hashlib.sha256(
                request.form["password"].strip().encode("utf-8")).digest()):
            return "ログイン完了です"
        else:
            return "パスワードが異なります"
    except:
        return "登録情報が異なります"


if __name__ == "__main__":
    app.run(debug=True)
    # User.__table__.drop(engine)  # テーブル削除用

```



##テーブルの消し方
下記箇所のコメントアウトを解除して動かせば消えます。

```python
  User.__table__.drop(engine)  # テーブル削除用
```

##Pylintと仲良くする
VSCでpythonを書いているのですが、FlaskがPylintと仲良くしてくれませんでした。

そのため、Setting.jsonを開いて下記設定を追記しました。

```
"python.linting.pylintArgs": [
        "--load-plugins",
        "pylint_flask"
    ],
```

##ハッシュ化

今回、**ハッシュ化してセキュリティ対策もばっちりだぜ!**ってのをやってみたかったんですが、
現状、②の箇所しかできていないので全く意味がないような気がしてます。
POSTを使うだけではセキュリティ不十分だよ～って記事がいっぱい出てくるので
やりとりする情報は全てハッシュ化しないとダメなのかな～と勝手に思ってます。
この辺り、詳しく知ってる方いたら教えてください。


![ログインイメージ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/d0498c91-8ea8-9ce9-5816-e73a743905b5.png)



##Basic認証とDigest認証
セキュリティうんぬんを調べている際に知りました。
認証にも種類があるそうです。

>Digest認証はセキュリティの観点でBasic認証より優れています。しかし、すべての環境に対応しているわけではありません。ページを利用するユーザーの環境がある程度分かっていて、対応しているブラウザを使っている場合には問題はありません。しかし、不特定多数のユーザーに向けたページで設定をする場合、Digest認証は向いていません。

>一方Basic認証はセキュリティ面でDigest認証に劣っています。しかし、あらかじめセキュリティ対策が行われている環境下、例えばSSLやローカルネットワーク内などで利用する分には特に問題はないでしょう。ユーザーの環境にも左右されません。

>このように、不特定多数のユーザーが使うページにユーザー認証を設定する場合はSSLと合わせたBasic認証、管理者など接続する環境が特定されている場合にはDigest認証、と状況によって使い分けるのが一般的です。


【引用元】：[Basic認証（基本認証）とDigest認証、それぞれの役割と違いについて](https://madalinazaharia.com/column/basic-authentication-and-digest-authentication/)


今回実装したものはBasic認証と呼ばれるものに該当するのでしょうか。
よくわかりませんので、"お前が作ったのはどちらでもない"とかでいいので知りたいです。


##参考リンク


[【PythonのORM】SQLAlchemyで基本的なSQLクエリまとめ](https://qiita.com/ckern/items/a762b1bc0f192a55eae8)