백엔드 공부 1년차 ... 이제 API도 만들 수 있고, AWS EC2, RDS, S3도 쓸 수 있고, 서버 배포도 할 수 있지만 아직도 회원가입/로그인은 하지 못한다...!
프로젝트를 할 때마다 잘하는 사람에게 맡기다 보니 직접 구현해볼 기회가 없었다. (>>> 핑계임)
이번 유니톤 참가 전까지 로그인/회원가입 + 소셜로그인도 구현할 수 있도록 해보자!
Spring Security란?
스프링 기반 어플리케이션의 보안(인증, 권한, 인가)을 담당하는 스프링 하위 프레임워크이다.
사용자의 요청에 대한 인증, 사용자가 권한이 있는지 확인하는 인가, 응답 등등 보안에 관련한 많은 부분을 제공해주고 있어 개발자의 입장에서는 하나하나 보안 관련 로직을 작성하지 않아도 된다는 장점이 있다.
🛰 Spring Security 용어
- 인증 (Authentication): 해당 사용자가 본인인지 확인하는 절차
- 인가 (Authorization): 인증된 사용자가 요청한 자원에 접근 가능한지 결정하는 절차
- 접근 주체 (Principal): 보호받는 Resource에 접근하는 대상
- 비밀번호(Credential): Resource에 접근하는 대상의 비밀번호
- 권한: 인증된 주체가 어플리케이션의 동작을 수행할 수 있도록 허락되어 있는지 결정
- 인증 과정을 통해 주체가 증명된 이후 권한을 부여할 수 있음
- 웹 요청 권한 / 메서드 호출 및 도메인 인스턴스에 대한 접근 권한 부여로 나뉨
Spring Security 과정은 크게 인증 → 인가로 진행된다.
인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하고 있다.
기본적으로 세션-쿠키 방식을 사용하고 있다.
Spring Security 동작 원리
🛰 로그인 절차
1. 요청
- 사용자가 로그인 정보를 요청
ex) 아이디와 비밀번호를 입력하여 서버로 전송
2. 토큰 생성
- AuthenticationFilter가 요청을 받음
- 토큰(인증용 객체) 생성 >
UsernamePasswordAuthenticationToken
3. AuthenticationFilter로부터 인증용 객체 전달 받음
- AuthenticationFilter는 토큰을 AuthenticationManager에게 보내면서 인증을 위임
ex) 여기 아이디와 비번이 담긴 토큰을 보낼테니 이걸로 회원가입한 유저인지 확인해줘! (인증)
4. 토큰을 처리할 수 있는 AuthenticationProvider 선택
- AuthenticationManager은 List 형태로 AuthenticationProvider를 가지고 있음
- AuthenticationProvider에게 인증용 객체를 다시 위임
- AuthenticationManager은 인증을 담당하는 클래스이지만, 실제 인증을 하는 것은 AuthenticationProvider 인터페이스
5. 인증 절차
- AuthenticationProvider 인터페이스는 DB에 있는 사용자 정보와 인증용 객체에 담긴 정보를 비교한다.
둘을 비교하기 위해서는 DB에 있는 사용자 정보와 인증용 객체 둘 다 가지고 있어야 한다.
1️⃣ DB에 있는 사용자 정보 가져오기
- AuthenticationProvider 인터페이스에서 DB에 있는 사용자 정보를 가져오기 위해서는
UserDetailsService
인터페이스를 통해 가져와야 한다.
UserDetailsService 인터페이스는 로그인 페이지에서 입력한 아이디를 통해 loadUserByUsername() 메서드를 호출하여 DB에 있는 정보를 UserDetails 타입으로 가져온다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return memberRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
}
2️⃣ 인증용 객체 가져오기
- AuthenticationProvider 인터페이스에서는 authenticate() 메서드를 오버라이딩하여 인증용 객체를 파라미터로 받아 사용자가 입력한 정보를 가지고 올 수 있다.
6. 인증 성공
- 인증에 성공하면, AuthenticationProvider에서 인증된 인증용 객체를 Authentication 객체에 담아 AuthenticationManager에게 전달한다.
- AuthenticationManager은 객체를 다시 AuthenticationFilter에게 전달한다.
7.AuthenticatioinSuccessHandler 실행
- AuthenticationFilter는 SecurityContextHolder라는 곳에 담은 후, AuthenticationSuccessHandler을 실행한다.
- 실패 시 AuthenticationFailureHandler 실행
- 이후 인증된 사용자가 다시 요청을 보내게 되면, SecurityContext 객체 안에 Authentication이 있는지 확인 후 있다면 바로 인과처리로 넘어감으로 인해 효율적 처리가 가능
🛰 Filters
- AuthenticationFilter
: 인증되지 않은 사용자와 인증된 사용자의 요청을 감시하고, AuthenticationManager에게 인증 처리를 맡김
- 인증 성공 → 인증용 객체를 AuthenticationContext에 저장 후 AuthenticationSuccessHandler 실행
- 인증 실패 → AuthenticationFailureHandler 실행
- AuthenticationProvider
: 입력한 정보와 DB에 저장된 정보를 비교
- 일치 → Authentication 객체에 담아 AuthenticationManager에게 전달
- 불일치 → 예외 처리
- UserDetailsService
: DB에서 유저 정보를 가져오는 역할
- UserDetails
: 사용자의 정보를 담은 인터페이스이며 직접 상속받아 사용
- 아래는 UserDetails에서 필수로 구현해야 하는 메서드들이다.
🔮 Reference
https://skatpdnjs.tistory.com/41
https://velog.io/@kyungwoon/Spring-Security-동작-원리