티스토리 뷰

SpringBoot

Spring Security 동작 원리

nswon 2022. 7. 17. 16:16

Spring Security란 ?

스프링에서 제공하는 보안 관련 프레임워크다. 사용자의 요청에 대한 인증, 사용자가 권한이 있는지 확인하는 인가, 응답 등등 보안에 관련해서 많은 부분을 제공해주고 있기 때문에 개발자가 하나하나 보안에 대응하는 코드를 짜지 않아도 되는 장점이 있다. 대부분 우리는 Spring Security를 로그인 유지/페이지 권한 등에 사용한다.

 

Spring Security를 좀 더 이해하기 위한 용어 의미들

위에서 Spring Security의 의미를 설명하면서 인증과 인가에 대해서 말했다. 그럼 인증과 인가의 정확한 의미는 뭘까 ?
인증, 인가 뿐만 아니라 나중에 나오는 중요한 용어들도 함께 정리해보았다.

 

  • 인증(Authentication) : 사용자가 본인인지 확인하는 절차
  • 인가(Authorizatioin) : 인증된 사용자가 요청한 경로에 사용자가 접근이 가능한지(권한이 있는지) 결정하는 절차
  • 접근주체(Principal) : 보호받는 경로(권한이 있어야 접근 가능한 경로)에 접근하려고 하는 사용자
  • 비밀번호(Credential) : 경로에 접근하려고 하는 사용자의 비밀번호. 다른말로 접근주체의 비밀번호라고도 할 수 있다.


용어 의미에서도 알 수 있다시피 Spring Security 과정은 크게 인증 -> 인가로 진행을 하게되며, 이러한 인증과 인가를 위해 Principal를 아이디로, Credential을 비밀번호로 사용하고 있다. Spring Security는 기본적으로 세션 - 쿠키 방식을 지원한다.

 

Spring Security 아키텍쳐

 


Spring Security에 대한 글을 많이 봤는데, 가장 많이 보였던 이미지가 바로 위 이미지가 아닐까 싶다. (그만큼 중요하다는 이야기이다.) Spring Security를 누군가 알려주면 좋겠다는 강력한 생각이 들었던 가장 큰 이유가 바로 이름이 엄청나게 긴 필터들과 각각의 역할... 그리고 그 필터들이 아주아주 많아 헷갈린다는 것이다. 여기서 조언해주고 싶은 점은 절대 필터에 집중하지 말자. 우리는 지금 전체적인 흐름을 보기 위한 것이지, 필터의 역할이 뭔지 하나하나 따지는 시간이 아니다. 그럼 로그인 과정의 전체적인 흐름을 정리해보겠다.

 

로그인 절차

1-4까지의 흐름사진


1. 요청
사용자가 로그인 정보를 요청한다.
예시) 로그인페이지에서 아이디와 비밀번호를 입력한다. 그럼 아이디와 비밀번호가 서버로 전송하게 된다.

2. 토큰 생성
AuthenticationFilter가 요청을 받아서 UsernamePasswordAuthenticationToken 토큰(인증용 객체)을 생성한다.
예시) 사용자가 보낸 아이디와 비번을 AuthenticationFilter가 받고 아이디와 비번을 UsernamePasswordAuthenticationToken 토큰(인증용 객체)에 담는다.

 

3. AuthenticationFilter로부터 인증용 객체를 전달 받는다.
AuthenticationFilter는 UsernamePasswordAuthenticationToken 토큰을 AuthenticationManger에게 보내면서 인증을 진행하라고 위임한다.
예시) AuthenticationManger에게 "여기 아이디와 비번이 담긴 토큰을 보낼테니 이걸로 회원가입을 한 유저인지(인증) 확인해줘" 라고 떠넘기는 것이다.

4. 토큰을 처리할 수 있는 AuthenticationProvider 선택
AuthenticationManager는 List 형태로 AuthenticationProvider들을 가지고 있는데, 실제로 인증을 할 AuthenticationProvider에게 인증용 객체를 다시 위임한다.
예시) AuthenticationManager는 인증을 담당하는 클래스이지만, 진짜는 AuthenticationManager가 가지고 있는 AuthenticationProvider 인터페이스들한테 "이거(인증용 객체) 줄테니 한번 인증처리가 가능한 AuthenticationProvider는 인증처리 해줘" 라고 또 떠넘기는 방식이다.

5-6까지의 흐름사진


5. 인증절차
선택된 AuthenticationProvider 인터페이스는 DB에 있는 사용자 정보와 인증용 객체에 담긴 정보와 비교한다.
예시) 선택된 AuthenticationProvider 인터페이스는 DB에 있는 사용자 정보(회원가입한 사용자 정보)와 로그인페이지에서 입력한 정보를 비교한다.

