-
Spring Security[1]Spring 2021. 7. 19. 11:23
Spring Security
프로젝트 구성 및 의존성 추가
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
스프링 시큐리티의 의존성 추가 시
- 서버가 기동되면 스프링 시큐리티의 초기화 작업 및 보안 설정이 이루어진다.
- 별도의 설정이나 구현을 하지 않아도 기본적인 웹 보안 기능이 현재 시스템에 연동되어 작동한다.
- 모든 요청은 인증이 되어야하 자원에 접근이 가능
- 인증 방식
- 폼 로그인
- httpBasic 로그인
- 기본 로그인 페이지 제공
- 기본 계정 한 개 제공
- ID : user
- PWD : 랜덤 문자열
문제점
- 계정 추가 권한 추가, DB 연동 등
- 기본적인 보안 기능 외에 시스템에서 필요로 하는 더 세부적이고 추가적인 보안기능이 필요
사용자 정의 보안 기능 구현
security 의존성 추가했을경우 보안설정을 할 수 있는 클래스로 해당 클래스를 상속을 받아 사용할 경우 HttpSecurity 클래스의 인증 API, 인가 API를 자유롭게 정의할 수 있다.
다른요청들은 인증을 하지 않을경우 기본 폼로그인으로 이동된다.
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated(); http .formLogin(); } }
매번 랜덤 문자열로 로그인 해야하는 비밀번호의 경우 application.properties 에 등록해서 간편하게 사용할 수 있다.
spring.security.user.name=user spring.security.user.password=1111
Form Login 인증
configure() 메소드 설정
http.formLogin() // Form 로그인 인증 기능이 작동함 .loginPage("loginPage") // 사용자 정의 로그인 페이지 .defaultSuccessUrl("errorPage") // 로그인 실패 후 이동 페이지 .usernameParameter("username") // 아이디 파라미터명 설정 .passwordParameter("password") // 패스워드 파라미터명 설정 .loginProcessingUrl("/login") // 로그인 form action url .successHandler(loginSuccessHandler()) // 로그인 성공 후 핸들러 .failureHandler(loginFailurHandler()) // 로그인 실패 후 핸들러
로그인 처리
- 사용자 인증 시도 시 UsernamePasswordauthenticationFilter 에서 사용자 정보를 확인한다
- AntPathRequestMatcher 에서 로그인 정보를 확인한다.
- 정보가 미 일치 시 다른 필터로 이동
- 정보가 일치 시 Authentication 에서 사용자가 입력한 로그인 값을 저장하여 인증 객체를 생성한다.
- AuthenticationManager 에서 인증 처리를 하는데 AuthenticationProvider 에게 인증처리를 위임한다.
- 인증 실패 시 AuthenticationException 발생
- 인증 성공 시 권한 정보 및 인증 객체를 저장하여 AhthenticationManager 에게 전송한다.
- AuthenticationManager 는 인증 객체를 Authentication 에게 반환한다.
- Authentication 에서 인증 결과인 인증 객체를 Securitycontext 에 전송한다.
- SecurityContext 에서 인증 객체를 저장한다
- SuccessHandler 처리
Logout 처리, LogoutFilter
로그아웃이 일어날 경우 세션, 인증토큰, 쿠키정보를 삭제하며 로그인 페이지로 이동한다.
http.logout() // 로그아웃 처리 .logoutUrl("/logout") // 로그아웃 처리 URL .logoutSuccessUrl("/login") // 로그아웃 성공 후 이동 페이지 .deleteCookies("JSESSIONIN", "remember-me") // 로그아웃 후 쿠키 삭제 .addLogouthandler(logouthandler()) // 로그아웃 핸들러 .logoutSuccessHandler(logoutSuccessHandler()) // 로그아웃 성공 후 핸들러
deleteCookies로 특정 쿠키를 삭제할 수 있으며, 만약 추가 작업이 필요하다면 로그아웃 핸들러를 이용하여 작업을 추가할 수 있다.
로그아웃 처리
- LogoutFiter가 POST방식의 로그아웃을 받는다.
- AntPathRequestMatcher에서 로그아웃을 요청하는건지 검사를 한다.
- 미 일치시 chain.doFiter 그다음 필터로 보낸다
- 일치 시 Authentication 에서 securityContext 에서 인증 객체를 꺼내온다.
- SecurityContextLogoutHandler 에서 세션 무효화, 쿠키 삭제, securityContext.clearContext() 컨텍스트에서 정보를 삭제한다.
- 로그아웃이 성공적 으로 끝날 경우 SimpleUrlLogoutSuccessHandler 에서 다시 login 페이지로 이동시킨다.
Remember Me 인증
- 세션이 만료되고 웹 브라우저가 종료된 이후에도 애플리케이션이 사용자를 기억하는 기능
- RememberMe 쿠기에 대한 Http 요청을 확인 후 토큰 기반 인증을 사용해 유효성을 검사하고 토근이 검증되면 로그인이 된다.
- 사용자 라이프 사이클
- 인증 성공 (Rembmer-Me 쿠키 설정)
- 인증 실패(쿠키가 존재하면 쿠키 무효화)
- 로그아웃(쿠키가 존재하면 쿠키 무효화)
htpp.rememberMe() .rememberMeParameter("remember") // 기본 파라미터명은 remember-me .tokenValiditySeconds(3600) // 만료시간 default 는 14일 .alwaysRemember(true) // 리멤버 기능이 활성화 되지 않아도 항상 실행 default 는 false .userDetailsService(userDetailsService) // remember-me 인증 후 처리할 서비스
JSESSION을 삭제하더라도 Security에서 쿠키가 있는지 확인 후 있을경우 user객체를 얻어 그 객체로 로그인을 시도한다.
RememberMeAuthenticationFilter
Remember Me 인증 절차
- 사용자의 세션이 있는지 필터를 통하여 검증
세션이 없을 시 RememberMeAuthenticationFilter 가 사용자의 인증을 다시 받도록 시도한다. - 사용자의 재 인증 과정은 PersistentTokenBasedRememberMeService 가 사용자 토큰과 DB에 저장된 토큰이 일치한지 확인을 한다.
- 토큰이 일치할 경우 토큰에서 쿠키를 추출한다.
- 추출한 토큰이 rememberBe 인지 확인한다.
미 일치 시 다른 필터로 이동 - 토큰의 형식이 일치한지 확인한다.
미 정상 시 예외를 발생시킨다. - 사용자의 토큰과 서버에 저장된 토큰이 일치한지 확인한다.
미 일치 시 예외를 발생시킨다. - 토큰의 User 계정이랑 DB 에 저장된 User 계정이랑 동일한지 확인한다.
미 일치 시 예외를 발생시킨다. - 전부 검증이 끝날경우 새로운 Authentication 객체를 만들어 AuthenticationManager 에게 전달한다.
- 이후 JSESSION 을 재발급하며 사용자 정보를 담는다.
AnonymousAuthenticationFilter
사용자의 인증 과정이 다른 필터랑 거의 동일하게 처리가 이루어 지지만 인증을 받지 않은 사용자일 경우 익명 객체를 만들어 SecurityContext 에 해당 객체를 저장하는 점이 다르다.
- 사용자의 요청을 받을 경우 해당 사용자의 인증객체가 SecurityContext 에 존재하는지 확인한다.
- 해당 객체가 존재하지 않을 경우 새로운 익명사용자 객체인 AnonymousAuthenticationToken 을 생성하여 SecurityContext 에 저장한다.
- 실제로 인증을 받은 사용자가 아니라면 세션에 인증객체를 저장하지 않는다.
- 해당 사용자가 다른 요청을 할 경우 Security 는 SecurityContext 에 저장된 객체를 확인하여 익명 사용자인지 인증 받은 사용자인지 확인한다.
- 또한 화면에서 인증 여부를 통하여 익명사용자와 인증 받은 사용자를 구분할 수 있다.
최초에 요청하는 사용자가 인증객체가 존재하는지 확인한다. securityContext 에 객체 존재하는지 확인
- 존재 시 다음 필터로 이동
- 미 존재 시 익명사용자용 인증객체인 AnonymousAuthenticationToken 을 생성 후 SC 에 저장한다.
- 추 후 SC 에서 객체가 존재하는지 확인할 때 해당 사용자가 익명사용자인지 인증 사용자인지 확인한다.
- 또한 화면에서 인증 여부 구현 시 익명사용자인지, 로그인한 사용자인지 구분가능할 수 있다.
- 실제로 인증을 받은 사용자가 아니기에 세션에 인증객체를 저장하지 않는다.
동시 세션 제어
현재 동일한 계정으로 인증을 받을때 세션의 허용 갯수 초과 시 어떻게 유지하는지 제어를 의미한다.
2가지 전략으로 세션 제어를 지원
- 이전 세션을 만료시킴
- 이전 세션 만료 설정
- 지금 세션 인증 실패
- 2 번째 사용자 세션 차단
http.sessionManagement() .maximumSessions(1) // 세션 최대 수 .maxSessionsPreventsLogin(true) // 동시 로그인 차단 (지금 세션 차단) false 설정 시 이전 세션 만료 설정 .invalidSessionUrl("/invalid") // 세션이 유효하지 않을 경우 이동할 페이지 .expiredUrl("/expired") // 세션이 만료된 경우 이동 할 페이지 invalidSessionUrl() 과 expiredUrl() 을 같이 설정할 경우 invalidSessionUrl() 이 실행된다.
세션 고정 보호
세션 고정 공격으로 부터 스프링은 매번 새로운 세션을 만드는 세션 고정 보호를 제공한다.
세션 고정 공격
세션 고정 공격은 공격자가 서버에 접속하여 세션의 아이디를 얻은 후 이를 사용자에게 주어 사용자가 공격자의 세션 쿠키를 이용해 로그인하는 경우 이루어 진다. 이경우 사용자가 보고있는 정보를 공격자가 같이 확인할 수 있다.
http.sessionManagement() .sessionFixation().changeSessionId() // 기본 값 // none, migrateSession, new Session
세션 정책
http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.If_Required) SessioncreationPolicy.Always // 스프링 시큐리티가 항상 세션 생성 SessioncreationPolicy.If_Required // 스프링 시큐리티가 필요 시 생성 기본 시 이 속성 사용 SessioncreationPolicy.Never // 생성하지 않지만 이미 존재하면 사용 SessioncreationPolicy.Stateless // 생성하지도 않으며 존재하지도 않음 JWT 처럼 토큰에 저장하고 인증 시 이 속성 사용
SessionManagementFilter & ConcurrentSessionFilter
SessionManagementFilter
- 세션 관리
- 인증 시 사용자의 세션정보를 등록, 조회, 삭제 등의 세션 이력을 관리
- 동시적 세션 제어
- 동일 계정으로 접속이 허용되는 최대 세션 수를 제한
- 세션 고정 보호
- 인증 할 때마다 세션 쿠키를 새로 재발급 받아 공격자의 쿠키 조작을 방지
- 세션 생성 정책
- Always, If_Required, Never, Stateless
ConcurrentSessionFilter
- 매 요청마다 현재 사용자의 세션 만료 여부 체크
- 세션이 만료되었을 경우 즉시 만료 처리
- Session.isExpired() == true
- 로그아웃 처리
- 즉시 오류 페이지 응답 (This Session has been expired)
SessionManagementFilter & ConcurrentSessionFilter 연계
- 새로운 사용자가 동일한 계정으로 로그인 시도 시 SessionManagementFilter 에서 최대 세션 허용 개수가 초과 한 경우 이전 사용자 세션을 만료 시킨다.
- 이전 사용자의 경우 ConcurrentSessionFilter 에서 SessionManagementFilter 에게 세션의 만료 여부에 알아보며 만료 시 로그아웃 후 오류 페이지를 응답한다.
SessionManagementFilter & ConcurrentSessionFilter 인증과정
user1 이 접속 시 세션정보를 등록 후 세션 보호정책 설정을 해준다.
user2 가 동일한 계정으로 접속 시
- 인증 실패 전략의 경우 Exception 발생
- 세션 만료 전략인 경우 기존 세션을 만료시키며, 새로운 세션정보를 등록 및 세션 보호정책 설정을 해준다.
user1 가 새로운 요청을 한 경우 세션 만료 여부를 판단하여 로그아웃 및 에러 페이지로 처리한다.
권한 설정 및 표현식
선언적 방식
- URL
- http.antMatchers("/users/**").hasRole("USER)
- Method
- @PreAuthorize("hasRole('USER')")
- public void user() { System.out.println("user")}
- URL
동적 방식 - DB 연동 프로그래밍
- URL
- Method
URL 방식
http .antMatchers("/shop/**") // 해당 요청 시 보안 검사를 한다. .authorizeRequest() .antMatchers("/shop/login", "/shop/users/**").permitAll() // 해당 요청 시 허용 .antMAtchers("/shop/mypage").hasRole("USER") // 해당 요청 시 USER 권한 확인 .antMAtchers("/shop/admin/pay").access("hasRole('ADMIN')") .antMAtchers("/shop/admin/pay").access("hasRole('ADMIN') or hasRole('SYS')") .anyRequest().autheticated() // 다른 요청은 인증을 받은 사람만 접근 가능
표현식
ExceptionTranslationFiter, RequestCacheAwareFilter
ExceptionTranslationFilter
- AuthenticationException
- AuthenticationEntryPoint 호출
- 로그인 페이지 이동, 401 오류 코드 전달 등
- 인증 예외가 발생하기 전의 요청 정보를 저장
- RequestCache : 사용자의 이전 요청 정보를 세션에 저장하고 이를 꺼내오는 캐시 메카니즘
- SaveRequest : 사용자가 요청했던 request 파라미터 값들, 그 당시 헤더값들을 저장
- AuthenticationEntryPoint 호출
- AccessDeniedException
- 인가 예외 처리
- AccessDeniedHandler 에서 예외 처리하도록 제공
- 인가 예외 처리
- 익명사용자가 접근 시 FilterSecurityInterceptor 에서 인가예외 처리한다.
- 인가예외에서 remember-me 사용자 일 경우 인증예외로 처리한다.
- 인증예외의 AuthenticationEntryPoint 에서 로그인 페이지로 이동시킨다.
- 또한 HttpSessionReqeustCache에서 사용자가 가고자 했던 정보를 DefaultSavedRequset에 담고 이를 세션에 저장해준다.
http.exceptionHandling() // 인증예외 .quthenticationEntryPoint(authenticationEntryPont()) // 인증 실패 시 처리 // 인가예외 .accessDeniedHandler(accessDeniedHandler()) // 인증 실패 시 처리
CSRF, CsrfFilter
- 사용자가 로그인 후 쿠키를 발급받음
- 공격자가 사용자 이메일로 링크를 전달 (공격용 웹페이지)
- 사용자가 링크를 클릭
- 브라우저는 이미지 팡리을 받아오기 위해 공격용 URL을 연다
- 사용자의 승인이나 인지 없이 배송지가 등록됨으로써 공격이 완료된다.
CsrfFilter
- 모든 요청에 랜덤하게 생성된 토큰을 HTTP 파라미터로 요구
- 요청 시 전달되는 토큰 값과 서버에 저장된 실제 값과 비교한 후 만약 일치하지 않으면 요청은 실패한다.
- Client
- HTTP 메소드 : PATCH, POST, PUT, DELETE
- Spring Security
- http.csrf() : 기본 활성화 되어 있음
- http.csrf().disabled() : 비활성화
'Spring' 카테고리의 다른 글
TDD[1] (0) 2021.10.08 Spring Security[2] (0) 2021.07.19 토비의 스프링[8] 스프링이란 무엇일까? (0) 2021.07.19 토비의 스프링[7] 스프링 핵심기술과 응용 (0) 2021.04.14 토비의 스프링[6] AOP (0) 2021.03.08