SPRING

Spring Security 정리

이충무 2022. 9. 21. 00:28
  • HTTP Session 기반의 구성
  • Spring Security 사용하는 이유
    1. 쿠키를 사용한 로그인 처리 → 서버에서 맴버의 정보를 가진 sessionId를 전달하고 서버에서는 쿠키 안에 sessionId의 value값을 통해 로그인 여부나 정보를 확인 문제점 키 값을 임의대로 변경할 수 있다. 쿠키에 보관된 정보(memberId) 를 타인이 도용하거나 이용할 수 있다.
    2. 세션과 쿠키를 사용한 로그인 처리 → 접근시 sessionId를 쿠키로 브라우저에게 주고 다음 접근 시 서버에서 sessionId를 통해 세션저장소에서 정보를 확인한다. 즉, 회원정보를 브라우저가 가지고 있지않고, 정보를 추측할 수 없는 sessionId를 통해 주고받아 안전하다. 문제점 세션기반 인증 방식을 사용하면 중앙 세션 관리 시스템에서 하나의 문제가 시스템 전체로 확산된다는 문제점이 있다. 사용자가 증가하면 해당 사용자가 가지고 있는 세션에만 통신하는 기술이 필요하다. ex) 세션1,세션2, 세션3가 있는 경우 나는 세션2에 자신의 정보가 있으면 세션2하고만 통신해야한다.
    3. Spring Security와 JWT를 이용한 로그인 처리 위와 같은 문제점을 통해 3번의 방식을 선택한다.

 

  • 스프링 시큐리티 구조

  • Authentication Provider는 인증 매니저가 어떻게 동작하는지 결정한다.(전달된 토큰의 타입을 처리할 수 있는지 확인)
  • 실제로 인증이 이루어 지는 것은 UserDetailService에서 이루어진다.
  • 핵심 개념 → 인증(Authentication ), 인가(Authorization)
    • 인증 → 너는 누구인가? or 너인것을 증명할 수 있나? (ex)로그인하는 경우)
    • 인가 → 너가 여기에 접근할 자격(권한)이 있나?

 

  • Authentication Filter와 Filter Chaining

    • 필터는 servlet, JSP에서 사용하는 필터와 같은 개념
    • 하지만, Spring Security에서는 Bean과 함께 사용하는 구조
    • 일반적으로 필터는 Bean을 사용하지 못해 특정 클래스를 상속받음
    • 여러 필터가 필터 체인 구조로 요청을 처리한다.

 

  • Authentication Manger
    • 필터의 동작은 Authentication Manger에 의해 인증 객체로 수행된다.
    • 즉, 받은 id와 비밀번호는 인증매니저를 통해 이루어진다.
    • 실제로 수행되는 파라미터는 UsernamePasswordAuthenticationToken과 같이 토큰으로 전달된다. → 필터의 주된 역할은 인증 정보를 토큰 객체로 만들어 전달하는 것!!
    • 인증 처리 방법은 DB를 사용하는지, 메모리의 정보를 활용하는 등 다양하다.
    • Authentication Manger는 앞선 처리를 Authentication Provider로 처리한다.
    • Authentication Provider는 UserDetailService를 사용하는데, UserDetailService는 인증을 위한 정보를 가져오는 역할을 한다. 그리고 인가의 과정을 거친다.

 

  • 인가 특정 리소스 설정
    • 1 - 설정을 통해서 패턴 지정
    @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http.authorizeHttpRequests((auth) -> {
                auth.antMatchers("/all").permitAll();
    						//USER만 접근할 수 있는 경로 설정
                auth.antMatchers("/member").hasRole("USER");
            });
    				
    				// 인증 or 인가 문제 발생시 로그인 화면(security 기본설정)
    				http.formLogin();
    				http.csrf().disable();
            http.logout();
            return http.build();
        }
    
    • 2 - 어노테이션

 

  • 일반적으로 로그인 처리를 할 경우, 아이디와 패스워드를 DB를 통해 조회하고 맞는 데이터가 있으면 세션이나 쿠키로 처리합니다.
  • 하지만, Spring Security는 조금 다른 방식으로 처리합니다.
    1. 1. 회원이나 계정을 User라고 표현, 회원 아이디는 username이라고 표현(이름X)
    2. 2. username과 password 따로 사용, UserDetailService를 통해서 회원이 있는지만 확인하고, 그 후 password가 틀리면 Bad Cridential이 나온다.(인증)
    3. 3. username과 password가 끝나면 원하는 URL에 접근할 수 있는지 인가 과정을 진행한다. 만약 없다면 Access Denied이 나온다.
  • 위와 같이 과정을 진행하려면 UserDetailService는 필수이다. UserDetailService는 loadUserByUsername() 메소드 단 하나를 가지고 있다.
  • loadUserByUsername()는 리턴타입은 UserDetails 타입이고 이를 통해 알아낼 수 있는 정보가 있습니다.
    • getAuthorities()- 사용자의 권한에 대한 정보
    • getPassword() - 인증을 위한 패스워드 정보
    • getUsername() - 인증을 위한 아이디와 같은 정보
    • 계정 만료 여부
    • 계정 잠김 여부
  • 위의 것을 처리하기 위해 두가지 방법이 존재합니다
    • 기존 DTO 클래스에 UserDetails interface를 구현
    • DTO 개념의 별도 클래스를 구성
  • UserDetails를 구현한 클래스가 이미 있는데 User, Person, InetOrgPerson, LdapUserDetailsImpl이 있다.

 

  • 컨트롤러에서 로그인된 정보 확인법
    1. SecurityContextHolder 객체 사용
    2. 직접 파라미터와 어노테이션을 사용 → @AuthenticationPrincipal

 

  • 필터는 왜 사용할까?
    • 외부에서 제약없이 호출하는 것은 서버에 부담을 줄 수있다. 그래서 필터를 거쳐 인증을 완료한 사람에게 서비스를 제공하려는 목적이 있다.
    • 쿠키나 세션의 경우, 사이트 내부에서 동작하기 때문에 API 서버 처럼 외부에서 데이터를 주고받을 때는 인증 키(토큰)를 사용합니다.
    • 즉 외부에서 API 호출시 꼭 인증에 필요한 토큰을 같이 전송하여 서버에서 확인합니다.
    • 이 과정에서 필요한 것이 필터입니다.
    • filter의 동작 순서를 변경하려면 addFilterBefore()을 사용하여 임의적으로 바꿀 수 있다.

 

  • OncePerRequestFilter → 매번 동작하는 기본 필터라고 생각하면 된다.
    • doFilterInternal()
    • 아무런 코드없이 돌려도 OncePerRequestFilter를 상속한 클래스가 작동하는 것을 볼 수 있다.
    • AntPathMatcher를 사용하여 패턴에 맞는지 확인합니다. 즉, 패턴에 맞는 경로만 처리되는 것을 확인할 수 있다.

 

  • AbstractAuthenticationProcessingFilter
    • attemptAuthentication()

 

  • Authorization 헤더처리
    • 특정 API를 호출하는 클라이언트는 다른 서버에서 실행되어 쿠키나 세션사용이 불가하다.
    • 그래서 특정 API를 호출할 때 Request전송 시 HTTP 헤더 메세지에 Authorization 헤더의 값을 지정해 전송한다.
    • 필터에서 Authorization 헤더값이 맞으면 계속 진행되지만, 아닐 경우 다른 메세지를 보내야합니다. 필터에 그것을 정의하는 메서드를 따로 생성해야한다.
    • Authorization 헤더에 따라 동작하는 코드가 완성되면 외부에서 인증할 수 있는 인증처리와 필터가 사용할 Authorization 헤더의 값을 만들어줘야합니다.
    • 내부적으로 AuthenticationManager를 사용해 동작해야한다.
    • AuthenticationManager는 authenticate()메소드를 가지고 있고 파라미터와 리턴 타입이 Authentication으로 동일하다.
    • 파라미터로 오는 id와 패스워드는 암호화가 필수!

 

  • JWT 토큰
    • 인증을 성공하면특정 API를 호출할 경우 적절한 토큰을 만들어서 전송해야합니다. 보통 JWT토큰을 사용합니다. 이유 추가 정리
    • 인증이 성공한 사람은 JWT토큰을 받고, API 호출 시 Request가 정상적으로 요청됬는지를 확인할 수단으로 사용될 것입니다.
    • JWT는 Header,Payload,Signature로 구성됩니다.
      • Header → 토큰 타입과 알고리즘 → HS256 or RSA
      • Payload → 이름과 값의 쌍을 Claim이라 하는데 이것을 모아둔 객체를 의미합니다.
      • Signature → Header와 Payload는 Base64로 인코딩하는데 이것을 다른 사람이 확인할 수 없도록 비밀 키로 해시함수로 처리된 결과를 말합니다.
      • 사용하는 경우
        1. 인증에 성공하면 JWT 문자열을 만들어 클라이언트에게 전송
        2. 클라이언트가 보낸 토큰을 검증할 때
      • 코드 구현시 유효 기간을 정하고 토큰을 생성하는 메소드와 토큰의 값을 추출해 검증하는 메소드를 만들어 사용한다.
      • 필터 내부에서 Authorization 헤더를 가지곻 검증하는 역할을 수행하는 메소드를 만든다.(이댸 JWT는 Basic이 아닌 Bearer을 사용)

 

  • CORS 필터
    • REST API 테스트를 무사히 마쳐도, 외부에서 ajax를 이용해 API문제를 해결하려면 결국 CORES필터를 설정해야한다.

 

  • 인증로직 구조
    • HTTP 리퀘스트 요청은 그대로 인증 필터로 들어온다
    • 여기서 토큰을 체크하는 토큰 유효성 검사가 들어가고 토큰이 없다면 컨트롤러에게 토큰 발급을 요청한다
    • 토큰 발급 요청을 받으면 Token Provider가 위치한 필터 체인의 Authentication Manager로 요청이 넘어간다
    • Authentication Manager는 UserDetailsService를 통해 객체 정보(ID/PW)를 저장/검증하고 다시 컨트롤러에게 정보를 넘긴다
    • 컨트롤러는 받은 정보를 통해 토큰을 발급받는다
    • 발급된 토큰을 컨트롤러가 다시 유저에게 넘긴다
    나만의 정리
    1. http 요청이 들어온다.
    2. SecurityConfig에서 request를 토대로 AuthenticationManagerBuilder 를 UserDetailsService를 통해 저장/검증을 하여 만들어주고 사용자의 인증을 처리합니다. → 동작방식 : AuthenticationManager 가 적절한 AuthenticationManagerProvider 를 찾아 authenticate()를 통해 인증을 시도
    3. 빌더를 통해 만든 AuthenticationManager 로 인증을 처리(ApiLoginFilter- attemptAuthentication) 이후, 성공 및 실패 핸들러 처리
    4. 로그인이 성공하면 인증헤더의 값으로 이용할 데이터(토큰)을 만들어(JWTUtils-generateToken) 전송한다(ApiLoginFilter-successfulAuthentication 내에서 JWTUtils를 이용해 문자열 발행).(ApiCheckFilter - checkAuthHeader메소드) *(여기서는 동일한 계정으로 일정시간동안 API를 호출하도록 구성)
    5. 이후에 검증이 필요한 경우 (JWTUtils-validateAndExtract) 검증을 시도