코딩하는 털보

Spring Security 본문

IT Study/Spring Framework

Spring Security

이정인 2020. 12. 18. 19:06

Spring Security

폼 인증

스프링 시큐리티 연동

의존성 (부트의 버전 생략 기 사용)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

스프링 부트의 시큐리티 자동 설정에 의하여…
모든 요청에서 인증이 필요하고,
기본 계정이 생성된다. (애플리케이션 실행마다 패스워드 바뀜)

스프링 시큐리티 설정

설정 파일 추가
@EnableWebSecurity 애노테이션(부트에서 생략가능), WebSecurityConfigurerAdapter 상속

@Configuration
@EnableWebSecurity
public class WebConfig extends WebSecurityConfigurerAdapter {

}

WebSecurityConfigurerAdapter를 오버라이딩하여 설정한다.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/","/info").permitAll()
                .mvcMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and().formLogin()
                .and().httpBasic();
    }

기본 계정 정보를 application.properties에서 변경할 수 있다.

spring.security.user.name=demo
spring.security.user.password=password
spring.security.user.roles=ADMIN

인메모리 계정

인메모리로 계정을 추가할 수 있다.
여기서 password의 prefix("{}") 부분은 인코딩 알고리즘을 정의하는데,
{noop}은 인코딩을 하지 않겠다는것을 의미한다.

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("jilee").password("{noop}123").roles("USER").and()
                .withUser("admin").password("{noop}admin").roles("ADMIN");
    }

DAO 계정

DAO를 이용해서 인증하고 싶을 때는 UserDetailsService를 사용한다.
이때, 특정 문자열(이름 등)을 통해서 UserDetails 객체를 반환하도록 구현해야한다.
UserDetails는 스프링 시큐리티가 제공하는 User.builder()를 통해서 쉽게 만들 수 있다.

@Service
public class AccountService implements UserDetailsService {

    @Autowired
    private AccountRepository accountRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<Account> account = accountRepository.findByUsername(username);
        if (account.isEmpty()) {
            throw new UsernameNotFoundException(username);
        } else {
            return User.builder()
                    .username(account.get().getUsername())
                    .password(account.get().getPassword())
                    .roles(account.get().getRole())
                    .build();
        }
    }

    public Account registerAccount(Account account) {
        account.encodePassword();
        return accountRepository.save(account);
    }
}

이때, User.builder()에서 사용되는 password 도 마찬가지로 {noop} 같은 인코딩 알고리즘의 정의가 필요하다.
일단 인코딩 없이 처리하기 위해 임시로 encodePassword()를 만들었지만
사실 패스워드를 암호화할 수 있는 인코딩 기능을 스프링 시큐리티에서 제공하고있다.

    public void encodePassword() {
        this.password = "{noop}" + this.password;
    }

시큐리티 설정에서 사용될 UserDetailsService 구현체를 지정해야하지만,
만약 작성한 UserDetailsService 구현체가 빈으로 등록되어 있다면 이 과정을 생략해도 자동으로 사용한다.

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(accountService);
    }

Password Encoder 사용하기

스프링 시큐리티는 패스워드 암호화를 위한 PasswordEncoder를 제공한다.

Noop 인코더 사용하기 (Deprecated 되었음)
인코딩을 안하기 때문에 비밀번호가 평문 그대로 저장됨 (비추)

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

스프링 시큐리티 5버전 부터 PasswordEncoder는 특정한 포맷으로 동작하게 된다.
({id}encodedPassword, id는 해싱 알고리즘 명칭)
포맷 기반으로 동작하여 다양한 해싱 전략의 패스워드를 지원할 수 있다는 장점이 있다.
DelegatingPasswordEncoder로 이런 포맷을 작성하게 되는데, 기본적으로는 bcrypt 알고리즘을 사용하게 된다.

PasswordEncoder를 빈으로 등록

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

{noop}을 붙이기 위해 임시로 만들었던 encodePassword()를 PasswordEncoder를 사용하도록 변경.

    public void encodePassword(PasswordEncoder passwordEncoder) {
        this.password = passwordEncoder.encode(this.getPassword());
    }

