[SpringBoot+Vue.js] 정리 (3) Vue router 추가 및 로그인

 

 

https://kora1492.tistory.com/75

 

[SpringBoot+Vue.js] 정리 (2) MariaDB 설치 및 JPA 추가하여 프론트 에 객체 데이터 출력

DB(MariaDB) 및 JPA를 사용하기 위해, build.gradle 추가 implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' MaraiDB 설치 https://mariadb.org/download/?t=mariadb&p=mariadb&r=10.

kora1492.tistory.com

 

이전글에 이어서 Vue-router 를 추가하기

 

Vue-router npm install 추가해서 진행하는데 

아래와 같은 에러 발생

uncaught typeerror cannot read property of null (reading 'parent')

 

-> 원인은 Vue3 과 호환이 안맞아서 발생한 것으로 보임.

-> 해결은 Vue2로 변경하거나 혹은 npm i vue-router@next  설치

 

설치 후 router.js 설정하기

scripts/router.js

import {createRouter, createWebHistory} from 'vue-router'
import Home from "@/pages/Home";
import Login from "@/pages/Login";

const routes = [
    {path:'/', component: Home},
    {path:'/login', component: Login}
]

const router = createRouter(
    {
        history: createWebHistory(),
        routes
    }
)

export default router

Home 화면과 Login 화면을 routes 의 경로 와 component 를 연결

 

Login.Vue

현재는 세션 스토리지에 ID를 저장하여 로그인을 유지 

 

<template>
  <div class="form-signin w-100 m-auto">
    <h1 class="h3 mb-3 fw-normal">Please sign in</h1>
    <div class="form-floating">
      <input type="email" class="form-control" id="floatingInput" placeholder="name@example.com"
             @keyup.enter="submit()" v-model="state.form.email">
      <label for="floatingInput">Email address</label>
    </div>
    <div class="form-floating">
      <input type="password" class="form-control" id="floatingPassword" placeholder="Password" @keyup.enter="submit()" v-model="state.form.password">
      <label for="floatingPassword">Password</label>
    </div>
    <div class="checkbox mb-3">
      <label>
        <input type="checkbox" value="remember-me" @keyup.enter="submit()"> Remember me
      </label>
    </div>
    <button class="w-100 btn btn-lg btn-primary" @click="submit()">Sign in</button>
    <p class="mt-5 mb-3 text-muted">&copy; 2017–2022</p>
  </div>
</template>

<script>
import {reactive} from "vue";
import axios from "axios";
import store from "@/scripts/store";
import router from "@/scripts/router";
export default {
  setup() {
    const state = reactive({
      form: {
        email: "",
        password: ""
      }
    })
    const submit = () => {
      axios.post("/api/member/login", state.form).then((res) => {
        store.commit('setAccount', res.data);
        sessionStorage.setItem("id", res.data);
        router.push({path: "/"});
        window.alert("로그인하였습니다.");
      }).catch(() => {
        window.alert("로그인 정보가 없습니다.");
      });
    }
    return {state, submit}
  }
}
</script>

<style scoped>
.form-signin {
  max-width: 330px;
  padding: 15px;
}
.form-signin .form-floating:focus-within {
  z-index: 2
}
.form-signin input[type="email"] {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
</style>

Header.Vue

 

웹 사이트 헤더쪽의 로그인/로그아웃 체크

<template>
  <header>
    <div class="collapse bg-dark" id="navbarHeader">
      <div class="container">
        <div class="row">
          <div class="col-sm-8 col-md-7 py-4">
            <h4 class="text-white">사이트맵</h4>
            <ul class="list-unstyled">
              <li>
                <router-link to="/" class="text-white">메인 화면</router-link>
              </li>
              <li>
                <router-link to="/login" class="text-white" v-if="!$store.state.account.id">로그인</router-link>
                <a to="/logoin" class="text-white" @click="logout()" v-else>로그아웃</a>

              </li>
            </ul>
          </div>

        </div>
      </div>
    </div>
    <div class="navbar navbar-dark bg-dark shadow-sm">
      <div class="container">
        <a href="#" class="navbar-brand d-flex align-items-center">
          <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor"
               stroke-linecap="round" stroke-linejoin="round" stroke-width="2" aria-hidden="true" class="me-2"
               viewBox="0 0 24 24">
            <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
            <circle cx="12" cy="13" r="4"/>
          </svg>
          <strong>Album</strong>
        </a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarHeader"
                aria-controls="navbarHeader" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
      </div>
    </div>
  </header>
</template>

<script>
import store from "@/scripts/store";
import router from "@/scripts/router";

export default {
  name: 'Header',
  setup() {
    const logout = () => {
      store.commit("setAccount", 0);
      sessionStorage.removeItem("id");
      router.push("/");
    }

    return {logout};
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  display: inline-block;
  margin: 0 10px;
}

a {
  color: #42b983;
}
</style>

main.js

 

store, router 추가

import { createApp } from 'vue'
import store from "@/scripts/store"
import router from "@/scripts/router"

import App from './App.vue'
createApp(App).use(store).use(router).mount('#app')


//createApp(App).mount('#app')

store.js

import {createStore} from 'vuex'

const store = createStore({
    state() {
        return {
            account: {
                id: 0
            }
        }
    },
    mutations: {
        setAccount(state, payload) {
            state.account.id = payload;
        }
    }
})

export default store;

 

Back

LoginController 및 Member 엔터티 생성

 

@Slf4j
@RestController
public class LoginController {

    @Autowired
    MemberRepository memberRepository;

    @PostMapping("/api/member/login")
    public int login(@RequestBody Map<String, String> params){
        Optional<Member> optionalMember = memberRepository.findByEmailAndAndPassword(params.get("email"), params.get("password"));
        Member member;
        if(optionalMember.isPresent()){
            member = optionalMember.get();
            log.info("멤버 로그인");

            return member.getId();
        }

        throw new ResponseStatusException(HttpStatus.NOT_FOUND);
    }
}

 

import jakarta.persistence.*;
import lombok.Getter;

@Getter
@Entity
@Table(name = "members")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(length = 50, nullable = false)
    private String email;

    @Column(length = 100, nullable = false)
    private String password;

}

 

 

현재 패스워드 암호화 및

JWT 토큰을 통한 로그인 방식으로 개선 진행 예정

 

 

Ref. 

아프리카도서관님 강의 참고

https://www.youtube.com/watch?v=htYYSszfzv0&t=5431s