스프링시큐리티

박민주·2024년 2월 11일
0

스프링시큐리티

목록 보기
6/6

스프링 시큐리티란?

스프링 시큐리티는 스프링 기반의 어플리케이션의 보안(인증과 권한)을 담당하는 프레임워크이다.
만약 스프링 시큐리티를 사용하지 않았다면, 자체적으로 세션을 체크하고 리다이렉트 등을 해야 한다.
스프링 시큐리티는 보안과 관련해서 체계적으로 많은 옵션들을 지원해준다.
스프링 시큐리티는 필터(Filter) 기반으로 동작하기 때문에 스프링 MVC 와 분리되어 관리 및 동작합니다.

시큐리티가 필요한 이유

웹사이트는 각종 서비스를 하기 위한 리소스와 서비스를 사용하는 유저들의 개인 정보를 가지고 있습니다. 이들 리소스를 보호하기 위해서 일반적으로 웹 사이트는 두가지 보안 정책을 설정해야 합니다.

  • 서버 리소스
  • 유저들의 개인정보

스프링 시큐리티가 관심을 가지고 있는걸 크게 두가지로 나눈다면 하나는 인증이고 하는 권한이다.

인증

사이트에 접근하는 사람이 누구인지 시스템이 알아야 합니다. 익명사용자(anonymous user)를 허용하는 경우도 있지만, 특정 리소스에 접근하거나 개인화된 사용성을 보장 받기 위해서는 반드시 로그인하는 과정이 필요합니다. 로그인은 보통 username / password 를 입력하고 로그인하는 경우와 sns 사이트를 통해 인증을 대리하는 경우가 있습니다.

  • Username/Password 인증
    • session 관리
    • 토큰관리(sessionless)
  • Sns로그인(소셜 로그인) : 인증위임

전통적인 사이트들은 누가 로그인 했는지에 대한 정보를 가지는 세션을 이용했었지만 최근에는 여러대의 서버로 서비스하는 경우가 많기때문에 세션을 서로 분리해서 저장하고 동기화하기 때문에 토큰방식을 쓰는 경우가 많다.

인가 혹은 권한(Authorization)

사용자가 누구인지 알았다면 사이트 관리자 혹은 시스템은 로그인한 사용자가 어떤 일을 할 수 있는지 권한을 설정합니다. 권한은 특정 페이지에 접근하거나 특정 리소스에 접근할 수 있는 권한여부를 판단하는데 사용됩니다. 개발자는 권한이 있는 사용자에게만 페이지나 리소스 접근을 허용하도록 코딩해야 하는데, 이런 코드를 쉽게 작성할 수 있도록 프레임워크를 제공하는 것이 스프링 시큐리티 프레임워크(Spring Security Framework) 입니다.

권한을 체크하는 것을 Authorization이라고 한다. 권한은 어노테이션으로 체크를 한다. 비즈니스 로직이 복잡해지면 어노테이션으로 하기 어렵다 이런경우 AOP를 이용해서 권한 체크를 하는경우도 있다. 비즈니스가 복잡해질수록 다양한 방법이 있을 수 있다.

  • Secured : deprecated
  • PrePOstauthorize
  • AOP

메모리 사용자 인증

간단히 특정된 소스를 위한 서비스나 테스트를 위해 사용하는 용도로 사용합니다. 스프링 시큐리티를 테스트 하기 위한 용도로 사용합니다.

  • 기본 사용자 로그인
  • application.yml 에 설정하고 로그인하기
  • UserDetailService 를 이용하기
  • WebSecurityConfigurerAdapter 를 사용하기(Deprecated)

application.yml에 설정하고 로그인하기

스프링 시큐리티가 설정되면 서버가 만들어놓은 패스워드를 가지고 접근해야한다. 이런경우 기억하기 어렵기 때문에 yml 에서 아래와 같이 설정하면 쉽게 이용가능하다.

spring.security.user:
name:user1
paswword:1111
roles:USER

스프링 시큐리티의 큰 그림

서블릿 컨테이너

톰캣과 같은 웹 애플리케이션을 서블릿 컨테이너라고 부르는데,(서버가 여러개의 서블릿들을 담고 있기 때문이다.) 이런 웹 애플리케이션(J2EE Application)은 기본적으로 필터와 서블릿으로 구성되어있다.

