내일배움캠프

[내일배움캠프] Bean 수동 등록, 쿠키와 세션

munsik22 2026. 4. 7. 16:47

📚 목차

    🧩 Bean을 수동으로 등록하는 방법

    프로젝트 생성하기

    Bean 수동 등록

    • 일반적으로는 @Component를 사용하여 Bean을 자동으로 등록하는 것이 좋다.
    • 기술적인 문제나 공통적인 관심사를 처리할 때 사용하는 객체들을 수동으로 등록하는 것이 좋다.
      • 공통 로그처리와 같은 비즈니스 로직을 지원하기 위한 부가 적이고 공통적인 기능들을 기술 지원 Bean이라 부르고 수동등록한다.
      • 수동등록된 Bean에서 문제가 발생했을 때 해당 위치를 파악하기 쉽다는 장점이 있다.
    • /config/PasswordConfig.java : @Bean이 설정된 메서드 passwordEncoder로 Bean 이름이 설정된다.
    @Configuration
    public class PasswordConfig {
    
        @Bean // 수동 등록
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    • /test/.../PasswordEncoderTest.java
    package com.sparta.springauth;
    
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @SpringBootTest
    public class PasswordEncoderTest {
    
        @Autowired
        PasswordEncoder passwordEncoder;
    
        @Test
        @DisplayName("수동 등록한 passwordEncoder를 주입 받아와 문자열 암호화")
        void test1() {
            String password = "Robbie's password";
    
            String encodePassword = passwordEncoder.encode(password);
            System.out.println("encodePassword = " + encodePassword);
    
            String inputPassword = "Robbie";
    
            boolean matches = passwordEncoder.matches(inputPassword, encodePassword);
            System.out.println("matches = " + matches);
        }
    }

    🧩 같은 타입의 Bean이 2개라면?

    같은 타입 Bean 등록

    • food 인터페이스
    package com.sparta.springauth.food;
    
    public interface Food {
        void eat();
    }
    • Chicken (Bean)
    package com.sparta.springauth.food;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Chicken implements Food {
        @Override
        public void eat() {
            System.out.println("치킨을 먹습니다.");
        }
    }
    • Pizza (Bean)
    package com.sparta.springauth.food;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Pizza implements Food {
        @Override
        public void eat() {
            System.out.println("피자를 먹습니다.");
        }
    }

    • /test/BeanTest.java
    @SpringBootTest
    public class BeanTest {
        @Autowired
        Food food;
    }

    • 등록된 Bean 이름 명시하기
    @SpringBootTest
    public class BeanTest {
    
        @Autowired
        Food pizza;
    
        @Autowired
        Food chicken;
    
        @Test
        void test1() {
             pizza.eat();
             chicken.eat();
        }
    }
    @Autowired가 기본적으로는 Bean Type(Food)으로 DI를 지원하며, 연결이 되지않을 경우 Bean Name(pizza, chicken)으로 찾는 다는 것을 알 수 있다.

    • @Primary 사용하기: 같은 타입의 Bean이 여러 개 있더라도 우선 @Primary가 설정된 Bean 객체를 주입한다.
    @Component
    @Primary // ← 추가
    public class Chicken implements Food {
        @Override
        public void eat() {
            System.out.println("치킨을 먹습니다.");
        }
    }

    오류가 사라졌다.

    • @Qualifier 사용하기
      • 우선순위: @Qualifier > @Primary
      • 같은 타입의 Bean이 여러 개 있을 때는 범용적으로 사용되는 Bean 객체에는 @Primary를 설정하고 지엽적으로 사용되는 Bean 객체에는 @Qualifier를 사용하는 것이 좋다.
    일반적으로 Spring에서는 중복되는 설정 범위가 있을 때 좁은 범위의 설정이 더 우선순위가 높다.
    @SpringBootTest
    public class BeanTest {
    
        @Autowired
        @Qualifier("pizza") // ← 추가
        Food food;
    
        @Test
        void test1() {
            food.eat();
        }
    }

    🧩 인증과 인가란?

    인증 vs 인가

    • 인증(Authentication): 해당 유저가 실제 유저인지 인증하는 개념 (로그인)
    • 인가(Authorization): 해당 유저가 특정 리소스에 접근이 가능한지 허가를 확인하는 개념 (회원-비회원, 관리자 권환)

    웹 애플리케이션 인증의 특징

    • 비연결성(Connectionless): 서버와 클라이언트가 연결되어 있지 않음
    • 무상태(Stateless): 서버가 클라이언트의 상태를 저장하지 않음

    인증의 방식

    • 쿠키-세션 방식
      • 특정 유저가 로그인 되었다는 상태를 저장하는 방식
      • 인증과 관련된 아주 약간의 정보만 서버가 가지고 있게 되고 유저의 이전 상태의 전부는 아니더라도 인증과 관련된 최소한의 정보는 저장해서 로그인을 유지시킨다는 개념
    • JWT(JSON Web Token)
      • 인증에 필요한 정보들을 암호화시킨 토큰
      • JWT 기반 인증은 쿠키-세션 방식과 유사하게 JWT 토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별함

    🧩 쿠키와 세션이란?

    쿠키와 세션 모두 HTTP 에 상태 정보를 유지(Stateful)하기 위해 사용된다. 즉, 쿠키와 세션을 통해 서버에서는 클라이언트 별로 인증 및 인가를 할 수 있게 된다.

    쿠키와 세션

    • 쿠키
      • 클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일
      • (크롬 기준) Application-Storage-Cookies에 도메인 별로 저장됨
      • 구성 요소: Name, Value, Domain, Path, Expires
    • 세션
      • 서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용
      • 서버에서 클라이언트 별로 유일무이한 세션 ID를 부여한 후 클라이언트 별 필요한 정보를 서버에 저장함
      • 서버에서 생성한 세션 ID는 클라이언트의 쿠키값(세션 쿠키)으로 저장되어 클라이언트 식별에 사용됨
    구분 쿠키 세션
    설명 클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일 서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용
    저장 위치 클라이언트 (웹 브라우저) 웹 서버
    사용 예시 사이트 팝업의 오늘 다시보지 않기 정보 저장 로그인 정보 저장
    만료 시점 쿠키 저장 시 만료일시 설정 가능
    (브라우저 종료시도 유지 가능)
    ▪ 브라우저 종료 시까지
    ▪ 클라이언트 로그아웃 시까지
    ▪ 서버에 설정한 유지기간까지 해당 클라이언트의 재요청이 없는 경우
    용량 제한 ▪ 하나의 도메인 당 180개
    ▪ 하나의 쿠키 당 4KB
    개수 제한 없음
    보안 취약 비교적 안전

    쿠키 다루기

    • AuthController 생성
    @RestController
    @RequestMapping("/api")
    public class AuthController {
    
        public static final String AUTHORIZATION_HEADER = "Authorization";
    
        @GetMapping("/create-cookie")
        public String createCookie(HttpServletResponse res) {
            addCookie("Robbie Auth", res);
    
            return "createCookie";
        }
    
        @GetMapping("/get-cookie")
        public String getCookie(@CookieValue(AUTHORIZATION_HEADER) String value) {
            System.out.println("value = " + value);
    
            return "getCookie : " + value;
        }
    
        public static void addCookie(String cookieValue, HttpServletResponse res) {
            try {
                cookieValue = URLEncoder.encode(cookieValue, "utf-8").replaceAll("\\+", "%20");
    
                Cookie cookie = new Cookie(AUTHORIZATION_HEADER, cookieValue);
                cookie.setPath("/");
                cookie.setMaxAge(30 * 60);
    
                res.addCookie(cookie);
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e.getMessage());
            }
        }
    }
    • createCookie 테스트: 웹 브라우저에 http://localhost:8080/api/create-cookie를 입력한 후 접속을 하면…

    갑자기 만든 적도 없는 로그인 페이지가 나왔다???

    • SpringAuthApplication 수정: 기본 로그인 페이지를 없애기 위해서 Spring Security의 기능을 제한 해야한다고 한다.
    @SpringBootApplication(exclude = SecurityAutoConfiguration.class) // ← 추가
    public class SpringAuthApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringAuthApplication.class, args);
        }
    }

    ???

    • SecurityConfig 생성
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http
                    .csrf(csrf -> csrf.disable())
                    .authorizeHttpRequests(auth -> auth
                            .anyRequest().permitAll()
                    );
    
            return http.build();
        }
    
        @Bean
        public UserDetailsService userDetailsService() {
            return new InMemoryUserDetailsManager();
        }
    }
    • SpringAuthApplication 원복
    @SpringBootApplication
    public class SpringAuthApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringAuthApplication.class, args);
        }
    }

    이제 기본 로그인 페이지도 안 뜨고, 쿠키도 정상적으로 생성되었다.

    • getCookie 테스트: 웹 브라우저에 http://localhost:8080/api/get-cookie를 입력한 후 접속을 하면…

    브라우저에 저장된 쿠키가 정상적으로 출력되었다.

    • AuthController 수정: createSession(), getSession() 구현
        @GetMapping("/create-session")
        public String createSession(HttpServletRequest req) {
            HttpSession session = req.getSession(true);
    
            session.setAttribute(AUTHORIZATION_HEADER, "Robbie Auth");
    
            return "createSession";
        }
    
        @GetMapping("/get-session")
        public String getSession(HttpServletRequest req) {
            HttpSession session = req.getSession(false);
    
            String value = (String) session.getAttribute(AUTHORIZATION_HEADER);
            System.out.println("value = " + value);
    
            return "getSession : " + value;
        }