9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

認可認証の実装 フロントエンド:nextjs  バックエンド:SpringSecurityのjwt認証

Last updated at Posted at 2022-12-30

概要

webアプリケーションの認可認証を実装します。
フロントエンドの側の実装がメインになります
バックエンドのSpringBootは仕上がっている体で進めます。postmanでのE2Eテストしかしません

以下フレームワークを使って実装します。

バックエンド:SpringBoot restapi
フロントエンド:next.js react typescript axios
認可認証:SpringSecurity

開発環境

開発環境
OS:windows10

バックエンド側:
IDE:IntelliJ Community
spring-boot-starter-parent 2.75
java : 11

フロントエンド:
IDE:VScode

├── @types/node@18.11.15
├── @types/react-dom@18.0.9
├── @types/react@18.0.26
├── axios@1.2.1
├── eslint-config-next@13.0.6
├── eslint@8.29.0
├── next-auth@4.18.7
├── next@13.0.6
├── react-bootstrap@2.7.0
├── react-dom@18.2.0
├── react@18.2.0
├── styled-components@5.3.6
├── styled-jsx@5.1.1
└── typescript@4.9.4

図とか

認証の動き

image.png

認可の動き

バックエンドの動きははかなり端折ってます。実際はややこしいです。
Controllerレイヤーに入る前にアクセストークンのバリデーション処理が入るよって理解で十分だと思います。

image.png

フロントエンドファイル構成(仮)

検証用ページ作って動きを確認します。
image.png

実装

バックエンド側

実装は済んでいるものとして進めます。

Postmanでの認証

ログインしてから、レスポンスで返されるaccesstokenを使って認可が必要なapiを叩いている様子です。

login_postman.gif

フロントエンドの側の実装

ソースコード

customer.d.ts
/** 
* @remarks ユースケース上、ログイン機能の実装するために使います。顧客の詳細情報(電話番号、住所など)はCustomerDetail側の管轄になります。
*
* @author RYA234
* @link {signin.ts}
*/
export  type Customer = {
	append(arg0: string, inputEmail: string): unknown
	/** Eメールアドレスです。ログインするときに必要な情報です 入力した情報とMySql側で照合します。*/
	email:string 
	/** パスワードです。 ログインするときに必要な情報です。SpringSecurity側でエンコードされます。*/
	password:string 
}


customerService.ts

import axios from 'axios';
import { URLSearchParams } from 'url';
import { Customer } from '../types/customer';



export const signIn = ({email,password}: Customer) =>{
	return axios.post(`http://127.0.0.1:5000/api/auth/signin`,
	{ email: email, password: password },
	{ headers: { 
		'Request-Method' : 'POST',
		'Content-Type': 'application/json',
		'Access-Control-Allow-Origin': '*',
		'Access-Control-Allow-Headers': '*',
		'Access-Control-Allow-Credentials': 'true'
	} } //COR回避
		
	);
};

export const logout =()=>{
	return axios.post(`http://127.0.0.1:5000/api/auth/logout`);
}


//認可が必要なapi 本来だったら別カテゴリーに移す
export const getCartItems =(jwtAccessKey : string)=>{
	return axios.get(`http://127.0.0.1:5000/api/cart/all`,
	{ headers : {
			'Authorization': `Bearer ${jwtAccessKey}`
		}}
	);
}

authcheck.tsx
import { MouseEventHandler, useRef, useState } from "react";
import * as customerService from "../../service/customerService";
import { Customer } from "../../types/customer";
import { JwtResponse } from "../../types/jwtResponse.t";
import { Button, CarouselItem, Form } from 'react-bootstrap';
import { Category } from "../../types/category";


/**
 * @remarks 検証用ページ <br/
 *			以下の2点の実装方法を確認するためのページです。<br/>
 *	  	  1. nextjsをフロントとしてSpringSecurityの認証するための実装方法確認<br/>
 *		  2. 認証成功後、認可が必要なapiを使えるか実装方法確認y<br/>	
 * @returns 
 * 
 * 
 */