스프링 시큐리티 테스트

스프링 시큐리티 테스트는 시큐리티 관련 테스트에서 편리한 기능을 제공한다.

의존성

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <version>${spring-security.version}</version>
            <scope>test</scope>
        </dependency>

스프링 시큐리티 테스트의 user() 메소드를 이용해서 가상의 사용자를 인증하여 테스트 할 수 있다.

    @Test
    public void dashboard_user() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/dashboard")
                .with(user("jilee").roles("USER")))
                .andDo(print())
                .andExpect(status().isOk());
    }

또는 anonymous() 메소드를 이용해서 익명의 사용자로 테스트 할 수 있다.

    @Test
    public void dashboard_anonymous() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/dashboard")
                .with(anonymous()))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

user()나 anonymous() 메소드 대신 애노테이션을 테스트 메소드에 붙여서 테스트 할 수도 있다.

또는 Mock 사용자 애노테이션을 만들어서 활용할 수 있다.

@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(username = "jilee", roles = "USER")
public @interface WithUser {
}

스프링 시큐리티 테스트의 formLogin() 메소드를 통해 폼 로그인 기능을 테스트 할 수 있다.

    @Test
    @Transactional
    public void login() throws Exception {
        Account account = new Account();
        account.setUsername("jilee");
        account.setPassword("123");
        account.setRole("USER");
        accountService.registerAccount(account);

        mvc.perform(formLogin().user("jilee").password("123"))
                 .andExpect(authenticated());
    }

결과를 authenticated() 또는 unauthenticated() 메소드로 확인할 수 있다.

스프링 시큐리티 아키텍처

SecurityContextHolder 와 Authentication

  • SecurityContextHolder : SecurityContext 제공, 기본적으로 ThreadLocal을 사용하여 제공한다. 인증이 된 정보만 가지고 있는다.

  • SecurityContext : Authentication 제공

  • Authentication : Principal과 GrantAuthority 제공

  • Principal : 인증된 사용자가 누구인지에 대한 정보 (UserDetailsService를 사용하면, 반환된 UserDetails 객체가 Principal이 된다.)

  • GrantAuthority : "ROLE_USER", "ROLE_ADMIN" 등 Principal이 가지고 있는 권한을 나타내며,
    인가 및 권한을 확인할 때 참조된다.

    public void dashboard() {
        SecurityContext securityContext = SecurityContextHolder.getContext();

        Authentication authentication = securityContext.getAuthentication();

        Object principal = authentication.getPrincipal();
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    }

SecurityContextHolder를 통해 애플리케이션 어디서나 Authentication 객체에 접근할 수 있다.
(기본적으로 개별 쓰레드 한정, 여러 쓰레드에서 공유하려면 ThreadLocal 외에 다른 전략을 사용해야 한다.)

  • UserDetails : 애플리케이션이 가지고 있는 유저 정보와 스프링 시큐리티가 사용하는 Authentication 객체 사이의 어댑터.

  • UserDetailService : 유저 정보를 UserDetails 타입으로 가져오는 DAO 인터페이스.
    인증을 하는 것이 아니고 어떤 저장소로부터의 데이터를 통해 UserDetails를 스프링 시큐리티로 전달하는 역할을 한다.

AuthenticationManager 와 Authentication

AuthenticationManager : Authentication를 만들고 인증을 처리하는 인터페이스.
Authentication를 인자로 받아(사용자가 입력한 인증에 필요한 정보) 유효한지 확인하고 유효하다면 Authentication를 리턴.
인증과정에서는 비활성 계정, 잘못된 비번, 잠긴 계정등의 에러를 던질 수 있다.

인증된 Authentication는 SecurityContextHolder에 들어가게 된다.

ProviderManager : AuthenticationManager의 기본 구현체, 여러 Provider로 인증 작업을 인계한다.
그중 DaoAuthenticationProvider가 UserDetailsService로 UserDetails를 참조하여
인자로 받았던 Authentication와 비교하여 인증하고, 참조했던 UserDetails(Principal)를 Authentication에 담아 리턴한다.

