본문 바로가기

Study

[내일배움캠프 TIL] 13일차 - security context, Authentication, @Secured

728x90
반응형

어제에 이어서 권한 부여, 확인 작업을 진행하였습니다.

Security context에 AuthenticationToken을 만들어서 넣어주고

그것으로 인증을 하고, controller에서도 가져와서 활용할 수 있다는 것을 알았습니다.

그리고 권한을 확인 할때 @Secured를 이용하여

각 api 함수에 필요한 권한을 달아주었습니다.

 

 


🛡️ [Spring Security] SecurityContext 완벽 정리

1. SecurityContext란?

인증이 완료된 사용자의 상세 정보(Authentication)를 애플리케이션이 실행되는 동안 임시로 저장하는 보관함입니다.

  • 핵심 역할: "누가 지금 우리 서비스를 이용 중인가?"에 대한 답을 들고 있습니다.
  • 유효 범위: 하나의 HTTP 요청(Request)이 들어와서 응답(Response)이 나갈 때까지 유지됩니다. (ThreadLocal 기반)

2. 저장 구조 (계층도)

보안 정보는 마치 '러시아 인형(마트료시카)'처럼 겹겹이 쌓여 관리됩니다.

  1. SecurityContextHolder: 보안 정보의 가장 바깥쪽 가방. 어디서든 접근할 수 있는 통로입니다.
  2. SecurityContext: 가방 안에 든 지갑. Authentication 객체 하나를 품고 있습니다.
  3. Authentication: 지갑 안의 신분증. 유저의 ID(Principal), 권한(Authorities), 비번(Credentials) 등이 들어 있습니다.

3. 필터(Filter)에서의 동작 흐름

우리가 만든 JwtAuthorizationFilter가 하는 일이 바로 이 흐름을 타는 것입니다.

  1. 검사: 요청 헤더에서 JWT 토큰을 꺼내 유효성을 검사합니다.
  2. 생성: 토큰이 유효하면 DB를 조회해 유저 정보(UserDetails)를 담은 Authentication 객체를 만듭니다.
  3. 저장: 이 객체를 SecurityContext에 넣고, 최종적으로 SecurityContextHolder에 보관합니다.
  4. 예시 코드
     
     
     
     
  5. 활용: 이후 컨트롤러나 서비스 어디서든 SecurityContextHolder.getContext().getAuthentication()을 통해 현재 로그인한 유저 정보를 꺼내 쓸 수 있습니다.
    // 인증 처리
    public void setAuthentiaction(String username){
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication authentication = createAuthentication(username);
        context.setAuthentication(authentication);

        SecurityContextHolder.setContext(context);
    }

    // 인증 객체 생성
    private Authentication createAuthentication(String username){
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }

 

 

** 필터 만든 후 꼭 추가해주어야함.

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정 (H2 콘솔 사용을 위해 비활성화)
        http.csrf(AbstractHttpConfigurer::disable);
        // H2 콘솔의 iframe 허용
        http.headers(headers -> headers
                .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
        );

        // 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
        http.sessionManagement((sessionManagement) ->
                sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );

        http.authorizeHttpRequests(auth -> auth
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                .requestMatchers("/h2-console/**").permitAll()
                .requestMatchers("/api/v1/auth/signup", "/api/v1/auth/login").permitAll()
                .anyRequest().authenticated()
        );

        //필터 관리
        http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);  // <- 추가한 부분
        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

 

 

 

🆔 [Spring Security] Authentication 객체 정리

1. Authentication이란?

현재 애플리케이션에 접근한 사용자가 **"누구인지(인증)"**와 **"무엇을 할 수 있는지(권한)"**를 증명하는 객체입니다.

  • 저장소: SecurityContext 내부에 저장됩니다.
  • 생성 시점: 로그인 성공 시(인증 필터) 또는 유효한 토큰 검증 완료 시(인가 필터) 생성됩니다.

2. Authentication의 3대 핵심 구성 요소

신분증에 적힌 항목들을 상상해 보세요.

  1. Principal (주체)
    • 정체: 사용자의 ID나 유저 객체 자체.
    • 실제 코드: 보통 우리가 만든 UserDetailsImpl 객체가 여기 담깁니다. "이 신분증의 주인"을 뜻합니다.
  2. Credentials (자격 증명)
    • 정체: 사용자가 본인임을 증명하기 위해 제시한 비밀번호나 토큰.
    • 특징: 보안을 위해 인증이 완료된 후에는 보통 null로 비워두는 경우가 많습니다. (유출 방지)
  3. Authorities (권한)
    • 정체: 유저가 가진 역할 리스트 (ROLE_CUSTOMER, ROLE_MASTER 등).
    • 형태: GrantedAuthority 객체의 컬렉션(Collection)으로 관리됩니다.

3. 인증 전과 후의 차이 (Authentication의 상태)

이 객체는 인증 과정에서 **'미인증 상태'**에서 **'인증 완료 상태'**로 진화합니다.

  • 인증 전: 사용자가 아이디/비번만 보낸 상태. (아직 신뢰할 수 없는 가짜 신분증)
  • 인증 후: AuthenticationManager가 "이 사람 진짜 맞음!" 하고 도장을 꽝 찍어준 상태. 이때 isAuthenticated = true가 되고 SecurityContext에 보관됩니다.

 

4. 코드에서 꺼내 쓰는 법

컨트롤러에서 @AuthenticationPrincipal을 썼던 이유가 바로 이 신분증의 Principal 부분을 바로 꺼내기 위해서입니다.

// 1. 수동으로 꺼내기
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) auth.getPrincipal();

// 2. 어노테이션으로 우아하게 꺼내기 
public ResponseEntity<?> getMyInfo(@AuthenticationPrincipal UserDetailsImpl loginUser) {
    // loginUser가 바로 Authentication 내부의 Principal입니다.
}

 

🔒 [Spring Security] @Secured 간단 정리

1. @Secured란?

메서드 단위에서 **"이 기능을 쓸 수 있는 등급(Role)"**을 아주 직관적으로 지정하는 어노테이션입니다.

  • 역할: 특정 메서드 실행 전, 현재 유저의 권한을 체크해서 입구 컷을 결정합니다.
  • 활성화 방법: SecurityConfig에 @EnableMethodSecurity(securedEnabled = true) 설정이 반드시 필요합니다.

2. 사용법 (배달 앱 예시)

딱 보고 "아, 이 등급만 되는구나!" 알 수 있는 게 최대 장점입니다.

// 1. 단일 권한
@Secured("ROLE_MASTER")
public void deleteUser() { ... }

// 2. 다중 권한 (OR 조건)
@Secured({"ROLE_OWNER", "ROLE_MASTER"})
public void updateStore() { ... }

3. 핵심 포인트 (주의사항)

  • 전통적인 방식: 스프링 시큐리티 초기부터 있던 방식이라 설정이 매우 단순합니다.
  • 문자열 기반: 반드시 ROLE_ 이라는 접두사를 직접 붙여줘야 정확히 인식합니다. (예: ROLE_MASTER)
  • OR 조건: 여러 권한을 넣으면, 그중 하나라도 가지고 있으면 통과됩니다.

4. @PreAuthorize와 차이점

  • @Secured: "너 이 등급이야?" (단순 등급 확인용)
  • @PreAuthorize: "너 이 등급이거나, 아니면 이 데이터 주인이니?" (SpEL을 사용한 복잡한 로직 확인용)
반응형