Spring Security – 세션 관리 간단 구현

Spring Boot와 Vue.js를 사용하는 애플리케이션에서 세션 관리로그아웃 기능을 구현하면서 간단한 내용을 정리해본다.

로그인/로그아웃 기능은 프로젝트 초반에 만들고 이후에 진행하면서 몇 가지 옵션들을 추가하는 형식으로 진행되는데 한창 진행할때는 전체적인 구조와 개념들이 머리 속에 있는 듯 하지만 다른 프로젝트를 시작할때는 기억이 잘 안나서 다시 찾아보곤 하게 된다.

아주 간단한 기본 구조를 기록으로 남기고, 이후 내용은 프로젝트 특성에 따라 가감해서 사용하도록 한다.


1. 백엔드에서 세션 기반 인증 설정

1.1 Spring Security 추가

build.gradle.kts에 Spring Security 의존성을 추가한다.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-security")
    // 기타 다른 의존성들....
}
YAML

1.2 기본 Security 설정

Spring Security를 사용하여 로그인과 세션 관리 설정을 추가합니다.

SecurityConfig.kt

package com.example.demo.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
       http
            .csrf { csrf ->
                csrf.disable() // 개발환경 CSRF 비활성화
            }
            .authorizeHttpRequests { requests ->
                requests
                    .requestMatchers("/login", "/api/public/**").permitAll() // 로그인 화면 및 공개 API는 허용
                    .anyRequest().authenticated() // 나머지는 인증 필요
            }
            .formLogin { formLogin ->
                formLogin
                    .loginPage("/login") // 로그인 페이지 설정
                    .permitAll()
            }
            .logout { logout ->
                logout
                    .logoutUrl("/logout") // 로그아웃 URL
                    .logoutSuccessUrl("/login") // 로그아웃 성공 후 이동할 페이지
                    .invalidateHttpSession(true) // 세션 무효화
                    .deleteCookies("JSESSIONID") // 쿠키 삭제
            }
        return http.build()
    }
}
Kotlin

1.3 컨트롤러에서 로그인 성공 후 세션 설정

로그인 후 사용자의 세션 정보를 저장하도록 구현한다.

LoginController.kt

package com.example.demo.controller

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import jakarta.servlet.http.HttpSession

data class LoginRequest(val username: String, val password: String)

@RestController
class LoginController {

    @PostMapping("/api/login")
    fun login(@RequestBody request: LoginRequest, session: HttpSession): String {
        // 로그인 검증 로직 (간단한 예제)
        if (request.username == "admin" && request.password == "password") {
            session.setAttribute("username", request.username) // 세션에 사용자 저장
            return "Login successful"
        }
        throw RuntimeException("Invalid credentials")
    }

    @GetMapping("/api/logout")
    fun logout(session: HttpSession): String {
        session.invalidate() // 세션 무효화
        return "Logged out successfully"
    }
}
Kotlin

2. 백엔드 인증 상태 확인 API

Vue.js에서 사용자의 로그인 상태를 확인하기 위해 종종 사용하게 되는 상태 인증용 API를 작성한다.

AuthController.kt

package com.example.demo.controller

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpSession

@RestController
class AuthController {

    @GetMapping("/api/auth/status")
    fun authStatus(session: HttpSession): Boolean {
        return session.getAttribute("username") != null // 세션에 사용자가 있으면 true
    }
}
Kotlin

3. 프론트엔드에서 인증 관리

3.1 Vuex 로그인 상태 관리

로그인 성공 여부를 저장하여 사용한다.

store.js

import { reactive } from "vue";

export const store = reactive({
  isAuthenticated: false,
  setAuthenticated(status) {
    this.isAuthenticated = status;
  },
});
Kotlin

3.2 인증 상태 확인

main.js 또는 각 라우트 전환 시 인증 상태를 확인하여 필요한 경우 로그인으로 유도한다.

라우터 가드 설정

import { createRouter, createWebHistory } from "vue-router";
import { store } from "./store";
import LoginView from "@/views/LoginView.vue";
import LogListView from "@/views/LogListView.vue";

const routes = [
  { path: "/login", name: "Login", component: LoginView },
  {
    path: "/logs",
    name: "Logs",
    component: LogListView,
    meta: { requiresAuth: true }, // 인증이 필요한 경로
  },
];

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

router.beforeEach(async (to, from, next) => {
  if (to.meta.requiresAuth) {
    // 인증 상태 확인
    const response = await fetch("/api/auth/status");
    const isAuthenticated = await response.json();

    if (isAuthenticated) {
      store.setAuthenticated(true);
      next();
    } else {
      store.setAuthenticated(false);
      next("/login");
    }
  } else {
    next();
  }
});

export default router;

3.3 로그인 및 로그아웃

LoginView.vue

<template>
  <div>
    <h1>로그인</h1>
    <input v-model="username" placeholder="아이디" />
    <input v-model="password" type="password" placeholder="비밀번호" />
    <button @click="login">로그인</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: "",
      password: "",
    };
  },
  methods: {
    async login() {
      const response = await fetch("/api/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ username: this.username, password: this.password }),
      });

      if (response.ok) {
        this.$router.push("/logs");
      } else {
        alert("로그인 실패!");
      }
    },
  },
};
</script>

Logout Button

<template>
  <button @click="logout">로그아웃</button>
</template>

<script>
export default {
  methods: {
    async logout() {
      await fetch("/api/logout");
      this.$router.push("/login");
    },
  },
};
</script>

4. 내용 정리

  1. 로그인 필요
    • 인증이 필요한 경로로 접근 시, 로그인하지 않았다면 /login 화면으로 리다이렉트된다.
  2. 로그인 성공
    • 로그인 시 세션에 사용자 정보가 저장되고, 인증 상태가 업데이트된다.
  3. 로그아웃
    • 로그아웃 시 세션이 무효화되며, /login 화면으로 이동한다.

기본적인 로그인/로그아웃 구성을 마치고 비즈니스 로직 개발에 일단 집중하자. Security 관련된 정책은 이후에도 변경되는 부분이 종종 생기기 때문에 처음에는 아주 간단한 구조만 준비하고 진행하면서 필요한 경우 적절히 추가하는 식으로 진행한다.

Leave a Comment