본문 바로가기
Backend boot camp/Session4

[Spring Security] Spring Security 기본

by orioncsy 2022. 11. 19.

Spring Security

개념

Spring Security

  • Spring MVC 기반 애플리케이션의 인증과 인가 기능을 지원하는 보안 프레임워크
  • Intereptor나 servlet filter를 이용해서 보안 기능을 구현 가능하지만 spring security에서 보안 기능 지원

Spring Security 보안 강화 기능

  • 사용자 인증 기능(폼 로그인 인증, 토큰 기반 인증, OAuth 2 기반 인증, LDAP 인증) 적용
  • 애플리케이션 사용자의 역할에 따른 권할 레벨 적용
  • 애플리케이션 제공 리소스에 대한 접근 제어
  • 민감 정보 암호화
  • SSL 적용
  • 웹 보안 공격 차단
  • SSO, client 인증서 기반 인증, 메서드 보안, 접근 제어 목록

사용 용어

  • Principal
    • 애플리케이션에서 작업을 수행할 수 있는 사용자, 디바이스, 시스템
    • 인증 프로세스가 성공적으로 수행된 사용자 계정 정보
  • Authentication 인증
    • 사용자가 본인이 맞음을 증명
    • Credential 신원 증명 정보 : 사용자 식별을 위한 정보
  • Authorization 인가, 권한 부여
    • 인증이 이루어진 사용자에게 권한을 부여하여 특정 리소스 접근 허용
    • 인증 과정 이후 진행되어야 하고 권한은 role 형태로 부여
  • Access Control 접근 제어
    • 사용자가 애플리케이션 리소스 접근 행위를 제어

사용목적

spring security 사용 목적

  • Apache Shiro, OACC 같은 보안 프레임워크가 존재하지만 spring security가 기능을 모두 가지고 지원하고 있다.
  • 대부분의 보안 요구사항 만족
  • 특정 보안 요구 사항은 코드의 커스터마이징이 spring security에서 가능

1. Spring Security 기본 구조 - 로그인, 로그아웃

SSR 방식의 샘플 애플리케이션

  • Spring Security 기본 구조를 이해하기 위해서 SSR 방식 샘플 애플리케이션 사용
  • 세션 기반의 폼 로그인 방식을 적용하기 좋은 SSR 방식 적용

Spring Security 적용 과정

  • gradle dependencies에 추가
    • implementation 'org.springframework.boot:spring-boot-starter-security'
  • Spring security의 자동 구성을 통해 내부적으로 제공해주는 디폴트 로그인 페이지 접속 가능
  • 등록된 회원이 없을 경우 로그인을 할 때 username과 password의 디폴트 제공 정보도 존재
    • username : user
    • password는 애플리케이션 실행할 때 로그로 Using generated security password: 뒤에 나오는 문자열
    • 그러나 애플리케이션을 실행할 때마다 비밀번호가 바뀌고 디폴트 인증으로 회원 각자의 인증 정보로 로그인하는 것은 불가능

Spring Security Configuration 적용

  • 클래스 생성(SecurityConfiguration)
  • @Configuration 애너테이션을 붙여서 클래스를 생성하고 @Bean을 붙여 메서드를 생성
  • 생성된 매서드는 UserDetailsManager를 반환형으로 갖는다.
  • UserDetails 객체를 생성하고 그 구현체인 User를 만들어서 할당
    • User.withDefaultPasswordEncoder().username(”….”).pasword(”…”).roles(”USER”).build();
    • 디폴트 패스워드 인코더를 이용해 패스워드 암호화
    • username에 식별 가능한 사용자 아이디
    • password()는 비밀번호 설정
    • roles()는 역할 지정 메서드(크게 사용자와 관리자 구분)

HTTP 보안 구성 기본

  • 위에서 생성된 클래스에서 HttpSecurity 객체를 매개변수로 받고 SecurityFilerChain을 리턴하는 메서드 정의하여 HTTP 보안 설정을 구성
  • @Bean으로 빈 등록을 한다.
  • 매개 변수로 들어온 객체에 메서드를 적용
    • .csrf().disable() 을 통해 CSRF 공격에 대한 spring security 설정 비활성화
      • 설정을 하지 않으면 csrf 공격 방지를 위해 CSRF Token을 수신 후 검증
      • 로컬 환경이라 비활성화하고 진행
    • formLogin()을 통해 기본적인 인증방법을 폼 로그인 방식으로 지정
    • loginPage(”…”)을 통해 커스텀 로그인 페이지 설정
      • 파라미터는 핸들러 메서드에 요청을 전송하는 요청 URL
    • loginProcessingUrl(”…”) 메서드를 통해 로그인 인증 요청을 수행할 요청 URL 지정
    • failureUrl()을 통해 인증 실패 시 보이는 화면을 지정
    • and() 메서드로 spring security 보안 설정을 메서드 체인 형태로 구성
    • authorizeHttpRequests() 메서드를 통해 접근 권한을 확인
    • anyRequest().permitAll()을 통해 모든 요청에 대해 접근 허용

