로그인

2021. 12. 11. 00:22Spring Security/Vue 3 Authentication with JWT

반응형

로그인 처리는 사용자가 로그인 시 JWT를 이용하여서 토큰을 발급받은 후 Local Storage에 서버로부터 발급받은 AccessToken과 사용자 정보를 저장하게 된다. 

 

저장된 정보는 로그인 후 페이지 접근 시 Request Header에 AccessToken을 전송하여서 사용자 인증을 처리하게 된다.

 

서버 쪽 프로세스는 아래 글을 참고하여주시기 바랍니다.

https://jydlove.tistory.com/63?category=1031676 

 

로그인 프로세스

사용자 로그인에 대한 로직을 살펴보도록 하겠습니다. 아래 시퀀스 다이어그램을 통해서 전체적인 로직을 확인 해보도록 하겠습니다. JwtFilter 로그인시 발급된 토큰 및 쿠키 정보가 없기 때문에

jydlove.tistory.com

변경된 서버 프로세스는 쿠키를 사용하지 않는다. 이유는 클라이언트의 Local Storage를 사용하기 때문이다.

 

로그인 화면

로그인 화면

 

로그인후 화면

로그인 후 화면 아래 보면 user 라는 정보에 로그인 후 전달받은 토큰 정보, 사용자 권한, 사용자 정보가 저장된 것을 확인할 수 있습니다.

 

소스 

프로젝트구조

앞에 프로젝트 설정 시 composables 폴더는 생성이 되지 않습니다.

composables 폴더는 컴포넌트의 재사용성을 위해 별도로 만든 폴더입니다.

 

views/auth/Login.vue

로그인 화면에 해당하는 화면 소스

<template>
  <form @submit.prevent="handleSubmit">
      <h3>로그인</h3>
      <input type="email" placeholder="Email" v-model="username">
      <input type="password" placeholder="Password" v-model="password">
      <div v-if="error" class="error">{{ error }}</div> 
      <button v-if="!isPending">로그인</button>
      <button v-if="isPending" disable>Loading</button>
  </form>
</template>

<script>
import useLogin from '@/composables/useLogin'
import { useStore } from 'vuex'
import { ref } from 'vue'
import { useRouter } from 'vue-router'

export default {
    setup() {
        const{ error, login, isPending } = useLogin()
        const store = new useStore()
        const router = useRouter()

        const username = ref('')
        const password = ref('')

        const handleSubmit = async () => {
            await login(username.value, password.value)

            if (store.state.auth.status.loggedIn) {
                router.push({ name: 'Profile' })
            }
        }

        return { username, password, handleSubmit, error, isPending }
    }
}
</script>

<style>

</style>

 

해당 소스 부분이 실행이 되면 아래의 순서로 로그인 처리가 진행됩니다.

const handleSubmit = async () => {
	// 사용자 로그인 처리
	await login(username.value, password.value)
}

 

composables/useLogin.js

실제 서버와의 통신을 통하여서 로그인 성공 시 응답 값을 받아서 Local Storage에 저장하는 역할과 vuex를 이용하여서 사용자의 로그인 여부를 state에 저장 함으로써 다른 컴포넌트에서도 사용자의 로그인 여부를 판단할 수 있다.

import { ref } from 'vue'
import store from "../store"

const error = ref(null)
const isPending = ref(false)

const login = async (username, password) => {

    error.value = null
    isPending.value = true


    try {
        await store.dispatch('auth/login', {username, password})

        error.value = null
        isPending.value = false
    } catch(err) {
        error.value = '로그인 정보가 올바르지 않습니다.'
        isPending.value = false
    }
}

const useLogin = () => {
    return { error, login, isPending }
}

export default useLogin

 

store/index.js

store 객체를 생성하고 모듈을 관리한다.

프로젝트에서는 로그인과 로그아웃을 관리하는 auth 모듈을 만들어서 별도로 관리한다.

import { createStore } from "vuex";
import { auth } from "./auth.module";

const store = createStore({
  modules: {
    auth,
  },
});

export default store;

 

store/auth.module.js

소스를 보기 전에 vuex의 store에 대한 개념은 아래의 그림을 참고하자

vuex store 개념

Actions: Backend API 호출

Mutatios: 뜻은 변이라는 뜻이지만 쉽게 말하자면 Backend API 호출 결괏값을 파라미터로 전달받은 후 state에 값을 저장하게 된다.

 

위의 개념을 보고 아래 소스를 보면 이해하기 쉬울 것입니다.

아래 소스는 Backend Login API 호출 후 결과로 사용자 정보를 담고 있는 user 객체를 받은 후 mutations에서 user 객체와 로그인 상태를 저장하게 됩니다.

 

import AuthService from '../services/auth.service';

const user = JSON.parse(localStorage.getItem('user'));
const initialState = user
  ? { status: { loggedIn: true }, user }
  : { status: { loggedIn: false }, user: null };

export const auth = {
  namespaced: true,
  state: initialState,
  actions: {
    login({ commit }, {username, password}) {
      return AuthService.login(username, password).then(
        user => {
          commit('loginSuccess', user);
          return Promise.resolve(user);
        },
        error => {
          commit('loginFailure');
          return Promise.reject(error);
        }
      );
    },
    logout({ commit }) {
      AuthService.logout();
      commit('logout');
    }
  },
  mutations: {
    loginSuccess(state, user) {
      state.status.loggedIn = true;
      state.user = user;
    },
    loginFailure(state) {
      state.status.loggedIn = false;
      state.user = null;
    },
    logout(state) {
      state.status.loggedIn = false;
      state.user = null;
    }
  },
  getters: {
    isLoggedIn: state => state.loginSuccess
  }

};

 

services/user.service.js

Backend API 인 로그인 API를 호출한다.

로그인 API는 아래 그림과 같다.

Swaager Login API 명세

import axios from 'axios';

const API_URL = 'http://localhost:7070/';

class AuthService {
    /**
     * 로그인
     */
    login(username, password) {
        return axios.post(API_URL + 'signin', {
            username: username,
            password: password
        })
        .then(response => {
            if (response.data.accessToken) {
                localStorage.setItem('user', JSON.stringify(response.data));
            }

            return response.data;
        });
    }

    /**
     * 로그아웃
     */
    logout() {
        // LocalStorage 사용자 정보
        let user = JSON.parse(localStorage.getItem('user'))

        let data = {
            username: user.username
        }

        return axios.post(API_URL + 'signout', JSON.stringify(data), {
            headers: {
                "Content-Type": 'application/json',
            },
        })
        .then(response => {
            console.log(response)
            localStorage.removeItem('user');
        });
    }
}

export default new AuthService();

여기까지 실행이 되면 로그인 API 호출 후 처리결과를 vuex store에 저장하게 됩니다.

 

마지막으로 아래 소스와 같이 store의 로그인 상태가 ture인 경우 vue-router를 이용하여서 Profile 페이지로 이동 처리합니다.

const handleSubmit = async () => {
	// 로그인 처리
	await login(username.value, password.value)

	// 로그인 상태 이면 Profile 화면으로 이동
	if (store.state.auth.status.loggedIn) {
		router.push({ name: 'Profile' })
	}
}

 

 

반응형