[Spring] SpringSecurity

쓰옹·2022년 12월 27일
0

개념

스프링 서버에 필요한 인증/인가를 위해 기능을 제공하는 프레임워크

  • 표준 서블릿 필터로 서플릿컨테이너와 통합되어 서블릿 컨테이너에서 실행되는 모든 어플리케이션에서 동작한다

프레임워크 추가

build.gradle

// 스프링 시큐리티
implementation 'org.springframework.boot:spring-boot-starter-security'

Spring Boot Auto Configuration

  • 스프링부트가 자동으로 해줌
    • springSecurityFilterChain 서블릿 filter빈 생성 → 보안처리 담당
      • 서블릿컨테이너에 등록해 모든 요청에 적용
    • UserDetailsService 빈 생성
      • user 와 랜덤 비밀번호 (콘솔 출력) 를 사용한 폼 기반 인증 지원
    • 어플리케이션 모든 상호작용에 사용자 인증 요구
    • 디폴트 로그인 폼 생성
    • BCrypt로 저장할 비밀번호보호
    • 사용자 로그아웃 지원
    • CSRF 공격 방어 - disable
      • CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조) 공격
        • 사용자(희생자)가 자신의 의도와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 만드는 공격
        • 공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용하여 공격
        • 공격 조건
          • 위조 요청을 전송하는 서비스에 희생자가 로그인 상태
          • 희생자가 해커가 만든 피싱 사이트에 접속
        • CSRF disable
          • Spring Security에서 기본적으로 CSRF에 대한 공격 방지 수행
          • REST API 방식은 쿠키나 세션에 의존하지 않는 경향이 크기 때문에 CSRF 공격에 대한 방어 설정을 비활성화시키는 경우가 많음
          • 쿠키 대신에 로컬 스토리지(localStorage)와 요청 헤더(Request Header) 사용하거나, 세션 대신에 JWT(Json Web Token)을 사용하기 때문
          • POST 요청마다 처리해 주는 대신 CSRF protection 을 disable

Spring Security 주요 컴포넌트

  • Filter
    • Spring Security는 요청이 들어오면 Servlet FilterChain을 자동으로 구성한 후 거치게 함
    • Filter - 웹 컨테이너에서 관리되는 서블릿의 기술
      • Client 요청이 전달되기 전후의 URL 패턴에 맞는 모든 요청에 필터링을 해줌
        • CSRF, XSS 등의 보안검사를 통해 올바른 요청이 아닐 경우 차단
    • 인증/인가 구현
  • SecurityFilterChain
    • Filter session, jwt 등의 인증방식을 사용하는 데 필요한 설정을 완전히 분리할 수 있는 환경 제공
    • RequestMatcher 인터페이스를 사용하면 URL 뿐 아니라 HttpServletRequest 에 있는 거라면 실행 여부를 결정할 수 있음
  • 스프링 시큐리티 필터 실행 순서

Authentication 주요 컴포넌트

스프링 시큐리티에서 지원하는 인증

  • SecurityContextHolder - 인증한 대상에 대한 상세정보 저장
  • SecurityContext - SecurityContextHolder 로 접근, 현재 인증한 사용자의 Authentication 을 갖고 있음
  • Authentication - 사용자의 credential 제공, AuthentiacationManager 의 입력으로 사용
  • GrantedAuthority - Authentication 에서 접근 주체(Principal)에 부여한 권한 (role, scope 등)
  • AbstractAuthenticationProcessingFilter - 인증에 사용할 필터의 베이스
  • AuthenticationManager - 스프링 시큐리티의 필터가 인증을 어떻게 수행할지 정의하는 API
  • 인증 메카니즘
    • username and password

    • OAuth 2.0 Login - OpenID connect. 소셜네트워크 로그인

      등등…

