예전에 spring boot에 spring security를 이론은 제쳐두고 적용해서 글을 쓴적이 있는데
다시 프로젝트에 사용할 일이 있어서 오랜만에 제 글을 다시 보니 역시 뭐가 뭔지 하나도 모르겠더라고요;;;
다시 공부하는겸 이번 글은 간단하게 이론편으로 글을 올리고 예전적어둔 글을 적용편으로 사용하도록 하겠습니다.
Spring Security(스프링 시큐리티)란?
스프링 시큐리티는 스프링 기반의 애플리케이션의 보안(인증과 권한,인가 등)을 담당하는 스프링 하위 프레임워크이다. 주로 서블릿 필터와 이들로 구성된 필터체인으로의 위임모델을 사용한다. 그리고 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안관련 로직을 작성하지 않아도 된다.
출처: https://coding-start.tistory.com/153 [코딩스타트]
Spring Security의 동작 원리
클라이언트는 앱에 요청을 보내고 컨테이너는 요청 URI의 경로를 기반으로 어떤 필터와 어떤 서블릿이 적용 할 것인지 결정합니다.
Spring Security는 FilterChainProxy라는 이름으로 내부에 여러 Filter들이 동작하고 있습니다.
(단일 물리적 Filter이지만 처리를 일련의 내부 필터에 위임합니다.)
Spring Security FilterChainProxy는 요청을 일치하는 첫 번째 체인으로 발송합니다.
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
...;
}
}
실제로도 위와 같은 방식으로 WebSecurityConfigurerAdapter를 상속받은 형태로 구현합니다.
(. . .)은 http.antMatcher("/bar/**"), http.antMatcher("/**")이런식으로 계속 이어서 구현합니다.
말그대로 체인을 만들어 냅니다.
이때 적는 순서도 중요합니다.
예를 들어 관리자 페이지가 존재하는 홈페이지경우 URL이 admin/**으로 시작된다고 합시다
유저가 잘못된 경로인 admin/**으로 접근하는 경우를 관리자가 막고 싶어합니다.
그렇다면 아래 둘의 경우중 뭐가 순서상 맞는 설정일까요?
1. http.antMatcher("admin/**").hasRole("ADMIN")
.antMatcher("/**")..permitAll();
2. http.antMatcher("/**")..permitAll().
antMatcher("admin/**").hasRole("ADMIN");
정답은 .... 1번입니다.
Path의 권한 설정은 위에서 부터 차례대로 적용되기 때문에
2번같은 경우는 anonymous가 admin/** URL로 접근해도
첫번째 필터에서 모든 접근을 허용해 주어서
두번째 필터부터는 사실상 의미가 없어집니다.
필터를 적용할때는 좁은 범위부터 차례로 적어주도록 합시다.
자세한 내용은 https://myserena.tistory.com/158
이정도면 어느정도 spring security의 동작의 흐름은 눈에 들어옵니다.
더 자세히 보실분은 https://spring.io/guides/topicals/spring-security-architecture#_web_security
다시 돌아와 저희는 이제 어떻게 구현할것인가?를 생각해봐야 됩니다.
위의 사진은 Form 기반 로그인에 대한 플로우를 보여주는 그림입니다.
1. 사용자가 Form을 통해 로그인 정보를 입력하고 인증 요청을 보냅니다.
2. AuthenticationFilter가 HttpServletRequest에서 사용자가 보낸 아이디와 패스워드를 인터셉트한다.
프론트 단에서 유효성검사를 할 수도 있지만, 안전을 위해서 다시 한번 사용자가 보낸
아이디와 패스워드의 유효성 검사를 해줍니다.(아이디 혹은 패스워드가 null인 경우 등)
HttpServletRequest에서 꺼내온 사용자 아이디와 패스워드를 진짜 인증을 담당할
AuthenticationManager 인터페이스(구현체 - ProviderManager)에게 인증용 객체 (UsernamePasswordAuthenticationToken)로 만들어줘서 위임한다.
3. AuthenticationFilter에게 인증용 객체(UsernamePasswordAuthenticationToken)을 전달받는다.
4. 실제 인증을 할 AuthenticationProvider에게 Authentication객체(UsernamePasswordAuthenticationToken)을
다시 전달한다.
5. DB에서 사용자 인증 정보를 가져올 UserDetailsService 객체에게 사용자 아이디를 넘겨주고 DB에서 인증에 사용할 사용자 정보(사용자 아이디, 암호화된 패스워드, 권한 등)를 UserDetails(인증용 객체와 도메인 객체를
분리하지 않기 위 해서 실제 사용되는 도메인 객체에 UserDetails를 상속하기도 한다.)라는 객체로 전달 받는다.
6. AuthenticationProvider는 UserDetails 객체를 전달 받은 이후 실제 사용자의 입력정보와 UserDetails 객체를 가지고 인증을 시도한다.
7. [8, 9, 10] 인증이 완료되면 사용자 정보를 가진 Authentication 객체를 SecurityContextHolder에 담은 이후 AuthenticationSuccessHandle를 실행한다.(실패시 AuthenticationFailureHandler를 실행한다.)
https://coding-start.tistory.com/153
사실 spring security 자체가 난이도가 높습니다. 하지만 구현만 하면 되는 입장에선
이 순서를 외울 필요도 없고 알고 싶지도 않습니다. 심지어
위 구조를 개발자가 다 커스텀마이징 할필요도 없습니다.
가장 중요한 부분은 4번 부터 8번까지 (Manager로 부터 값을 받아와 Porvider 부터 UserDetails 까지)
이부분만 자기가 커스텀 마이징 할줄알면 됩니다.
그래서 1~3 정리를 하면
1. AuthenticationFilter가 Request 요청을 받음
Filter에서 아이디와 비밀번호가 null인지 체크
자바스크립트로도 개발자가 하지만 시큐리티가 안전을 위해 한번 더해준다는 의미입니다.
2. Filter로 유효하다 판단되면 인증용 객체(토큰)이 생성
3. ProviderManager는 실제 인증을 할 AuthenticationProvider 에게 전달한다.
4번(AuthenticationProvider)은 위에서 예를 들은 WebSecurityConfigurerAdapter을 상속받은 Class로
구현 하게 됩니다.
이제 4,5,6,7,8번 코드를 짜러 가봅시다.
2020/03/24 - [spring boot] - [spring security] springboot에 가장 빠르게 적용해 보자[2]