프로그래밍
[SpringBoot+Vue.js] 정리 (3) Vue router 추가 및 로그인
가시가되어
2023. 4. 6. 20:00
https://kora1492.tistory.com/75
이전글에 이어서 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">© 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