컨트롤러의 메소드에 URL을 매핑을 해서 웹 리퀘스트가 그 메소드를 찾아가게 하는 방법은 바로 dispathcServlet때문인데, dispathcServlet은 등록된 컨트롤러와 메소드들에서 URL과 매칭이 되는 메소드를 찾아서 실행시켜주는 역할을 한다.

필터는 세션을 매칭시켜 준다거나, 인코딩을 맞춰준다거나 등등 모든 리퀘스트에 대해서 공통적으로 동작해야 하는것들을 주로 필터에 위치시키게된다.

필터들은 체인으로 엮어 있기때문에 리퀘스트가 서블릿의 메소드에 도착하려면 반드시 필터를 하나하나 거쳐야한다.

스프링 시큐리티의 큰 그림

하지만 시큐리티의 경우에는 여러가지 정책이 존재할 수 있다. 필터체인에 여러정책이 하나로 묶여 들어가면 문제가 생긴다. 중간의 프록시를 통해서 선택적으로 필터체인을 스위칭해가면서 사용해야 한다. 그래서 스프링 시큐리티는 DelegatingFilterProxy 라는 필터를 만들어 메인 필터체인에 끼워넣고, 그 아래 다시 SecurityFilterChain 그룹을 등록한다.

  • 이 필터체인은 반드시 한개 이상이고, url 패턴에 따라 적용되는 필터체인을 다르게 할 수 있다. 본래의 메인 필터를 반드시 통과해야만 서블릿에 들어갈 수 있는 단점을 보완하기 위해서 필터체인 Proxy 를 두었다고 할 수 있다.
  • web resource 의 경우 패턴을 따르더라도 필터를 무시(ignore)하고 통과시켜주기도 한다.

시큐리티 필터들

시큐리티 필터체인에 어떤 필터들을 넣을수있는가 ? 하는것이 시큐리티의 가장 큰 부분이기도 하다 스프링 시큐리티 라이브러리가 제공해주는 라이브러리가 많고, 대부분의 필터는 각각 다른 관심사를 필터링한다.

  • HeaderWriterFilter : Http 헤더를 검사한다. 써야 할 건 잘 써있는지, 필요한 해더를 더해줘야 할 건 없는가?
  • CorsFilter : 허가된 사이트나 클라이언트의 요청인가?
  • CsrfFilter : 포스트나 풋과같이 서버리소스를 변경하는 요청의 경우 내가 내보낸 리소스에서 올라온 요청인가?
  • LogoutFilter : 지금 로그아웃하겠다고 하는건가?
  • UsernamePasswordAuthenticationFilter : username / password 로 로그인을 하려고 하는가? 만약 로그인이면 여기서 처리하고 가야 할 페이지로 보내 줄께. 승인이 되면 Authentication을 유저에게 부여해주거나 실패처리를 한다.
  • ConcurrentSessionFilter : 여거저기서 로그인 하는걸 허용할 것인가?(동시접근)
  • BearerTokenAuthenticationFilter : Authorization 해더에 Bearer 토큰이 오면 인증 처리 해줄께.
  • BasicAuthenticationFilter : Authorization 해더에 Basic 토큰을 주면 검사해서 인증처리 해줄께.
  • RequestCacheAwareFilter : 방금 요청한 request 이력이 다음에 필요할 수 있으니 캐시에 담아놓을께.
  • SecurityContextHolderAwareRequestFilter : 보안 관련 Servlet 3 스펙을 지원하기 위한 필터라고 한다.(?)
  • RememberMeAuthenticationFilter : 아직 Authentication 인증이 안된 경우라면 브라우저의 RememberMe 쿠키를 검사해서 인증 처리해줄께. - Remember는 보통 세션이 완료되더라도 재 로그인 없이도 쓸수 있게 해주는것이다.
  • AnonymousAuthenticationFilter : 앞선 필터에서 아직도 인증이 안되어 Authentication이 채워지지 않았다면 너는 Anonymous(익명) 사용자로 인식해서 Anonymous로 Authentication를 채워줘서 null인걸 방지한다.
  • SessionManagementFilter : 서버에서 지정한 세션정책을 검사할께.
  • ExcpetionTranslationFilter : 나 이후에 인증이나 권한 예외가 발생하면 내가 잡아서 처리해 줄께.
  • FilterSecurityInterceptor : 여기까지 살아서 왔다면 인증이 있다는 거니, 니가 들어가려고 하는 request 에 들어갈 자격이 있는지 그리고 리턴한 결과를 너에게 보내줘도 되는건지 마지막으로 내가 점검해 줄께. 만약 실패가 된다면 ExcpetionTranslationFilter로 간다.

그 밖에... OAuth2 나 Saml2, Cas, X509 등에 관한 필터들도 있다.

로그인 하기

스프링 프레임워크에서 로그인을 한다는 것은 authenticated 가 true인 Authentication 객체를 SecurityContext 에 갖고 있는 상태를 말합니다. 단 Authentication이 AnonymousAuthenticationToken 만 아니면 됩니다.

로그인 == Authentication(authenticated = true) 
only if Authentication != AnonymousAuthenticationToken

Authentication(인증)

인증은 '증명하다'라는 의미로 예를 들어, 유저 아이디와 비밀번호를 이용하여 로그인 하는과정 을 말합니다.

  • spring security filter : 일련의 spring security filter는 각 요청을 가로채고 함께 작동하여 인증이 필요한지 여부를 식별한다. 인증이 필요한 경우 사용자를 로그인 페지로 이동하거나 초기 인증 시 저장된 기존 세부정보를 사용한다.
  • Authentication : UsernamePasswordAuthenticationFilter와 같은 필터는 HTTP 요청에서 사용자 이름/비밀번호를 추출하고 인증 유형 개체를 준비합니다.
  • Authentication Manager : 필터로부터 요청을 받으면 사용자 세부 정보의 유효성 검사를 사용 가능한 Authentication Provider에게 위임합니다. 여러 provider가 존재 할 수 있으므로 사용가능한 Authentication Provider를 관리하는 것은 Authentication Manager의 책임입니다.
  • Authentication Provider : Authentication Provider에는 인증을 위해 사용자 세부 정보를 확인하는 모든 핵심 논리가 있습니다.
  • UserDetailManager/UserDetailsService : DB/스토리지 시스템에서 사용자 세부 정보를 검색, 생성, 업데이트, 삭제하는데에 도움이 됩니다.
  • PasswordEncoder : 비밀번호 인코딩 및 해싱에 도움이 되는 서비스 인터페이스 입니다. 그렇지 않다면 암호화 되지 않은 일반 텍스트 비밀번호를 사용해야 할수도 있다.
  • SecurityContext : 요청이 인증되면 일반적으로 SecurityContextHolder가 관리하는 쓰레드-로컬 SecurityContext에 인증이 저장됩니다. 이는 동일한 사용자의 향후 요청에 도움이 됩니다.

스프링 시큐리티에서 Authentication은 Authorization으로 구성이 된다. 필터들 중에 일부 필터는 인증 정보에 관여한다. 이들 필터가 하는 일은 AuthenticationManager 를 통해 Authentication 을 인증하고 그 결과를 SecurityContextHolder 에 넣어주는 일입니다.

스프링 시큐리티에서 로그인을 하는 것은 Authentication을 제공(Provide)하는 AuthentificationProvider(인증제공자)는 한 개이상 존재할 수 있고, AuthentificationProvider를 관리하는 인터페이스 AuthentificationManager를 구현하고 있는 구현체는 providerManager도 인증방식에 따라 복수로 존재 할 수 있다. 페어런트 구조로 레퍼런스를 하고 있는 구조를 띄고 있다,

인증 토큰(Authentication)을 제공하는 필터들

  • UsernamePasswordAuthenticationFilter : 폼 로그인 -> UsernamePasswordAuthenticationToken
  • RememberMeAuthenticationFilter : remember-me 쿠키 로그인 -> RememberMeAuthenticationToken
  • AnonymousAuthenticationFilter : 로그인하지 않았다는 것을 인증함 -> AnonymousAuthenticationToken
  • SecurityContextPersistenceFilter : 기존 로그인을 유지함(기본적으로 session 을 이용함)
  • BearerTokenAuthenticationFilter : JWT 로그인
  • BasicAuthenticationFilter : ajax 로그인 -> UsernamePasswordAuthenticationToken
    - Authorization헤더에 베이직 토큰에 유저네임, 패스워드를 담아서 base 64로 인코딩해서 보내주면 서버에서 필터에서 로그인 인증을 해주고 요청을 수행해주는 역할
    • 로그인 페이지가 필요없는 SPA나 모바일경우에 사용할 수 있다. session이 있는경우 많이 사용한다. 없는 경우에는 JWT로그인을 많이 사용한다.
  • OAuth2LoginAuthenticationFilter : 소셜 로그인 -> OAuth2LoginAuthenticationToken, OAuth2AuthenticationToken
  • OpenIDAuthenticationFilter : OpenID 로그인
  • Saml2WebSsoAuthenticationFilter : SAML2 로그인
  • ... 기타