SecurityContextHolder

  • 스프링 시큐리티는 여기에 값이 있다면 현재 인증한 사용자 정보로 사용함. 어떻게 값을 넣는지는 상관하지 않음
  • Setting SecrutiyContextHolder 코드 예시
    // SpringSecurity docs
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    Authentication authentication =
        new TestingAuthenticationToken("username", "password", "ROLE_USER");
    context.setAuthentication(authentication);
    
    SecurityContextHolder.setContext(context);
    
    // MySelectShop (JwtAuthFilter.java)
    public void setAuthentication(String username) {
    		SecurityContext context = SecurityContextHolder.createEmptycontext();
    		Authentication authentication = jwtUtil.createAuthentication(username);
    		context.setAuthentication(authentication);
    
    		SecurtiyContextHolder.setContext(context);
    }
    • empty SecurityContext 생성
      • 멀티스레드 경합을 피하기 위해SecurityContextHolder.getContext().setAuthentication(authentication) 대신 new SecurityContext 를 생성
    • new Authentication 객체 생성
      • Authentication 구현체라면 모두 SecurityContext에 담을 수 있다. 주로 UsernamePasswordAuthenticationToken(userDetials, password, authorities) 를 사용
    • set the SecurityContext on the SecurityContextHolder
  • 인증된 주체 (principal) 정보를 얻어야 한다면 SecurityContextHolder 에 접근하면 된다.
    // Access Currently Authenticated User
    SecurityContext context = SecurityContextHolder.getContext();
    Authentication authentication = context.getAuthentication();
    String username = authentication.getName();
    Object principal = authentication.getPrincipal();
    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

Authentication

  • AuthenticationManager의 입력으로 사용
  • 현재 인증된 사용자 정보
    • principal : 사용자 식별. username/password로 인증 시 보통 UserDetails 인스턴스임
    • credential : password. 대부분 사용자 인증에 사용한 다음 비움
      • isAuthenticated()false 를 리턴
    • authorities : 사용자에게 부여한 권한을 GrantedAuthority로 추상화
    // MySelectShop
    public class UserDetailsImpl implements UserDetails {
    		...
    
    		@Override
    		public Collection<? extends GrantedAuthority> getAuthorities() {
    				UserRoleEnum role = user.getRole();
    				String authority = role.getAuthority();
    				
    				SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthroty(authority);
    				Collection<GrantedAuthority> authorities = new ArrayList<>();
    				authorities.add(simpleGrantedAuthority);
    
    				return authorities;
    		}
    
    		...
    }	
    
    // Authentication 객체 생성 시
    Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

💡 UsernamePasswordAuthenticationTokenAuthentication을 implements한 AbstractAuthenticationToken의 하위 클래스로 인증 객체를 만드는 데 사용됨

AbstractAuthenticationProcessingFilter

  • credential(인증정보)을 인증하기 전에 SpringSecurity는 보통 AuthentiacationEntryPoint 를 사용해서 credential을 요청고 그 후 이 필터는 제출한 모든 요청을 처리함

  1. 사용자가 credential을 제출하면 HttpServletRequest에서 Authentication 을 만듦
    • Authentication 타입은 AbstractAuthenticationProcessingFilter 의 하위클래스에 따라 다름
      • UsernamePasswordAuthenticationFilter ⇒ 제출된 HttpServletRequest 에 있는 username 과 password로 UsernamePasswordAuthenticationToken 생성
  2. Authentication 을 AuthenticationManager로 전달되어 인증됨
  3. 인증 실패 시
    1. SecurityContextHolder 를 비움
    2. RememberMeServices.loginFail 실행. RememberMe를 설정하지 않았다면 아무것도 실행되지 않음
    3. AuthenticationFailureHandler 실행
  4. 인증 성공 시
    1. SessionAuthenticationStrategy 에 로그인 했음을 통보
    2. SecurityContextHolderAuthentication세팅 후 SecurityContextPersistenceFilterHttpSessionSecurityContext 저장
    3. RememberMeServices.loginSuccess실행. 노설정이면 무동작
    4. ApplicationEventPublisher - InteractiveAuthenticationSuccessEvent 발생

