【Angularアプリケーション開発 #9】ログイン機能とセッション管理について

# はじめに
今回、IDとパスワードでのログインを実装しました。
また、ログイン後の画面に直接アクセスした場合、ログイン状態を確認し、遷移先を分岐させるようにしました。

## ログイン
### ログイン画面
```html:login.component.html
<div class="container">
    <h1>Login</h1>
    <form (ngSubmit)="onSubmit()" #loginForm="ngForm">
        <table>
            <tr>
                <td><label for="id">名前</label></td>
                <td><input type="text" id="id" required [(ngModel)]="model.loginId" name="idName" (blur)="idValid()">
                    <span *ngIf="idVaridFlg">
                        名前を入力して下さい。
                    </span>
                </td>
            </tr>
            <tr>
                <td><label for="password">パスワード</label></td>
                <td><input type="text" id="password" required [(ngModel)]="model.password" name="passName" (blur)="passwordValid()">
                    <span *ngIf="passwordVaridFlg">
                        パスワードを入力して下さい。
                    </span>
                </td>
            </tr>
        </table>
        <button type="submit" [disabled]="!loginForm.form.valid">ログイン</button>
        <br /> {{model.message}}
    </form>
    <button (click)="define()">セッションの削除</button>
</div>
```
### ログイン処理
簡単に説明すると、IDとパスワードをPOSTでバックエンド側にリクエストしています。
ステータス200が返ってきた場合は、次のページへ遷移させています。
```typescript
  onSubmit(): void {
    this.auth();
  }
  auth(): void {
    const myAsync = async (url) => {
      const headers = {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      };
      const obj = this.model;
      const body = JSON.stringify(obj);
      const response = await fetch(url,{method: "POST",credentials: 'include', headers:headers,body:body}); //await で fetch() が完了するまで待つ
      return response.status;
    }
    myAsync(this.Host + '/sessionsave').then((data)=>{
      if(data == 200){
        this.router.navigate(['members']);
      }else{
        this.model.message = 'ログインに失敗しました';
      }
    }).catch((err) => console.log('セッション情報の生成に失敗'));
  }
```
ログイン処理でリクエストしているAPIです。
内容としては、ボディにセットされているIDとパスワードが正しい場合にセッション情報を作成しています。
また、今回はCookieも一応セットするようにしています。
補足ですが、Cookieとセッションは分けて考えたほうが良いです。
セッションもCookieを使用していますが、セッションIDをCookieに保存しています。
セッションで保存された情報はサーバ側にあります。また、セッションの情報はサーバ側で削除することもできます。
Cookieだけでもセッションを実現することはできますが、常にキーと値をフロントとバックを行き来することになるのと、その値は、デベロッパーツールで簡単に見ることができるので、セキュリティ的に弱いです。
例えば、ログアウトしてもCookieにキーと値をセットしてサーバ側に送信すればなりすましでログインできてしまいます。
DBを介せばCookieだけでもセッション管理できそうですが。。

```java
    @PostMapping("/sessionsave")
    public void save(HttpServletResponse response, @RequestBody LoginInfo loginInfo) {

    	if(loginInfo.getLoginId().equals("×××") && loginInfo.getPassword().equals("×××")) {
        	Cookie cookie = new Cookie("key", "value");
        	response.addCookie(cookie);
        	session.setAttribute("key", cookie.getValue());
    	}
    }
```
## 直アクセスへの対応（ガード処理）
### ガード処理
バック側へセッションが有効かどうかをリクエストしています。
```typescript:auth.guard.ts
canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      this.authCheck();
    return true;
  }
  authCheck(): void {
    const myAsync = async (url) => {
      const response = await fetch(url,{credentials: 'include'});
      const data = await response.json();
      if(data.cookieValidFlag){
        this.router.navigate(['members']);
        
      }else{
        this.router.navigate(['login']);
      }
      return data;
    }
    myAsync(this.Host + '/sessionload').then().catch((err) => console.log("データ取得に失敗"));
  }
```
判定を入れたいコンポーネントに```canActivate: [AuthGuard]```を記載しています。
```typescript:app-routing.module.ts
const routes: Routes = [
  {path:'', redirectTo: '/login',pathMatch: 'full'},
  {path:'login', component: LoginComponent},
  {path:'members', component: MembersComponent, canActivate: [AuthGuard]}
]
```
java側の処理です。
セッションがnullでないことを確認しています。
nullでなければ、フロントに有効であることを返しています。
```java
    // セッションの取得
    @GetMapping("/sessionload")
    public CookieInfo load(
    		@CookieValue(name = "key", required = false) String value)  {
    	String result =  (String)session.getAttribute("key");  // 取得
    	CookieInfo cookieInfo = new CookieInfo();
    	cookieInfo.setCookieValidFlag(false);
    	if(result != null) {
    		cookieInfo.setCookieValidFlag(true);
    	}
    	return cookieInfo;
    }
```
# おわりに
どうでしたでしょうか。
かなり端折っていますが、重要な部分は記載できていると思います。
今回は触れていませんが、セッションIDをリクエストに付加して送る場合は、java側でフロントのURLを設定しておく必要がありました。
IDとパスワードはjavaのsrcに直接書いていますが、通常はDBに登録していると思うので、そちらは各々で変更してください。
もうセッションを使った認証は古いかもしれませんが、、良ければ参考程度に見てください。