Authentication은 인터페이스

  • Set<GrantedAuthority> authorities : 인증된 권한 정보
  • principal : 인증 대상에 관한 정보. 주로 UserDetails 객체가 옴
  • credentials : 인증 확인을 위한 정보. 주로 비밀번호가 오지만, 인증 후에는 보안을 위해 삭제함.
  • details : 그 밖에 필요한 정보. IP, 세션정보, 기타 인증요청에서 사용했던 정보들.
    - UserDetails와는 다르다.
  • boolean authenticated : 인증이 되었는지를 체크함.

굳이 Authentication에 넣어놓은 이유는 SecurityContext holder로 데이터가 들어가게 되면 컨트롤러 안이나 서비스 안에서 정보를 접근하게 되는데 이 경우 Request 객체에서 접근할 수 있는 값들이 필요한 경우에는 불필요하게 HTTP 서블릿 리퀘스트를 서비스까지 끌고 들어가야 하는걸 방지하기 위해서이다.

폼 로그인

DefaultLoginPageGeneratingFilter

  • GET /login 을 처리
  • 별도의 로그인 페이지 설정을 하지 않으면 제공되는 필터
  • 기본 로그인 폼을 제공
  • OAuth2 / OpenID / Saml2 로그인과도 같이 사용할 수 있음.

UsernamePasswordAuthenticationFilter

  • POST /login 을 처리. processingUrl 을 변경하면 주소를 바꿀 수 있음.
  • form 인증을 처리해주는 필터로 스프링 시큐리티에서 가장 일반적으로 쓰임.
  • 주요 설정 정보
    • filterProcessingUrl : 로그인을 처리해 줄 URL (POST)
    • username parameter : POST에 username에 대한 값을 넘겨줄 인자의 이름
    • password parameter : POST에 password에 대한 값을 넘겨줄 인자의 이름
    • 로그인 성공시 처리 방법
      • defaultSuccessUrl : alwaysUse 옵션 설정이 중요
      • successHandler
    • 로그인 실패시 처리 방법
      • failureUrl
      • failureHandler
      • authenticationDetailSource : Authentication 객체의 details 에 들어갈 정보를 직접 만들어 줌.

DefaultLogoutPageGeneratingFilter

  • GET /logout 을 처리
  • POST /logout 을 요청할 수 있는 UI 를 제공
  • DefaultLoginPageGeneratingFilter 를 사용하는 경우에 같이 제공됨.

LogoutFilter

  • POST /logout 을 처리. processiongUrl 을 변경하면 바꿀 수 있음.
  • 로그 아웃을 처리
    - session, SecurityContext, csrf, 쿠키, remember-me 쿠키 등을 삭제처리 함.
    - (기본) 로그인 페이지로 redirect
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	if (requiresLogout(request, response)) {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug(LogMessage.format("Logging out [%s]", auth));
		}
		this.handler.logout(request, response, auth);
		this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
		return;
	}
	chain.doFilter(request, response);
}
  • LogoutHandler
    - void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication);
    - SecurityContextLogoutHandler : 세션과 SecurityContext 를 clear 함.
    - CookieClearingLogoutHandler : clear 대상이 된 쿠키들을 삭제함.
    - CsrfLogoutHandler : csrfTokenRepository 에서 csrf 토큰을 clear 함.
    - HeaderWriterLogoutHandler
    - RememberMeServices : remember-me 쿠키를 삭제함.
    - LogoutSuccessEventPublishingLogoutHandler : 로그아웃이 성공하면 이벤트를 발행함.

  • LogoutSuccessHandler
    - void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
    throws IOException, ServletException;
    - SimpleUrlLogoutSuccessHandler

참고

https://gitlab.com/jongwons.choi/spring-boot-security-lecture/-/blob/master/part0/2%20%EC%8A%A4%ED%94%84%EB%A7%81%20%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0%EB%9E%80.md?ref_type=heads
https://m.boostcourse.org/web326/lecture/58997

profile
개발자 되고싶다..

0개의 댓글