어떻게 비교할까 ?
일단 비교하기 전에 AuthenticationProvider 인터페이스는 DB에 있는 사용자 정보와 인증용 객체(로그인페이지에서 입력한 정보) 둘 다 가지고 있어야 한다. 그럼 어떻게 가져오는지 알아보자.

 5-1. DB에 있는 사용자 정보 가져오기
AuthenticationProvider 인터페이스에서 DB에 있는 사용자 정보를 가져오려면 UserDetailsService 인터페이스를 통해 가져와야 한다.
UserDetailsService 인터페이스는 로그인페이지에서 입력한 아이디(username)를 통해 loadUserByUsername() 메서드를 호출하여
DB에 있는 사용자 정보를 UserDetails 형태로 가져온다. 만약 사용자의 정보가 존재하지 않다면 예외를 던진다.
이해를 돕기 위해 잠깐 코드를 보여주겠다.

 


위에서도 말했다시피, loadUserByUsername() 메서드를 재정의해 로그인페이지에서 입력한 아이디를 매개변수로 받고, 그 매개변수로 DB에 있으면 UserDetails로 반환, 없다면 UsernameNotFoundException 예외를 던지는 것을 볼 수 있다.

5-2. 인증용객체(로그인페이지에서 입력한 정보) 가져오기
AuthenticationProvider 인터페이스에서는 authticate() 메서드를 오버라이딩해 인증용객체를 파라미터로 받아 로그인페이지에서 입력한 사용자의 정보를 들고 올 수 있다. 

6. 인증성공
인증에 성공하게 되면 AuthenticationProvider에서 인증된 인증용 객체(로그인페이지에서 입력한 정보)를 Authentication객체에 담아 AuthenticationManager한테 Authentication객체를 보내면서 알려준다.
예시) 회원가입된 정보와 로그인페이지에서 입력한 정보가 일치하면 로그인페이지에서 입력한 정보를  Authentication객체에 담아 AuthenticationManager한테 보내지고, AuthenticationManager는 다시 AuthenticationFilter에게 전달한다.

 

7 사진


7. Authentication객체를 전달받은 AuthenticationFilter는 SecurityContextHolder라는 곳에 담은 후 AuthenticationSuccessHandler을 실행한다. (실패시 AuthenticationFailureHandler을 실행한다)
예시) 이제 SecurityContextHolder는 1차캐시 역할을 담당한다. 이후 인증된 사용자가 다시 요청을 보내게 되면, 먼저 SecurityContext객체 안에 Authentication이 있는지 확인 후 있다면 바로 인과처리로 넘어감으로 인해 효율적인 처리가 된다.
위 사진을 보자. 방금도 말했다시피 SecurityContextHolder는 SecurityContext를 감싸고 있는 wrapper 클래스며, 실제로
Authentication객체에 접근할 땐 SecurityContext객체를 통해 꺼내올 수 있다.

자, 전체적인 흐름을 이해했으면 각 필터들의 역할이 무엇인지 따지는 시간을 가져보자.
이 밑에 내용은 위 내용을 정확히 이해했다면 Filter들의 역할을 이해하기 수월할 것이다. 

 

각 Filter들의 역할

 

  • AuthenticationFilter
  • AuthenticationProvider
  • UserDetailsService
  • UserDetails

 

AuthenticationFilter

인증되지 않은 사용자와 인증된 사용자의 요청을 감시하고, AuthenticationManager에게 인증처리를 맡김
인증이 성공한다면 인증용 객체를 AuthenticationContext 객체에 저장하고 AuthenticationSuccessHandler 실행
실패 시 AuthenticationFailureHandler 실행

 

AuthenticationProvider

로그인페이지에서 입력한 정보와 DB에 저장된 정보를 비교
일치하다면 Authentication객체로 담아 AuthenticationManager에게 전달
만약 DB에 저장된 정보들 중 로그인페이지에서 입력한 정보와 일치하는 정보가 없다면 UsernameNotFoundException예외 처리

 

UserDetailsService

DB에서 유저정보를 가져오는 역할담당

 

UserDetails

사용자의 정보를 담은 인터페이스이며 직접 상속받아 사용한다.
유저객체에서 UserDetails를 상속받게 되면 구현해야할 메서드들이 있으며, 각 메서드들의 정의는 다음과 같다.


아래는 UserDetails를 상속받아 메서드들을 구현한 코드다.

 

마치며

이번 포스팅을 통해서 Spring Security의 전체적인 흐름과 각 Filter들의 역할에 대해서 알아보았다.
위 글들을 100퍼센트 이해했으면, 실전코드도 어렵지 않을 것이다. 제일 어려운 이론을 마스터한 것이다.

다음포스팅에서는 jwt와 실제로 코드로 구현해 토큰을 얻기까지 과정을 설명하도록 하겠다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday