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()을 통해 모든 요청에 대해 접근 허용
- .csrf().disable() 을 통해 CSRF 공격에 대한 spring security 설정 비활성화
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
- http 객체에 .headers().frameOptions().sameOrigin().and() 추가
- 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> 형태로 반환
- DBMemberSErvice에서
- 로그인 인증 시, User의 권한 정보를 DB에서 조회
- HelloUserDetailsService에서
- 생성자에서 setRoles를 설정해준다.
- getAuthorities() 메서드에서 return 할 때 매개 변수를 getRoles()로 변경하여 DB에서 값을 가져오도록 설정
- HelloAuthorityUtils 클래스에서
- createAuthorities 메서드를 매개변수에 List <String> 타입의 roles로 받고
- 스트림을 만들어서 규칙에 맞게(ROLE_OOO) 문자열을 만들어 SimpleGrantedAuthority로 생성하여 mapping 하고 리스트로 반환한다.
- HelloUserDetailsService에서
방법 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를 넣어서 반환
- supports 메서드는 매개변수로 authentication을 받는다.
Spring Security 웹 요청 처리 흐름
보안이 적용된 웹 요청 처리 흐름
- Spring Security의 웹 요청의 처리 흐름
- 사용자가 보호된 리소스 요청
- 인증 관리자가 사용자의 credential 요청
- 사용자가 credential 제공
- 인증 관리자는 credential 저장소에서 사용자 credential 조회
- 사용자가 제시한 credential을 비교 검증
- 유효한 경우가 아니면 Exception throw
- 유효하다면 접근 결정 관리자가 사용자의 권한을 검증
- 적절하지 못하면 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 실행
- /**가 디폴트 패턴이다.
- DelegatingFilterProxy
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) 흐름
- 사용자가 로그인 폼을 이용해 username, password를 포함한 request를 Spring Security가 적용된 애플리케이션에 전송
- Spring Security Filter Chain에 들어와서 여러 filter 중에 UsernamePasswordAuthenticationFilter가 요청을 받는다.
- 사용자의 요청을 받은 UsernamePasswordAuthenticationFilter는 username, password를 이용해서 UsernamePasswordAuthenticationToken을 생성
- UsernamePasswordAuthenticationToken는 Authentication을 구현한 구현체)
- UsernamePasswordAuthenticationFilter는 UsernamePasswordAuthenticationToken을 AuthenticationManager에게 전달
- AuthenticationManager를 구현한 ProviderManager가 인증을 처리를 AuthenticationProvider에게 위임
- AuthenticationProvider는 UserDetailsService를 이용해 UserDetails를 조회
- UserDetails는 DB에 저장된 사용자의 Username, password, authorities를 포함하는 component
- DB에 저장된 사용자의 credential을 포함한 정보를 기반으로 UserDetails를 생성 후에 AuthenticationProvider에게 전달
- AuthenticationProvider는 PasswordEncoder를 이용해 UserDetails에 포함된 암호화된 password와 Authentication에 포함된 passoword가 일치하는지 확인
- 검증 성공하면 인증된 Authentication 생성, 실패 시 Exception 발생
- AuthenticationProvider는 인증된 Authentication을 ProviderManager에게 전달
- 여기서는 Authentication에 인증에 성공한 사용자 정보를 가지고 있다.
- ProviderManager는 인증된 Authentication을 UsernamePasswordAuthenticationFilter에게 전달
- UsernamePasswordAuthenticationFilter는 SecurityContextHolder를 사용해 SecurityContext에 인증된 Authentication을 저장
- 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 호출
- dofilter() 메서드에서
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
- Princial
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를 연결
- SecurityContextHolderStrategy 객체가 필드 멤버로 선언
Spring Security Authorization 구성 요소
Spring Security Authorization 처리 흐름
- Spring Security Filter Chain에서 URL을 통해 사용자의 액세스를 제한하는 권한 부여 Filter는 AuthorizationFilter
- SecurityContextHolder로부터 Authentication 획득
- 획득한 Authentication와 HttpServletRequest를 AuthorizationManager에게 전달
- AuthorizationManager는 권한 부여 처리 총괄 매니저 역할의 인터페이스
- RequestMatcherDelegatingAuthorizationManager는 그 구현체 중 하나
- RequestMatcher 평가식을 기반으로 매치되는 AuthorizationManager에게 권한 부여 처리 위임
- RequestMatcherDelegatingAuthorizationManager 내부에 매치되는 AuthorizationManager 구현 클래스가 있으면 구현 클래스가 사용자 권한 체크
- 적절한 권한이면 다음 요청 프로세스 진행
- 적절하지 않으면 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
- hasRole(String role)
'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 |