SPRING SECURITY
DispatcherServlet 받기 이전에 ServletFilter를 통해 인증/인가 처리.
(ServletFilter는 WAS담당이지만 DelegatingFilterProxy를 boot에서 SecurityFilterAutoConfiguration 설정하여서 서블릿에 등록, 요청이 오면 FilterChainProxy(스프링 필터)에게 위임)
필터를 통한 접근제어 프레임웤
용어
인증(Authentication) : 로그인
인가(Authorization) : 권한확인
AuthenticationFilter - 로그인url 감시 -> AuthenticationManager인증 -> 성공이면 Authentication를 SecurityContextHolder에 저장 AuthenticationSuccessHandler -> 실패면 AuthenticationFailureHandler
UsernamePasswordAuthenticationToken - 인증 전후 Authentication 부모 생성
principal - 유저
Credential - 비밀번호
GrantedAuthority - 유저의 권한
Authentication - 유저의 인증정보
SecurityContextHolder - Authentication 보관소. application 전역에서 접근가능
ex) Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserDetails - 일반 유저와 Spring Security 유저를 호환해주는 어댑터
UserDetailsService - DB에서 유저 정보 가져오는 인터페이스
PasswordEncoder - 비밀번호 암호화
로그인 과정(UsernamePasswordAuthenticationFilter )
( 전달 -> 인자로 넘긴다 )
필터는 여러개 존재할 수 있고, 또한 Manager도 공유할 수 있다. (Provider도 마찬가지로 여러개 가질 수 있고 공유가능)
기본 customizing
AuthenticationFilter 감지 -> UsernamePasswordAuthenticationFilter의 .attemptAuthentication()에서 ID / PW로 UserPasswordAuthenticationToken 발급, 토근에 details만들어 저장 | AbstractAuthenticationProcessingFilterter상속(혹은 UsernamePasswordAuthenticationFilter상속) .attemptAuthentication()구현, 필터를 config에서 추가 |
UsernamePasswordAuthenticationToken을 AuthenticationManager의 구현체인 ProviderManager에게 전달 | AuthenticationManager 굳이 직접 구현 X |
다시 AuthenticationProvider에게 전달( authenticate()에서 확인작업 ) | |
인증할 수 있는(토큰별로 처리하는 provider가 다름) AuthenticationProvider 구현체를 찾고, 그 provider가 유저를 찾는다. 보통 DaoAuthenticationProvider(UserDetailsService구현했다면)이 사용됨 | AuthenticationProvider상속 .authenticate()구현, config에서 AuthenticationManagerBuilder에 추가 |
UserDetailsService구현 config에 추가 .loadUserByUsername()에서 유저를 찾아서 return UserDetails | UserDetailsService(@service) + JPA 구현하여 ID 유/무 조회 |
UsernamePasswordAuthenticationToken을 AuthenticationManager에게 반환 | 이후PasswordEncoder이용하여 비밀번호 확인, return token |
토큰을 AuthenticationFilter에게 전달-> Success / Fail Filter로 반환 | AuthenticationSuccessHandler 구현, config추가(혹은 custom필터에 추가) |
토큰을 SecurityContextHolder에 저장 |
참고 : https://mangkyu.tistory.com/77
설정
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.4.2</version>
</dependency>
SecurityAutoConfiguration 기본(default)설정을 담당
WebSecurityConfigurerAdapter 상속받아 아래 함수를 override (편리한 설정)
-
configure(AuthenticationManagerBuilder auth)로 인증객체 설정
inMemory, Jdbc, Ldap 등의 인증방법 존재 (defalt시 UserDetailsServiceAutoConfiguration에서 user/랜덤PW 생성),
userDetailsService 인터페이스 상속받은 빈 사용하여 DB조회 후 UserDetails 반환 구현, config추가
-
configure(WebSecurity web)로 css,js,image와 같은 인증이 필요하지 않는 자원을 ignoring() 해야한다.
requestMatchers(PathRequest.toStaticResources().atCommonLocations())을 통해 빠른설정 가능 -
PasswordEncoder
유저도메인에 encode(PasswordEncoder passwordEncoder)추가, PasswordEncoder를 주입받아 유저객체에 넘겨줘서 작업
{encoding이름}비밀번호로 저장 -> 비밀번호를 encoding으로 암호/복호화@Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); }
-
AuthenticationSuccessHandler & AuthenticationFailureHandler
@EnableWebSecurity - SpringSecurityFilterChain자동 생성, boot자동생성 일부무시
@Configuration - 설정임을 명시
인증과정 예외
- DisabledException - 비활성화
- LockedException - 잠김처리
- BadCredentialsException - 비밀번호 불일치
로그인한 사용자의 정보 변수로 받고 싶을때,
-
Java의 Principal 객체 사용
-
@AuthenticationPrincipal 애노테이션을 사용하면 UserDetailsService에서 Return한 UsernamePasswordAuthenticationToken 를 파라메터로 직접 받아 사용할 수 있다.
-
User(securty User)를 extend 받고 운영서비스의 유저를 가지고 있는 Adapter 클래스 생성
UserDetailsService에서 Adapter 클래스 리턴, annotation등록@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : userAdmin") public @interface AuthUser { }
Method Security
(desktop app에 주로 사용)
설정
@EnableGlobalMethodSecurity(secureEnabled = true, prePostEnabled = ture, jsr250Enabled = true)
- securedEnabled - @Secured사용여부
스프링지원하는 사전 권한확인 - prePostEnabled - @PreAuthorize, @PostAuthorize 사용여부
PostAuthorize는 해당 메서드의 리턴값을 returnObject 로 참조하여 SpEL을 통해 인가 처리를 할 수 있다. - jsr250Enabled - @RolesAllowed 사용여부
자바지원하는 사전 권한 확인
MethodSecuirty의 ROLE Hierachy
-> 데스크탑 애플리케이션에 적합 (설정시 app깨짐)
GlobalMethodSecurityConfiguration 상속받아, RoleHierarachy를 설정한뒤 accessDicisionVoter에 추가
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
Map<String, List<String>> roleHierarchyMap = new HashMap<>();
roleHierarchyMap.put("ROLE_ADMIN", Arrays.asList("ROLE_BUSINESS"));
roleHierarchyMap.put("ROLE_BUSINESS", Arrays.asList("ROLE_DUTY"));
String roles = RoleHierarchyUtils.roleHierarchyFromMap(roleHierarchyMap);
roleHierarchy.setHierarchy(roles);
AffirmativeBased accessDecisionManager = (AffirmativeBased) super.accessDecisionManager();
accessDecisionManager.getDecisionVoters().add(new RoleHierarchyVoter(roleHierarchy));
return accessDecisionManager;
}
}
필터
참고: https://pupupee9.tistory.com/98
Spring 5 부터 DelegatingFilterProxy 자동설정 -> springSecurityFilterChain에게 요청 위임
FilterChainProxy
configure(HttpSecurity http)에서 설정
-
WebAsyncManagerIntergrationFilter - Async기능 사용시, 서로 다른 Thread간에 SecurityContext를 공유할수있도록 도와주는 필터
- Callable 이전의 작업은 Tomcat에서 할당한 NIO Thread가 사용 (SecurityContext를 공유)
- Callable 작업을 실행할때는 다른 Thread가 할당되어작업을 진행 (SecurityContext 를 정리)
- SecurityContextHolder 전략변경으로 하위 Thread에서도 SecurityContext가 공유되도록 할 수 있다.
- MODE_THREADLOCAL - 같은 Thread만 SecurityContext 공유 (default)
- MODE_INHERITABLETHREADLOCAL - 하위 Thread까지 SecurityContext 공유
- MODE_GLOBAL - app 전체에서 SecurityContext 공유
@Override protected void configure(HttpSecurity http) throws Exception { SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL); }
-
SecurityContextPersistenceFilter - 요청(request)전에, HttpSessionSecurityContextRepository에서 캐시(HttpSession기반) 정보를 SecurityContextHolder에 주입
-
HeaderWriterFilter - 응답 헤더에 시큐리티 관련 헤더를 추가해주는 역할을 담당한다.
- XContentTypeOptionsHeaderWriter: MIME TYPE SNIFFING ATTACK PROTECITON(Content-Type으로만 랜더링)
- XXssProtectionHeaderWriter: 브라우저에 내장된 XSS 필터 적용
- CacheControlHeadersWriter - 캐시 금지
- HstsHeaderWriter: HTTPS로만 소통하도록 강제한다
- XFrameOptionsHeaderWriter: clickjacking방어 (iframe 과 같은것을 활용하여 개인정보 노출을 방지)
-
CsrfFilter - CsrfAttack(사용자가 원치않는 요청을 악의적으로 임의로 만들어 보내는 기법)을 방어하는 Filter
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().cors(); }
-
LogoutFilter - Principal의 로그아웃
- Logout handler
configure(HttpSecurity http)에서 설정- CsrfLogoutHandler
- SecurityContextLogoutHandler
- Logout Success Handler
- SimpleUrlSuccessHandler - 로그아웃 이후 행동
@Override protected void configure(HttpSecurity http) throws Exception { http.logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .invalidateHttpSession(true) .deleteCookies("COOKIE_NAME") .logoutSuccessHandler(); }
- Logout handler
-
UsernamePasswordAuthenticationFilter - (상단 의 로그인 과정) 인증 과정을 진행
-
DefaultLoginPageGeneratingFilter / DefaultLogoutPageGeneratingFilter - 사용자가 별도의 로그인 페이지를 구현하지 않은 경우, 스프링에서 기본적으로 설정한 로그인 페이지를 처리, form 필드명 변경가능
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .usernameParameter("username") .passwordParameter("password") .loginPage("/login") .loginProcessingUrl("/login/login") .successHandler() .failureHandler(); }
-
BasicAuthenticationFilter - HTTP 요청의 (BASIC)인증 헤더를 읽어 결과를 SecurityContextHolder에 저장, 매 요청 마다 인증 필요
@Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic() }
-
RememberMeAuthenticationFilter - SecurityContext에 인증(Authentication) 객체가 있는지 확인하고RememberMeServices를 구현한 객체의 요청이 있을 경우, Remember-Me(ex 사용자가 바로 로그인을 하기 위해서 저장 한 아이디와 패스워드)를 인증 토큰(RememberMeAuthenticationToken)으로 컨텍스트에 주입
-
RequestCacheAwareFilter - 인증이 필요한 경우, 이전 요청을 캐시해 두었다가 인증 요청을 먼저 처리한다.이후 캐시해두었던 요청을 처리
-
SecurityConetextHolderAwareRequestFilter - 인증확인, 인증, 로그아웃, 비동기 구현필터
-
AnonymousAuthenticationFilter - SecurityContextHolder에 인증(Authentication) 객체가 있는지 확인하고, 필요한 경우 기본 Authentication 객체를 주입
@Override protected void configure(HttpSecurity http) throws Exception { http.anonymous().principal("anonymousUser") .authorities("ROLE_ANONYMOUS") }
-
SessionManagementFilter - 요청이 시작된 이 후 인증된 사용자 인지 확인하고, 인증된 사용자일 경우SessionAuthenticationStrategy를 호출하여 세션 고정 보호 메커니즘을 활성화하거나 여러 동시 로그인을 확인하는 것과 같은 세션 관련 활동수행
최대 세션, 새로운 세션 유지 전략, 세션 변조 방지, 세션 생성 전략 설정@Override protected void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS)//세선생성X .sessionFixation() .migrateSession()//세션변조방지 .invalidSessionUrl("/login")//유효하지않는세션일경우 url .maximumSessions(1)//최대세션설정 .maxSessionsPreventsLogin(false)//중복로그인방지 }
-
ExceptionTranslationFilter - 필터 체인 내에서 발생(Throw)되는 대부분 예외(인가AccessDeniedException, 인증AuthenticationException)를 처리
- AuthenticationException(인증) - AuthenticationEntryPoint를 실행하여 인증을 유도
- AccessDeniedException(인가)
- 비인증 일시 AuthenticationEntryPoint를 실행하여 인증을 유도
- 권한없을 시 AccessDeninedHandler
@Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling() .accessDeniedPage("/403") .accessDeniedHandler() .authenticationEntryPoint("/login") }
-
FilterSecurityInterceptor - HTTP 리소스의 보안 처리
AccessDecisionManager의 구현체 WebExpressionVoter - 권한처리(인가), Voter소유, configure(HttpSecurity http)만족하는지 확인
전략- AffirmativeeBased - 여러 Voter들중 하나만 허용하더라도 인가에 성공 (default)
- ConsensusBased - 다수결
- UnanimousBased - 만장일치
-
mvcMatchers() - url 검사
-
antMatchers() - url 정규식검사
-
hasAuthority() - ROLE_ADMIN
-
hasRole() - ADMIN
-
hasIpAddress() - IP허용
ExpressionHandler 커스터마이징을 통해 ROLE Hirerachy 설정 가능
(AccessDecisionManager를 커스터마이징 하여 Hirerachy 설정도 가능하지만 더 복잡하다)@Bean public SecurityExpressionHandler<FilterInvocation> expressionHandler() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); Map<String, List<String>> roleHierarchyMap = new HashMap<>(); roleHierarchyMap.put("ROLE_ADMIN", Arrays.asList("ROLE_BUSINESS")); roleHierarchyMap.put("ROLE_BUSINESS", Arrays.asList("ROLE_DUTY")); String roles = RoleHierarchyUtils.roleHierarchyFromMap(roleHierarchyMap); roleHierarchy.setHierarchy(roles); DefaultWebSecurityExpressionHandler webSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler(); webSecurityExpressionHandler.setRoleHierarchy(roleHierarchy); return webSecurityExpressionHandler; } @Override protected void configure(HttpSecurity http) throws Exception { http.expressionHandler(expressionHandler()) }
- CustomFilter - GenericFilterBean상속, http.add(Fillter filter)핕터 추가
테스트
spring-security-test 의존성 추가
@WithMoxkUser로 인증정보 주입가능
'스프링 > SpringSecurity' 카테고리의 다른 글
JWT + SpringSecurity (0) | 2021.03.20 |
---|
댓글