export default function Authcheck(){

	// 開発効率上げるため この時点で登録情報は入力済みとする
	// 登録情報はMysqlに保存され passwordはバックエンド側でbcrypt方式で暗号化され保存される。 
	const [inputEmail,setInputEmail] = useState<string>('aaaba@gmail.com');
	const [inputPassword,setInputPassword] = useState<string>('test');
	const [jwtAccessToken, setJwtAccessToken] = useState<string | null>();
	const [cartItemMessage,setCartItemMessage] = useState<string>();
	const [presentCartItems, setPresentCartItems] = useState<cartItemType[]>([]);

	// 本当は型定義ファイルに移すべきだが、今回はここに書く
	type cartItemType = {
		id:number,
		customerId:number,
		productId:number,
		quantity:number,
	}
	
	// 認証ログイン処理
	const submitHandler=(event : React.FocusEvent<HTMLFormElement>) => {
		event.preventDefault();
		const requestBody : Customer  ={
			email: inputEmail,
			password: inputPassword,			
			append: function (arg0: string, inputEmail: string): unknown {
				throw new Error("Function not implemented.");
			}
		} ;
		customerService
		.signIn(requestBody)
		.then((response) => {
			localStorage.setItem('accessToken', response.data.accessToken)
			setJwtAccessToken(response.data.accessToken);})
		.catch((error) => { 
			localStorage.setItem('accessToken', ' error');
			setJwtAccessToken(localStorage.getItem('accessToken'));})
		;
	}

	// 認可 顧客の買い物かごの情報を取得する。
	const getCategoriesHandler = (onClick? : React.MouseEvent<HTMLButtonElement> | undefined) => {
		customerService
		.getCartItems(jwtAccessToken as string)
		.then((cartItems) => {
				cartItems.data.map((cartItem : cartItemType) => { 
				setPresentCartItems(presentCartItems => [...presentCartItems,{
					id:cartItem.id,
					customerId:cartItem.customerId,
					productId:cartItem.productId,
					quantity:cartItem.quantity
				}]);
				setCartItemMessage('成功');
				}
				)
		})
		.catch((error: any) => {
			setPresentCartItems([]); // 初期化
			setCartItemMessage('失敗');
		})	
	}
	return(
		<>
			<h2>auth jwt 認証 ログイン</h2>
			フロント側:NextJs<br />
			バックエンド側:Springboot SpringSecurity<br />
			動き
			<br />
			URI http://localhost:5000/api/auth/signinにPOST Methodを送ります。<br />
			<br />
			リクエストボディにはjson形式で ユーザーが入力したemailとpasswordが含まれています<br />
			正しいemailとpasswordが入力された場合localstorageがJWT-Tokenが収納されます<br />
			誤ったemailとpasswordが入力された場合localstorageにerrorが収納されます<br />

			local:storageの値UseStateに渡している値:<br />
			{jwtAccessToken}
			<Form onSubmit = {submitHandler} >
			<Form.Group className="mb-3" controlId="formBasicEmail">
				<Form.Label>Email address</Form.Label><br />
				<Form.Control 
					type="email" 
					placeholder="Enter email"  
					value  = {inputEmail as string}
					onChange={(event) => setInputEmail(event.target.value)}
					/><br />
			</Form.Group>

			<Form.Group className="mb-3" controlId="formBasicPassword">
				<Form.Label>Password</Form.Label><br />
				<Form.Control 
					type="password" 
					placeholder="Password" 
					value ={inputPassword as string}
					onChange={(event) => setInputPassword(event.target.value)}
					/><br /><br />
			</Form.Group>

			<Button variant="primary" type="submit">
				Submit
			</Button>
			</Form>
		<div>
			<h2>auth jwt 認可</h2>
			認証が必要なAPIを叩けるか実際に確認する<br />
				ユースケース的にはECショップにおいてユーザーが自分の買い物カゴの情報を取得します<br />
				<Button onClick={getCategoriesHandler}>買い物カゴの情報を取得する</Button>
				<br />
				id, 商品Id数量
				 {presentCartItems.map((cartItem:cartItemType) => {
					return(
						// eslint-disable-next-line react/jsx-key
						<div>
						{cartItem.id}  {cartItem.productId} {cartItem.quantity}
						</div>
					)
				 }
				)}
				<br/><br/>
				api実行できたか<br/>
				{cartItemMessage}
		</div>
	</>
	)
}

認証(ログイン)ができるか検証する

localstorageにaccessTokenが入っているので 期待した動きができてますね
login_OK.gif

認証後 認可が必要なapiが実行できるか検証する。

情報が取得されてるので、実行されてますね。
認可完了_OK.gif

認証せず、認可が必要なapiが実行できないことを検証する。

以下 gifアニメは accessTokenを消して、認可が必要なAPIを叩いている様子です。

認可完了_NGpatter1.gif

		.catch((error: any) => {
			setPresentCartItems([]); // 初期化
			setCartItemMessage('失敗');
		})	

らへんの処理をしているので期待した動きをしています。

あとはログアウト処理できれば機能的に十分そう

9
4
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
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?