ThreadLocal

Java.lang 패키지에서 제공하는 쓰레드 범위 변수, 쓰레드 수준의 데이터 저장소.

  • 같은 쓰레드 내에서만 공유
  • 같은 쓰레드라면 해당 데이터를 메소드 매개변수로 넘겨줄 필요가 없음
  • SecurityContextHolder의 기본 전략

Authentication 와 SecurityContextHolder

두가지 필터에 의하여
AuthenticationManager의 인증기능 호출과
리턴된 Authentication을 SecurityContextHolder에 입력하는 작업이 처리된다.

UsernamePasswordAuthenticationFilter

  • 폼인증을 처리(AuthenticationManager의 authenticate()함수 호출)하는 시큐리티 필터
  • 인증된 Authentication 객체를 SecurityContextHolder에 넣어준다.

SecurityContextPersistenceFilter

  • SecurityContext를 HTTP session에 캐시(기본 전략)하여 여러 요청에서 Authentication를 공유할 수 있게 해주는 필터
  • SecurityContextRepository에 SecurityContext를 저장하거나 불러오는 기능을 한다.
  • 기본 전략에 의한 저장소는 HttpSessionSecurityContextRepository
  • SecurityContextRepository를 교체하여 세션을 HTTP session이 아닌 다른 곳에 저장할 수도 있다.

스프링 시큐리티 필터와 FilterChainProxy

FilterChainProxy가 여러가지 필터 목록을 순차적으로 호출한다.
요청과 매칭되는 FilterChain을 선택하여 필터 목록이 구성되는데,
FilterChain은 시큐리티 설정 파일(extends WebSecurityConfigurerAdapter)으로 생성된다.
설정 파일 하나가 하나의 FilterChain이 되며 사용한 설정에 따라 사용될 Filter들이 선택된다.
여러개의 FilterChain을 사용하려면 우선순위(@Order)를 정하거나 매칭될 URI(http.antMatcher(url))를 정의해야 한다.

DelegatingFilterProxy와 FilterChainProxy

DelegatingFilterProxy

  • 일반적인 서블릿 필터
  • 서블릿 필터 처리를 스프링에 들어있는 빈으로 위임하고 싶을 때 사용
  • 타겟 빈 이름을 설정한다.
  • 부트가 아니면 AbstractSecurityWebApplicationInitalizer를 사용해서 등록
  • 부트에서는 자동으로 등록 (SecurityFilterAutoConfiguration)

요청을 기본적으로 FilterChainProxy(기본 빈 이름 : springSecurityFilterChain)로 넘긴다.

AccessDecisionManager

AccessDecisionManager :
Access Control(Authorization, 인가) 결정을 내리는 인터페이스, 기본적으로 세 가지 구현체를 제공한다.

  • AffirmativeBased : 여러 Voter 중 하나라도 허용하면 허용, 기본 전략
  • ConsensusBased : 다수결
  • UnanimousBased : 만장일치

AccessDecisionVoter :
해당 Authentication이 특정한 Object에 접근할 때 필요한 ConfigAttributes를 만족하는지 확인한다.

  • WebExpressionVoter : 웹 시큐리티에서 사용하는 기본 구현체, ROLE_Xxxx 매칭 확인.
  • RoleHierarchyVoter : 계층형 ROLE 지원. (ex ADMIN > MANAGER > USER)

ConfigAttributes : hasRole(ROLE), permitAll() 같은 접근 제한

ROLE의 계층구조를 위해 Voter를 RoleHierarchyVoter로 변경하기

@Configuration
@EnableWebSecurity
public class WebConfig extends WebSecurityConfigurerAdapter {

    public AccessDecisionManager accessDecisionManager() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        //상위 권한 ADMIN, 하위 권한 USER
        roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");

        DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler =
                new DefaultWebSecurityExpressionHandler();
        defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy);

        WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
        webExpressionVoter.setExpressionHandler(defaultWebSecurityExpressionHandler);

        List<AccessDecisionVoter<? extends Object>> voters = Arrays.asList(webExpressionVoter);
        return new AffirmativeBased(voters);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/","/info","/account/**").permitAll()
                .mvcMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                //커스터마이징한 AccessDecisionManager 사용  
                .accessDecisionManager(accessDecisionManager())
                .and().formLogin()
                .and().httpBasic();
    }

}

FilterSecurityInterceptor

FilterSecurityInterceptor : AccessDecisionManager를 사용하여 Access Control 또는 예외 처리 하는 필터.
대부분의 경우 FilterChainProxy의 제일 마지막 필터로 들어있다.

AccessDecisionManager의 decide() 메소드를 호출하여 인가를 진행한다.

ExceptionTranslationFilter

ExceptionTranslationFilter :
FilterChain에서 발생하는 AccessDeniedException과 AuthenticationException을 처리하는 필터.
AbstractSecurityInterceptor의 하위 클래스(ex FilterSecurityInterceptor)에서 발생하는 예외만 처리한다.

AuthenticationException(인증 관련 예외) 발생 시

  • AuthenticationEntryPoint 실행
  • UsernamePasswordAuthenticationFilter 사용시 발생하는 AuthenticationException는
    ExceptionTranslationFilter가 아닌 UsernamePasswordAuthenticationFilter 자체에서 처리된다.

AccessDeniedException(인가 관련 예외) 발생 시

  • 익명 사용자라면 AuthenticationEntryPoint 실행
  • 익명 사용자가 아니면 AccessDeniedHandler에게 위임

AuthenticationEntryPoint : 인증 처리기 (formLogin인 경우 로그인 페이지 호출)

웹 애플리케이션 시큐리티

스프링 시큐리티 ignoring()

WebSecurity의 ignoring()을 사용해서 시큐리티 필터 적용을 제외할 요청을 설정할 수 있다.

favicon.ico 요청을 시큐리티 설정에서 배제시키기

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers("/favicon.ico");
    }

그러나 static resource를 매번 따로 적어주어야 하는 불편함이 있다.
=> 부트에서는 static resource의 위치로 제외할 수 있다.

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }

WebAsyncManagerIntegrationFilter

Async 웹 MVC를 지원하는 필터, 별다른 설정 없이도 동작한다.

스프링 시큐리티의 SecurityContextHolder는 기본적으로 ThreadLocal 기반으로 SecurityContext를 제공하게 된다.
WebAsyncManagerIntegrationFilter는 스프링 MVC Async 기능(핸들러에서 Callable 리턴)에 의해 비동기적으로 다중 쓰레드를 사용했을 때,
서로 다른 쓰레드일지라도 동일한 SecurityContext를 공유할 수 있도록 해준다.

  • PreProcess : SecurityContext를 설정한다.
  • Callable : 비록 다른 쓰레드이지만 그 안에서는 동일한 SecurityContext를 참조할 수 있다.
  • PostProcess : SecurityContext를 정리(clean up)한다.

스프링 시큐리티와 @Async

Callable 리턴과는 다르게 @Async의 서로 다른 쓰레드에서는 기본적인 ThreadLocal 전략에서 SecurityContext를 공유받을 수 없다.
이를 해결하려면 SecurityContextHolder의 전략을 변경해야 한다.

@Override
    protected void configure(HttpSecurity http) throws Exception {
        ~

        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }

MODE_INHERITABLETHREADLOCAL 전략은 현재 쓰레드의 하위 쓰레드에 SecurityContext를 공유한다.

SecurityContextPersistenceFilter

SecurityContextRepository를 사용해서 기존의 SecurityContext를 읽어오거나 초기화 한다.

  • 기본적인 전략으로 HTTP Session 사용 (HttpSessionSecurityContextRepository)
  • Spring-Session과 연동하여 세션 클러스터를 구현할 수 있다.

HeaderWriterFilter

응답 헤더에 시큐리티 관련 헤더를 추가해주는 필터, 주로 보안 관련 여러가지 HeaderWriter를 포함하고 있다.

  • XContentTypeOptionsHeaderWriter : 마임 타입 스니핑 방어
  • XXssProtectionHeaderWriter : 브라우저에 내장된 XSS 필터 적용
  • CacheControlHeaderWriter : 캐시 히스토리 취약점 방어
  • HstsHeaderWriter : HTTPS로만 소통하도록 강제
  • XFrameOptionsHeaderWriter : clickjacking 방어

CsrfFilter

CSRF 토큰을 사용하여 CSRF 어택을 방지하는 필터

  • CSRF 어택 : 인증된 유저의 계정을 사용해 악의적인 변경 요청을 만들어 보내는 기법
  • CORS를 사용할 때 특히 주의 해야 한다.(다른 도메인에서 보내오는 요청을 허용하기 때문에)

변경 요청을 보내주길 기대하는 곳에 먼저 CSRF 토큰을 보내주고 요청을 받을 때 헤더에 동일한 토큰 값을 받아서 확인하는 방식이다.
의도되지 않은 변경 요청인 경우에는 CSRF 토큰이 없거나 다른 값일 것이기 때문에 요청을 거부하고 예외 처리한다.
테스트할 때는 with(csrf())로 적절한 CSRF 토큰을 보낼 수 있다.

    @Test
    public void processSignUp() throws Exception {
        mvc.perform(post("/signup")
        .param("username","jilee")
        .param("password","123")
        .with(csrf()))
                .andExpect(status().is3xxRedirection());
    }

LogoutFilter

로그아웃을 처리하는 필터

LogoutHandler : 로그아웃을 처리하는 핸들러, 여러 로그아웃 핸들러를 포함하는 CompositeLogoutHandler,
임의로 핸들러를 추가할 수 있으며 기본적으로 CsrfLogoutHandler, SecurityContextLogoutHandler를 사용한다.

  • CsrfLogoutHandler
  • SecurityContextLogoutHandler

LogoutSuccessHandler : 로그아웃 성공 후 처리

LogoutFilter 관련 설정

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ~

        http.logout()
                .logoutUrl("/logout")           //로그아웃을 처리할 URL (default "/logout")
                .logoutSuccessUrl("/")          //로그아웃 성공 후 Redirection URL
                .addLogoutHandler()             //로그아웃 핸들러 추가 가능
                .logoutSuccessHandler()         //로그아웃 성공 핸들러 커스텀 가능
                .invalidateHttpSession(true)    //로그아웃 후 http session invalid 처리 (default true)
                .deleteCookies()                //로그아웃 후 특정 쿠키 삭제

UsernamePasswordAuthenticationFilter

폼 로그인 인증을 처리하는 인증 필터

  • 사용자가 폼에 입력한 username과 password로 Authentication을 만들고 AuthenticationManager를 통해 인증 시도
  • AuthenticationManager(ProviderManager)는 여러 AuthenticationProvider를 사용하여 인증을 시도한다.
  • 그 중에서 DaoAuthenticationProvider는 UserDetailsService를 사용하여 UserDetails 정보를 가져와 사용자가 입력한 password와 비교한다.

DefaultLoginPageGeneratingFilter

기본 로그인 폼 페이지를 생성해주는 필터

  • GET /login 요청을 처리
    http.formLogin()
        .loginPage("/login") 

설정하면 DefaultLoginPageGeneratingFilter, DefaultLogoutPageGeneratingFilter
필터가 사용되지 않고, 커스텀 로그인 페이지를 사용할 수 있다.

DefaultLogoutPageGeneratingFilter
기본 로그아웃 폼 페이지를 생성해주는 필터

  • GET /login 요청을 처리

BasicAuthenticationFilter

Basic 인증이란?

  • 요청 헤더에 username과 password를 실어 보내면 브라우저 또는 서버가 그 값을 읽어서 인증하는 방식.
  • 보통, 브라우저 기반 요청이 클라이언트의 요청을 처리할 때 자주 사용.
  • 보안에 취약하기 때문에 HTTPS 사용 권장.
  • UserPasswordAuthenticationFilter와 유사하지만 인증 정보를 헤더로 가져온다는 것과 캐싱 기능이 없다는 것이 다르다.

RequestCacheAwareFilter

현재 요청과 관련있는 캐시된 요청이 있는지 찾아 적용하는 요청 캐시 필터

  • 캐시된 요청이 없다면, 현재 요청 처리.
  • 캐시된 요청이 있다면, 해당 캐시된 요청 처리.

로그인 전에 가려고 있던 페이지 요청 등…

SecurityContextHolderAwareRequestFilter

시큐리티 관련 서블릿 API를 구현해주는 필터

  • HttpsServletRequest#authenticate(HttpServletResponse)
  • HttpsServletRequest#login(String, String)
  • HttpsServletRequest#logout()
  • AsyncContext#start(Runnable)

AnonymousAuthenticationFilter

현재 SecurityContext에 Authentication이 null이면 "익명 Authentication"을 만들어 넣어주고,
null이 아니면 아무일도 하지 않는다.

기본으로 만들어서 사용되는 "익명 Authentication" 객체를 설정할 수 있다.

http.anonymous()
    .principal()
    .authorities()
    .key()

SessionManagementFilter

  • 세션 변조 방지 전략 설정 : sessionFixation() (default changeSessionId)
        http.sessionManagement()
                .sessionFixation().changeSessionId();
  • 유효하지 않은 세션을 리다이렉트 시킬 URL 설정 (로그아웃 등)
        http.sessionManagement().invalidSessionUrl("/login");
  • 동시성 제어 : maximumSession(), 최대 로그인 허용량
    • maxSessionsPreventsLogin : 추가 로그인을 막을지 여부 (dafault false)
        http.sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(false);
  • 세션 생성 전략 : sessionCreationPolicy
    • IF_REQUIRED : 필요시 생성 (default)
    • NEVER : 스프링 시큐리티에서는 만들지 않지만 세션이 있다면 사용한다.
    • STATELESS : 세션을 사용하지 않는다.
    • ALWAYS : 항상
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);

ExceptionTranslationFilter

FilterSecurityInterceptor에서 발생한 예외를 처리한다.

AuthenticationException인 경우,
AuthenticationEntryPoint(인증이 가능한 페이지로 보내기)를 사용해서 처리한다.

AccessDeniedException인 경우,
AccessDeniedHandler(기본적으로는 403 Forbidden 에러메시지)를 사용해서 처리한다.

커스터마이징 가능

        http.exceptionHandling()
                .accessDeniedHandler("/access-denied");

FilterSecurityInterceptor

HTTP 리소스 시큐리티 처리를 담당하는 필터.
AccessDecisionManager를 사용해서 인가를 처리한다.

HTTP 리소스 시큐리티 설정

        http.authorizeRequests()
                .mvcMatchers("/","/info","/signup","/account/**").permitAll()
                .mvcMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .accessDecisionManager(accessDecisionManager())
                .and().formLogin().loginPage("/login").permitAll()
                .and().httpBasic();

RememberMeAuthenticationFilter

세션이 사라지거나 만료가 되더라도 쿠키 또는 DB를 사용하여 저장된 토큰 기반으로 인증을 지원하는 필터.
파라미터로 "remember-me"(default)를 받아 사용한다.

RememberMe 설정

        http.rememberMe()
                .rememberMeParameter("remember-me")
                .userDetailsService(accountService)
                .key("remember-me-sample");

커스텀 필터 추가하기

필터 만들기, 일반적인 서블릿 필터처럼 만들 수 있다.

public class LoggingFilter extends GenericFilterBean {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        filterChain.doFilter(servletRequest, servletResponse);
        stopWatch.stop();
        logger.info(stopWatch.prettyPrint());
    }
}

필터 집어넣기, 특정 필터 전후에 커스텀 필터를 집어넣을 수 있다.

    http.addFilterBefore(new LoggingFilter(), WebAsyncManagerIntegrationFilter.class);
Comments