UsernamePasswordAuthenticationFilter

  • AbstractAuthenticationProcessingFilter를 상속한 Filter

  • Reading the Username & Password

    • 스프링시큐리티에서 제공하는 HttpServletRequest 에서 username 과 password를 읽을 수 있는 기본 메커니즘
      • Form
      • Basic
      • Digest
  • Form Login

    • html 폼 기반 username/password 인증

    • 인증이 필요한 URL 요청이 들어왔을 때 인증이 되지 않았다면 로그인 페이지를 반환

      1. 권한이 없는 리소스(/private)에 인증되지 않은 요청을 보냄
      2. FilterSecurityInterceptor에서 AccessDeniedException 을 던져 인증되지 않은 요청이 거절됨을 알림
      3. ExceptionTranslationFilter 에서 인증을 시작하고 AuthenticationEntryPoint 에서 설정한 LoginUrl 페이지로 리다이렉트 응답을 전송
      4. 리다이렉트된 로그인 페이지 요청
      5. 로그인 페이지 렌더링
    • 스프링 시큐리티에선 폼 로그인이 디폴트로 활성화됨

      • Default Form Log In

        // SpringSecurity docs 
        protected void configure(HttpSecurity http) {
        		http
        				...
        				.formLogin(withDefaults()));
        }
        
        // MySelectShop (WebSecurityConfig.java)
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        			...
        			
        			// 로그인 사용
        			http.formLogin();
        
        			return http.build();
        }
      • Custom Log in Form Configuration

        // SpringSecurity docs
        protected void configure(HttpSecurity http) throws Exception {
        		http
        				...
        				.formLogin(form -> form
        						.loginPage("/login")
        						.permitAll()
        				);
        }
        
        // MySelectShop (WebSecurityConfig.java)
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        			...
        			
        			// Custom 로그인 페이지 사용
        			http.formLogin().loginPage("/api/user/login-page").permitAll();
        
        			return http.build();
        }

UserDetails

  • UserDetailsService가 리턴하는 값
  • DaoAuthenticationProviderUserDetails를 인증하고 이걸 principal로 가진 Authentication을 리턴
  • 검증된 UserDetails는 UsernamePasswordAuthenticationToken 타입의 Authentication을 만들 때 사용, 해당 인증객체는 SecurityContextHolder에 세팅
  • Custom하여 사용가능

UserDetailsService

  • username/password로 인증 시 사용자를 조회하고 검증한 후 UserDetails 를 반환
  • 커스텀하여 Bean으로 등록 후
public class UserDetailsImpl implements UserDetails {
					...
			// 인증이 완료된 사용자 추가
			// 사용자의 권한 GrantedAuthority로 추상화 및 반환
			// Getter
					...
}

public class UserDetailsServiceImpl implements UserDetailsService{
					...
			@Override
			UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
												...
			}
}
  • 인증객체 생성 시 사용
// MySelectShop (JwtUtil.java)
public Authentication createAuthentication(String username) {

		UserDetails userDetails = userDetailsService.loadUserByUsername(username);
		return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

}

PasswordEncoder

  • 비밀번호 암호화
  • 회원 등록 시 비밀번호 암호화 후 저장
  • PasswordEncoder 구현체를 커스텀하려면 빈을 정의하면 됨
    // MySelecShop (WebSecurityConfig.java)
    
    @Bean
    public PasswordEncoder passwordEncoder() {
    		return new BCryptPasswordEncoder();
    }
    • 스프링시큐리티가 제공하는 적응형 단방향 함수 bCrypt 사용

💡 적응형 단방향 함수는 내부적으로 리소스의 낭비가 심해 성능이 떨어질 수 있기 때문에 세션, 토큰 등의 인증방식을 사용하여 검증하는 것이 속도 및 보안 측면에서 유리함

  • 양방향, 단방향
    • 양방향 암호 알고리즘
      • 암호화 : 평문 —(암호화 알고리즘)—> 암호문
      • 복호화 : 암호문 —(암호화 알고리즘)—> 평문
    • 단방향 암호 알고리즘
      • 암호화 : 평문 —(암호화 알고리즘)—> 암호문
      • 복호화 : 불가 암호문 —(암호화 알고리즘)—> 평문
      • 확인절차 사용자) 로그인- 아이디, 패스워드(평문) 입력 → 서버에 로그인 요청 → 서버) 패스워드 암호화 → DB 저장된 아이디, 패스워드(암호문)과 일치 여부
  • Password Matching
    • 일치여부 확인 함수

      boolean matches(CharSequence rawPassword, String encodedPassword);
      
      // 사용 예시
      if (!passwordEncoder.matches( 입력한 비밀번호, 저장된 비밀번호)) {
      			throw new IllegalAccessError("비밀번호가 일치하지 않습니다.");
      }


reference

https://itstory.tk/entry/CSRF-공격이란-그리고-CSRF-방어-방법

https://junhyunny.github.io/information/security/spring-boot/spring-security/cross-site-reqeust-forgery/

Spring Security Documentation

profile
기록하자기록해!

0개의 댓글