Request URI에 접근 권한 부여

  • SecurityConfiguration 클래스에 들어가서
  • filterChain 메서드에서 http에 붙인 메서드들을 수정한다.
  • .authorizeHttpRequests().anyRequest().permitAll() 을 지우고
  • .exceptionHandling().accessDeniedPage("/auths/access-denied")을 넣어 접근 권한이 없는 사용자가 접근할 경우 403 에러를 처리하는 페이지 설정
  • .authorizeHttpRequests()에서 매개변수로 람다식으로 정의하고
  • 람다식에서 .antMatchers(”…”).hasRole(”ADMIN”) 등으로 권한을 설정
    • ant라는 빌드 툴에서 사용되는 path pattern을 이용
    • 매개변수로 경로를 작성하는데 “/orders/**”는 /orders로 시작하는 모든 URL 접근 허용이라는 뜻
    • “/**”는 앞에서 지정한 URL 외 모든 URL에 대한 것을 의미
    • 따라서 구체적인 접근권한을 먼저 부여해야 한다.
    • .permitAll()을 통해 모든 role에 대해 접근 허용 가능

관리자 권한을 가진 사용자 정보 추가

  • SecurityConfiguration에 들어가서
  • UserDetailsManager를 반환하는 메서드 안에 UserDetails 객체를 만들고
  • 위에서 했던 방식과 마찬가지로 작성한 뒤 .roles(”ADMIN”)을 설정하고
  • return new InMemoryUserDetailsManager(user, admin);
    • 매개변수에 정의한 UserDetails 객체를 넣고 반환

사용자 로그아웃

  • header.html에 기능 추가
    • thymeleaf 기반 HTML 템플릿에서 인증 정보나 권한 정보를 이용한 로직 처리를 위해 sec 태그 사용을 위한 XML 네임스페이스 지정
    • grandle dependencies에 아래 추가(sec태그 사용을 위해)
      • implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5’
    • <span sec:authorize="isAuthenticated()">
      • 인증에 성공한 사용자이면 태그 하위 콘텐츠 표시
    • <span sec:authorize="hasRole('USER')">
      • USER role을 가진 사용자에게 표시
    • <span th:text="${#authentication.name}"></span>
      • 로그인 사용자의 username 표시
  • SecurityConfiguration 클래스
    • filterChain 메서드에 http에 메서드 추가
    • .logout().logoutUrl(”/logout”).logoutSuccessUrl(”/”).and()
    • 로그 아웃하였을 때, 로그아웃을 수행하는 URL, 로그아웃 성공 후 화면 URL을 지정

2. Spring Security 기본 구조 - 회원가입

1) 데이터베이스 연동 없는 로그인 인증

  • Spring Security에서 지원하는 InMemory user를 사용하면 DB 연동 없이 회원 등록 가능

회원가입 폼을 통한 InMemory User 등록

  • PasswordEncoder Bean 등록
    • 회원가입 폼에서 받은 패스워드를 암호화하기 위함
  • SecurityConfiguration 클래스에
    • @Bean으로 등록하여 PasswordEncoder을 반환하는 passwordEncoder 메서드 선언
    • 메서드 안에 PasswordEncoder 구현체 생성
      • return PasswordEncoderFactories.createDelegatingPasswordEncoder();

MemberService Bean 등록을 위한 JavaConfiguration 구성

  • 편의상 MemberService 인터페이스로 구현
    • createMember 메서드로 member를 받아 반환
  • InMemoryMemberService라는 클래스로 MemberService 구현
  • 데이터베이스에 User 등록을 위해 DBMemberService 클래스 구현(MemberService의 구현체로 선언)
    • @Transactional로 생성
  • JavaConfiguration 클래스를 생성하고 @Configuration으로 설정
  • 메서드로 @Bean을 선언한 inMemoryMemberService 선언
    • MemberService를 반환형으로 하고 UserDetailsManager 객체와 PasswordEncoder 객체를 매개변수로 설정
    • InMemoryMemberService를 생성해서 반환(생성자 매개변수에 위의 두 객체 설정)

InMemoryMemberService 구현

  • UserDetailsManager, PasswordEncoder 객체 DI로 설정
  • createMember 메서드 작성
    • List<GrantedAuthority> authorities로 선언하고 권한을 생성하는 메서드를 작성해서 할당한다.
    • passwordEncoder.encode(member.getPassword())를 통해 비밀번호 암호화한다.
    • UserDetails 객체를 선언해서 User를 생성자로 생성해 할당(member의 식별자, 암호화된 비밀번호, 위에서 구현한 authorities)
    • userDetailsManager.createUser(생성한 userDetails 객체)로 회원 생성
    • return member;
  • 권한을 생성하는 메서드는 String… roles로 가변 인자로 문자열을 입력받아서 List <GrantedAuthority>로 반환
    • roles를 스트림으로 변환해서 map을 통해 SimpleGrantedAuthority로 각자 변환해주고 리스트로 바꾸어서 반환한다

2) 데이터베이스 연동을 통한 로그인 인증

  • InMemory User를 사용하는 인증 방식은 앱을 다시 실행하면 초기화된다.

방법 1. Custom UserDetailsService를 사용하는 방법

  • User의 인증 정보를 테이블에 저장하고 이것을 이용해 인증 프로세스 진행 가능
    • 인증을 시도하는 주체를 User(혹은 Prinicipal)
  • SecurityConfiguration의 설정 변경
    • http 객체에 .headers().frameOptions().sameOrigin().and() 추가
      • H2 웹 콘솔을 사용하기 위한 설정
      • frameOptions()는 HTML 태그 중 <frame>, <iframe>, <object> 태그에서 페이지 렌더링 여부 결정
      • clickjacking 공격을 막기 위해 디폴트로 DENY를 가지고 해당 태그를 이용한 페이지 렌더링 거부
      • .frameOptions().sameOrigin() 할 경우 동일 출처로 오는 request만 페이지 렌더링 허용
    • InMemory User를 위한 설정은 제거
    • UserDetailsService
  • JavaConfiguration 변경
    • MemberService를 반환형으로 하고 매개변수로 MemberRepository, PasswordEncoder를 받아서 DBMemberService()를 생성자로 선언하고 반환하는 메서드로 변경
    • DBMEmberService의 매개변수로는 위의 두 객체를 넣는다.

DBMemberService 구현

  • @Transactional 애너테이션을 붙여서 선언
  • MemberRepository, PasswordEncoder 객체를 필드로 선언하고 생성자 주입으로 DI를 받는다.
  • createMember()에서
    • verifyExistsEmail 메서드를 만들어서 member의 email을 검사한다.
    • 비밀번호를 passwordEncoder.encode()를 사용해서 암호화하고 member.setPassword로 암호화된 비밀번호를 설정한다.
    • memberRepository.save(member)로 저장하고 저장된 member를 return 한다.
  • 패스워드는 암호화된 상태에서 복호화할 이유가 없기 때문에 단방향 암호화 방식으로 암호화한다.

Custom UserDetailsService 구현

  • Spring Security에서 제공하는 컴포넌트로 UserDetailsService는 User 정보를 로드하는 인터페이스이다.
  • InMemoryUserDetailsManager는 UserDetailsManager 인터페이스의 구현체이고, UserDetailsManger는 UserDetailsService를 상속하는 확장 인터페이스이다.
  • HelloUserDetailsService라는 클래스를 생성하고 UserDetailsService를 implements 한다.
    • 필드로 MemberRepository, HelloAuthorityUtils 객체를 선언하고 dI 생성자 주입받는다.
    • HelloAuthorityUtils 객체는 따로 작성할 것이다.
    • @Override로 loadUserByUsername을 선언하고 username을 string으로 매개변수로 받는다.
    • memberRepository에서 findByEmail을 통해 member를 찾고 존재하지 않는 경우 Exception 발생시킨다.(orElseThrow(()→ new Exception());
    • Collection <? extends GrantedAuthority>를 선언해서 authorityUtils.createAuthorities()를 통해 권한을 가져온다.
    • new User(멤버의 식별자, 멤버의 비밀번호, 권한)을 return 한다.
  • HelloAuthorityUtils 클래스를 생성
    • @Component를 붙인다.
    • 필드 멤버로 String adminMailAddress를 생성하고 @Value(${mail.address.admin}”) 애너테이션을 붙여서 application.yml에 정의되어 있는 값을 클래스 내에 사용 가능
    • application.yml에 위의 경로에 특정 이메일 값을 정의
    • 멤버 변수로 List <GrantedAuthority> 타입을 두 개 선언한다.
      • 하나는 AuthorityUtils.createAuthorityList("ROLE_ADMIN", "ROLE_USER");
      • 다른 하나는 AuthorityUtils.createAuthorityList("ROLE_USER");
      • 각자 관리자 권한, 유저 권한을 생성하는 과정이다.
    • createAuthorites라는 메서드를 선언하고 email을 string으로 받아서 List <GrantedAuthority>로 반환한다.
      • email의 값이 위에서 @Value에서 정의한 값과 같다면 관리자 권한을 주고 아니면 유저 권한을 준다.
      • 즉 yml에 위의 경로로 적은 값은 관리자 권한을 부여하는 것이고 나머지는 다 유저 권한이다.
  • HelloUserDetailsService 클래스 리펙터링
    • loadUserByUsername 메서드에서 마지막 부분에 반환을 할 때 찾은 member를 받아서 UserDetails 객체로 생성해주는 클래스를 추가한다.
    • 추가한 클래스는 Member를 extends 하고 UserDetails를 implements 한다.
    • 이후 생성자를 만들어서 member를 받아서 각 항목에 설정하고 getAuthorities()를 overriding 하여 현재 객체의 email을 받아서 authorityUtils.createAuthorities()를 실행하여 return 한다.

User의 Role을 DB에서 관리하기

  • User의 권한 정보를 DB에서 관리하도록 수정
  • User의 권한 정보 테이블 생성
    • User와 User의 권한 정보 간의 관계 생각
    • JPA를 통해 연관 관계
    • member 클래스에서 List<String> roles=new ArrayList <>()로 선언한다.
    • @ElementCollection(fetch =FetchType.EAGER) 애너테이션 추가
      • 컬렉션 타입의 필드는 @ElementCollection를 붙이면 User의 권한 정보와 별도의 엔티티 클래스를 생성하지 않아도 간단하게 매핑 가능
  • 회원 가입 시 User의 권한 정보 DB에 저장
    • DBMemberSErvice에서
      • HelloAuthorityUtils를 필드 멤버로 추가하고 생성자 DI로 받는다.
      • List <String> roles = authorityUtils.createRoles(member.getEmail());
      • 리스트를 하나 선언하고 authorityUtils 클래스에서 string으로 반환하는 메서드를 만들어서 적용한다.
      • 이후 roles를 member.setRoles로 설정한다.
    • HelloAuthorityUtils 클래스에서
      • 필드 멤버로 List <String>으로 List.of()를 사용해서 권한을 설정하여 관리자, 사용자를 각각 생성
      • 이후 createRoles라는 메서드를 생성해서 관리자 메일과 비교하여 권한을 List <String> 형태로 반환
  • 로그인 인증 시, User의 권한 정보를 DB에서 조회
    • HelloUserDetailsService에서
      • 생성자에서 setRoles를 설정해준다.
      • getAuthorities() 메서드에서 return 할 때 매개 변수를 getRoles()로 변경하여 DB에서 값을 가져오도록 설정
    • HelloAuthorityUtils 클래스에서
      • createAuthorities 메서드를 매개변수에 List <String> 타입의 roles로 받고
      • 스트림을 만들어서 규칙에 맞게(ROLE_OOO) 문자열을 만들어 SimpleGrantedAuthority로 생성하여 mapping 하고 리스트로 반환한다.

방법 2. Custom AuthenticationProvider를 사용하는 방법

  • 위의 방식은 Spring Security가 내부적으로 인증을 처리해주는 방식
  • 직접 로그인 인증을 처리하는 방법으로 Custom AuthenticationProvider 사용 가능

구현 과정

  • HelloUserAuthenticationProvider를 AuthenticationProvider를 implements 하여 @Component로 생성한다.
    • authenticationProvider가 구현체가 빈으로 등록되어 있다면 구현체로 인증 절차를 거친다.
  • MemberService, HelloAuthorityUtils, PasswordEncoder 객체를 필드 멤버로 선언하고 생성자 DI 주입
  • 오버 라이딩하여 두 개의 메서드 구현
    • supports 메서드는 매개변수로 authentication을 받는다.
      • UsernamePasswordAuthenticationToken.class.equals(authentication)로 비교
      • Username/Password 방식의 인증을 지원한다는 것을 Spring Security에게 알려주는 역할
      • true 값인 경우 authenticate 메서드를 실행
    • authenticate 메서드는 Authentication 객체를 인자로 받고 Authentication 객체로 반환한다.
      • UsernamePasswordAuthenticationToken 객체로 authToken을 선언하여 인자로 받은 authentication을 캐스팅하여 할당
      • authToken에서 getName()으로 사용자의 username을 얻은 후 존재하는지 체크
        • Optional.ofNullable(). orElseThrow()를 통해 예외 처리
      • 만약 member가 존재한다면 memberService를 통해서 DB에서 member 객체 생성
      • member에서 password를 가져오고 verifyCredentials 메서드를 생성하여 검증
        • verifyCredentials 메서드는 Object credentials와 password를 입력받아서 passwordEncoder.matches(credentials, password)를 통해 같은 지 검증하고 다르면 예외 발생
      • authorityUtils.createAuthorities(member.getRoles())를 통해 권한을 가져온다.
      • 마지막으로 UsernamePasswordAuthenticationToken 객체를 생성자를 통해 username, password, authorities를 넣어서 반환

Spring Security 웹 요청 처리 흐름

보안이 적용된 웹 요청 처리 흐름

  • Spring Security의 웹 요청의 처리 흐름
  1. 사용자가 보호된 리소스 요청
  2. 인증 관리자가 사용자의 credential 요청
  3. 사용자가 credential 제공
  4. 인증 관리자는 credential 저장소에서 사용자 credential 조회
  5. 사용자가 제시한 credential을 비교 검증
  6. 유효한 경우가 아니면 Exception throw
  7. 유효하다면 접근 결정 관리자가 사용자의 권한을 검증
  8. 적절하지 못하면 throw Exception / 적절하다면 리소스 접근 허용

웹 요청에서 서블릿 필터와 필터 체인 역할

  • 위의 요청 흐름처럼 인증 관리자나 접근 결정 관리자처럼 엔드포인트에 요청이 도달하기 전 처리를 할 수 있는 포인트를 제공하는 이것이 서블릿 필터(Servlet Filter)
  • javax.servlet 패키지의 인터페이스로 이를 구현한 서블릿 필터는 웹 요청을 중도에 전 처리하고 요청 처리가 끝난 후 응답을 클라이언트에게 전달하기 전 후처리도 가능
  • Filter Chain
    • 각 filter가 doFiler()라는 메서드를 구현해야 하고 작업들을 수행하고 service() 메서드를 통해 HttpServlet을 거쳐서 doService()로 DispatcherServlet에 요청이 전달된다.

Spring Security에서 필터의 역할

  • Spring Security에서 서블릿 필터는 보안의 역할 수행
  • Spring Security Filter
    • DelegatingFilterProxy
      • ApplicationContext에 bean으로 필터를 등록
      • 이것으로 보안 관련 작업 수행
      • Bean으로 등록된 필터를 사용하는 시작점
      • 서블릿 컨테이너 영역 필터와 ApplicationContext에 Bean으로 등록된 필터를 연결하는 역할
    • FilterChainProxy
      • Spring Security에서 보안 작업을 처리하는 필터의 모음
      • Spring Security filter를 사용하기 위한 진입점 역할
      • 어떤 Filter Chain을 사용할지 FilterChianProxy가 정하고 가장 먼저 매칭 된 filter chain 실행
      • /**가 디폴트 패턴이다.

Spring Security에서 지원하는 Filter 종류

  • 다양한 필터 존재
  • 개발자가 직접 핸들링할 필요가 없는 filter 대부분

Filter와 FilterChain 구현

Filter

  • Servlet Filter는 엔드포인트에 요청이 도달하기 전 중간에 요청을 가로채 처리를 하는 Java Component
  • 서버 측 애플리케이션으로 요청을 전송하면 Servlet Filter를 먼저 거치고 DispatcherServlet에서 요청을 핸들러 매핑하기 위한 다음 작업 진행

Filter Chain

  • Filter가 체인을 형성하고 있는 묶음

Filter & Filter Chain 특성

  • Servlet FilterChain은 요청 URI path를 기반으로 HttpServletRequest를 처리
  • 클라이언트가 서버에 요청을 전송하면 요청 URI 경로 기반으로 Filter와 sevlet을 매핑 결정
  • Filter는 Filter Chain 안에서 순서 지정 가능
  • Spring Boot에서 여러 개 Filter를 등록하고 순서를 정하는 방법
    • @Order를 Filter에 추가하거나 Ordered 인터페이스를 구현해서 순서 지정
    • FilterRegistrationBean을 사용해 Filter 순서 지정

Filter 인터페이스 구현한 기본 구조

  • Filter 인터페이스를 implements 한 FirstFilter 클래스 생성
  • 메서드로 void init(FilterConfig filterConfig) throws ServletException 선언
    • 초기화 작업을 진행
  • void doFilter(ServletRequest request, ServletResponse response, FilterChian chain) throws IOException, ServletException 선언
    • 다음 filter로 넘어가기 전 전처리 작업
    • chain.doFilter(request, response)를 통해 호출
    • 이후 후처리 작업 작성
  • void destroy() 메서드를 만들어 Filter가 사용한 자원 반납 처리

Filter 예시

  • Filter를 구현하는 구현체를 클래스로 선언한다.
    • overriding 하면 세 개의 메서드가 존재
    • init 메서드에서 초기 설정 작업
    • dofilter에서 전처리 작업/chain.doFilter(request, response)로 필터 실행/후처리 작업
    • destroy 메서드에서 filter가 사용한 작업 반납
  • Configuration으로 Filter 적용
    • @Configuration 애너테이션을 붙인 class 생성
    • 그 안에 메서드로 @Bean을 붙여 등록하고 FilterRegistrationBean <필터 구현한 구현체 클래스명>을 반환형으로 하는 메서드를 선언
    • 메서드 안에 new 키워드를 사용해 생성자로 Filter 구현체를 매개변수로 넣어서 FilterRegistrationBean 객체를 생성해서 return
    • 이때 반환하기 전에 FilterRegistrationBean 객체에. setOrder()를 통해 필터 순서 지정 가능(수가 적을수록 먼저 실행)

DelegatingPasswordEncoder

  • PasswordEncoder 구현 객체를 생성해주는 컴포넌트
  • 사용자가 입력한 패스워드를 단방향 암호화하는 역할

배경

  • 이전에는 text password를 그대로 사용하는 NoOpPasswordEncoder가 디폴트로 고정되었지만 문제점 존재
  • 패스워드 단방향 암호화에 사용하는 hash 알고리즘은 시간이 지나면서 안전한 알고리즘이 고안되고 있어 고정된 방식의 암호화를 사용하는 것은 바람직하지 않다
  • 오래된 방식의 알고리즘은 deprecated 되어 간다.

장점

  • DelegatingPasswordEncoder를 사용하면 다양한 암호화 알고리즘 적용 가능
  • 알고리즘 지정을 하지 않으면 Spring Security에서 권장하는 최신 알고리즘 사용
  • 패스워드 검증에 레거시 방식의 암호화 알고리즘으로 검증 지원
  • 암호화 방식을 나중에 변경 가능

Custom DelegatingPasswordEncoder 생성

  • Spring Security에서 지원하는 PasswordEncoderFactories 클래스를 이용하면 권장 PasswordEncoder 사용 가능하지만 직접 지정해서 사용 가능
  • Map 타입으로 객체를 선언하고 key 값에는 문자열을 지정하고 그에 맞게 value 값에는 new BCryptPasswordEncoder(), NoOpPasswordEncoder.getInstance(), new Pbkdf2PasswordEncoder(), new SCryptPasswordEncoder(), new StandardPasswordEncoder() 등의 다양한 encoder를 할당한다.
  • PasswordEncoder 객체에 new DelegatingPasswordEncoder(해당 문자열, Map 타입의 객체) 통해 생성 가능

암호화된 Password Format

  • 암호화된 패스워드의 포맷 : {id}encodedPassword
  • Custom DelegatingPasswordEncoder에서 지원하는 단방향 암호화 알고리즘
    • BCryptPasswordEncoder → {bcrypt}[encodedPassword]
    • Pbkdf2PasswordEncoder → {pbkdf2}[encodedPassword]
    • SCryptPasswordEncoder → {scrypt}[encodedPassword]
    • StandardPasswordEncoder →{sha256}[encodedPassword]

패스워드 해킹 공격에 따른 패스워드 암호화 기술

  • plain Text 저장
    • 패스워드를 암호화하지 않는 경우
  • Hash 알고리즘
    • 단방향 암호화의 핵심 알고리즘
    • 한번 암호화하면 복호화하기 어려운 특성
    • DB에 암호화되어 저장되는 패스워드 자체는 사용자가 입력한 패스워드와 비교하여 올바른지 검증만 하기 때문에 복호화가 필요 없다.
  • MD5(Message Digest 5)
    • 단방향 알고리즘이지만 복호화 사례로 지금은 사용하지 않는다.
    • Digest는 원본 메시지를 암호화한 메세지를 의미
  • SHA(Secure Hash Algorithm)
    • MD5 결함을 보안해서 나온 해시 알고리즘
    • 해시된 문자열을 만들어 내기 위해 비트 회전 연산 추가
    • 사용자가 사용할 만한 password를 목록으로 작성해놓은 Rainbow Table을 활용하여 이 목록에 있는 문자열을 같은 알고리즘으로 암호화하고 탈취한 암호화 문자열과 비교하여 원본 문자열을 차는 Rainbow attack이 가능
  • Rainbow Attack에 대한 대응
    • 자동화된 Rainbow Attack에서 비교 가능한 digest 수를 줄이는 방법 존재
    • Key Stretching : 해시된 다이제스트를 연속적으로 해시를 하는 방법
    • Salt 추가 : 원본 메시지에 문자열을 추가해서 해시 처리
  • Work Factor를 추가한 Hash 알고리즘
    • Rainbow Attack으로 공격해도 최대한 느리게 최대 비용으로 만드는 것을 연구
    • PBKDF2, bcrypt, scrypt 같은 해시 알고리즘 탄생
    • Work factor : 공격자가 해시된 메시지를 알아내는데 더 오래 걸리게 만드는 특정 요소
    • PBKDF2, bcrypt는 work Factor로 Salt와 Key Stretching을 사용하고 내부적으로 복잡한 알고리즘 사용
    • scrypt는 다이제스트 생성 시, 메모리 오버헤드를 갖도록 설계

Spring Security Authentication 구성 요소

Authentication 처리 흐름

Spring Security component에 의한 인증(Authentication) 흐름

  1. 사용자가 로그인 폼을 이용해 username, password를 포함한 request를 Spring Security가 적용된 애플리케이션에 전송
  2. Spring Security Filter Chain에 들어와서 여러 filter 중에 UsernamePasswordAuthenticationFilter가 요청을 받는다.
  3. 사용자의 요청을 받은 UsernamePasswordAuthenticationFilter는 username, password를 이용해서 UsernamePasswordAuthenticationToken을 생성
    1. UsernamePasswordAuthenticationToken는 Authentication을 구현한 구현체)
  4. UsernamePasswordAuthenticationFilter는 UsernamePasswordAuthenticationToken을 AuthenticationManager에게 전달
  5. AuthenticationManager를 구현한 ProviderManager가 인증을 처리를 AuthenticationProvider에게 위임
  6. AuthenticationProvider는 UserDetailsService를 이용해 UserDetails를 조회
    1. UserDetails는 DB에 저장된 사용자의 Username, password, authorities를 포함하는 component
  7. DB에 저장된 사용자의 credential을 포함한 정보를 기반으로 UserDetails를 생성 후에 AuthenticationProvider에게 전달
  8. AuthenticationProvider는 PasswordEncoder를 이용해 UserDetails에 포함된 암호화된 password와 Authentication에 포함된 passoword가 일치하는지 확인
  9. 검증 성공하면 인증된 Authentication 생성, 실패 시 Exception 발생
  10. AuthenticationProvider는 인증된 Authentication을 ProviderManager에게 전달
    1. 여기서는 Authentication에 인증에 성공한 사용자 정보를 가지고 있다.
  11. ProviderManager는 인증된 Authentication을 UsernamePasswordAuthenticationFilter에게 전달
  12. UsernamePasswordAuthenticationFilter는 SecurityContextHolder를 사용해 SecurityContext에 인증된 Authentication을 저장
    1. Spring Security 세션 정책에 따라 HttpSession에 저장되어 사용자 인증 상태 유지하거나 무상태 유지 가능

Authentication Component

UsernamePasswordAuthenticationFilter

  • 사용자의 request에서 제일 처음 접하는 컴포넌트
  • 로그인 폼에서 제출되는 Username과 password를 통한 인증을 처리하는 filter
  • UsernamePasswordAuthenticationToken을 생성
  • 내부 코드
    • AbstractAuthenticationProcessingFilter를 상속한다.
    • request parameter의 디폴트 값은 username과 password이다.
    • AntPathRequestMatcher는 클라이언트의 URL에 매치되는 매처
      • URL과 HTTP Method를 생성자로 입력받는다.
    • AntPathRequestMatcher 객체와 AuthenticationManager를 상위 클래스에게 전달
    • attemptAuthentication() 메서드는 인증을 시도하는 메서드
      • HTTP Method가 POST가 아니면 Exception 발생
      • username과 password를 이용해 UsernamePasswordAuthenticationToken 생성
      • AuthenticationManager를 호출하여 인증 위임

AbstractAuthenticationProcessingFilter

  • UsernamePasswordAuthenticationFilter가 상속하는 상위 클래스
  • HTTP 인증 요청을 처리하지만 인증은 하위 클래스에 맡기고 인증을 성공하면 사용자 정보를 SecurityContext에 저장
  • 내부 코드
    • dofilter() 메서드에서
      • 인증 처리를 해야 하는지 아니면 다음 filter를 호출할지 여부 결정
      • requiresAuthentication 메서드는 requiresAuthenticationRequestMatcher 객체를 통해 들어오는 요청이 인정 처리해야 하는지 여부 결정
      • 하위 클래스에서 인증을 시도해줄 것을 요청
      • 인증이 성공하면 successfulAuthentication() 메서드 호출
        • SecurityContextHolder를 통해 사용자 인증정보를 SecurityContext에 저장하고 SecurityContext를 HttpSession에 저장
      • 인증 실패하면 unsuccessfulAuthentication() 메서드 호출
        • SecurityContext를 초기화하고 AuthenticationFailureHandler 호출

UsernamePasswordAuthenticationToken

  • 인증을 수행하기 위한 토큰
  • 인증 성공 후 사용자의 인증 정보가 토큰에 포함되어 Authentication 객체 형태로 SecurityContext에 저장
  • 내부 코드
    • 두 개의 필드(principal(username), credentials(password)) 존재
    • unauthenticated() 메서드는 인증에 필요한 용도의 토큰 생성
    • authenticated() 메서드는 인증 성공 후 SecurityContext에 저장될 토큰 생성

Authentication

  • Spring Security에서 인증 자체 표현 인터페이스
  • UsernamePasswordAuthenticationToken은 AbstractAuthenticationToken 추상 클래스를 상속하고 Authentication 인터페이스 메서드 일부 구현한 클래스
  • 인증을 위해 생성되거나 성공 후 생성되는 토큰은 UsernamePasswordAuthenticationToken같은 하위 클래스의 형태로 생성되지만 생성된 토큰을 리턴 받거나 SecurityContext에 저장될 경우 Authentication 형태로 리턴받거나 저장
  • 내부 코드
    • Princial
      • 사용자 식별 고유 정보
      • 일반적으로 username에 해당
      • UserDetails가 될 대도 있다.
    • Credential
      • password에 해당
      • 인증이 이루어지고 ProviderManager가 해당 Credential 삭제
    • Authorities
      • AuthenticationProvider에 의해 부여된 접근 권한 목록
      • GrantedAuthority 인터페이스 구현 클래스는 SimpleGrantedAuthority

AuthenticationManager

  • 인증 처리를 총괄하는 매니저
  • 인증을 위한 실질적 관리는 해당 인터페이스를 구현한 구현 클래스로 이루어진다.

ProviderManager

  • AuthenticationManager를 구현한 클래스
  • AuthenticationProvider에게 인증 처리 위임
  • 내부 코드
    • Bean으로 등록하면 List<AuthenticationProvider> 객체를 DI 받는다.
    • for문을 통해 적절한 AuthenticationProvider를 찾는다.
    • AuthenticationProvider에게 인증 처리 위임
    • 인증 처리가 되면 Credentials를 제거

AuthenticationProvider

  • 실질적인 인증 수행을 담당하는 컴포넌트
  • Username/password 인증 처리는 DaoAuthenticationProvider가 담당
  • UserDetailsService로부터 받은 UserDetails를 이용해 인증 처리
  • 내부 코드
    • DaoAuthenticationProvider는 AbstractUserDetailsAuthenticationProvider를 상속
    • AuthenticationProvider 인터페이스 구현체는 AbstractUserDetailsAuthenticationProvider이고 그것을 DaoAuthenticationProvider가 상속한다.
    • retrieveUser() 메서드는 UserDetailsService에서 UserDetails를 조회
      • this.getUserDetailsService().loadUserByUsername(username)에서 UserDetails를 조회
    • additionalAuthenticationChecks() 메서드에서 PasswordEncoder를 이용해 패스워드 검증

UserDetails

  • DB에 저장된 사용자의 Username과 크리덴셜, 권한 정보를 포함하는 컴포넌트
    • 권한 정보, 패스워드, Username을 포함하고 사용자 계정 만료 여부, lock 여부, credentials 만료 여부, 사용자 활성화 여부 정보 포함

UserDetailsService

  • UserDetails를 로드하는 인터페이스
  • loadUserByUsername 메서드 하나만 정의
    • 이를 통해 사용자 정보 로드

SecurityContext, SecurityContextHolder

  • SecurityContext는 인증된 Authentication 객체를 저장하는 컴포넌트
  • SecurityContextHolder는 SecurityContext를 관리하는 역할
  • SecurityContextHolder 코드 내부
    • SecurityContextHolderStrategy 객체가 필드 멤버로 선언
      • 기본 전략은 ThreadLocalSecurityContextHolderStrategy
      • SecurityContext에 연결하기 위해 ThreadLocal을 사용
      • ThreadLocal은 스레드 간 공유되지 않는 고유의 로컬 변수
    • getContext를 통해 현재 실행 스레드에서 SecurityContext 반환
    • setContext를 통해 SecurityContext를 연결

Spring Security Authorization 구성 요소

Spring Security Authorization 처리 흐름

  1. Spring Security Filter Chain에서 URL을 통해 사용자의 액세스를 제한하는 권한 부여 Filter는 AuthorizationFilter
  2. SecurityContextHolder로부터 Authentication 획득
  3. 획득한 Authentication와 HttpServletRequest를 AuthorizationManager에게 전달
  4. AuthorizationManager는 권한 부여 처리 총괄 매니저 역할의 인터페이스
    1. RequestMatcherDelegatingAuthorizationManager는 그 구현체 중 하나
    2. RequestMatcher 평가식을 기반으로 매치되는 AuthorizationManager에게 권한 부여 처리 위임
  5. RequestMatcherDelegatingAuthorizationManager 내부에 매치되는 AuthorizationManager 구현 클래스가 있으면 구현 클래스가 사용자 권한 체크
  6. 적절한 권한이면 다음 요청 프로세스 진행
  7. 적절하지 않으면 AccessDeniedException이 throw 되고 ExceptionTranslationFilter가 처리

Spring Security Authorization Component

AuthorizationFilter

  • URL을 통해 사용자 액세스 제한하는 권한 부여 Filter
  • 내부 코드
    • 객체가 생성될 때, AuthorizationManager를 DI로 받는다.
    • AuthorizationManager의 check() 메서드를 통해 적절한 권한 부여 여부를 체크
    • AuthorizationManager 구현 객체로 RequestMatcherDelegatingAuthorizationManager 사용

AuthorizationManager

  • 권한 부여 처리 관리하는 매니저 역할 인터페이스로 check() 메서드만 정의
  • 제너릭 타입의 객체를 파라미터로 가진다.

RequestMatcherDelegatingAuthorizationManager

  • chekc() 메서드 내부
    • 루프를 돌면서 RequestMatcherEntry 정보를 얻은 후에 RequestMatcher 객체를 얻는다.
    • MatchResult.isMatch()가 true이면 AuthorizationManager 객체를 얻은 뒤 사용자 권한 체크
    • RequestMatcher는 .antMatchers().hasRole() 과 같은 메서드 체인 정보를 기반으로 생성

접근 제어 표현식

  • Spring Security에서 지원하는 표현식(Spring EL)
    • hasRole(String role)
      • 지정된 역할을 가지고 있다면 true 리턴
      • 파라미터로 넘긴 role이 ROLE_로 시작하지 않으면 추가한다.
    • hasAnyRole(String… roles)
      • 현재 보안 주체가 지정한 역할 중 1개라도 있으면 true
      • 문자열을 콤마로 구분해서 전달
    • hasAuthority(Stirng authroity)
      • 지정한 권한을 가지고 있는지 확인(’read’, ‘write’ 등)
    • hasAnyAuthority(String… autorites)
      • 현재 보안 주체가 지정한 권한 중 하나라도 있으면 true
    • principal
      • 현재 사용자를 나타내는 principal 객체 접근
    • authentication
      • SecurityContext로 조회할 수 있는 현재 Authentication 객체에 접근
    • permitAll
      • 항상 true
    • denyAll
      • 항상 false
    • isAnonymous()
      • 보안 주체가 익명 사용자면 true
    • isRememberMe()
      • 보안 주체가 remember-me 사용자면 true
    • isAuthenticated()
      • 사용자가 익명이 아닌 경우 true
    • isFullyAuthenticated()
      • 사용자가 익명 사용자나 remember-me 사용자가 아니면 true
    • hasPermission(Object target, Object permission)
      • 사용자가 target에 해당하는 permission 권한이 있으면 true
    • hasPermission(Object targetId, String targetType, Object permission)
      • 사용자가 targetId와 targetType에 해당하는 permission이 있다면 true

'Backend boot camp > Session4' 카테고리의 다른 글

[Spring WebFlux] Reactor  (0) 2022.12.09
[Spring WebFlux] 리액티브 프로그래밍  (0) 2022.12.09
[Spring Security] OAuth 2  (0) 2022.12.09
[Spring Security] JWT 인증  (0) 2022.12.09
[인증/보안] 기초  (0) 2022.11.19