본문 바로가기
SpringSecurity

스프링 시큐리티 복습 1 - 시큐리티 기본 API 및 Filter 이해

by 서영선 2023. 8. 18.

 

 

💎 인증 API

 

1. 스프링 시큐리티 의존성 추가 

- pom.xml -

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-security</artifactId>

</dependency>

 

 

 

2. 스프링 시큐리티 의존성 추가 시 일어나는 일들

1. 서버가 기동되면 스프링 시큐리티의 초기화 작업 및 보안 설정이 이루어진다.

2. 별도의 설정이나 구현을 하지 않아도 기본적인 웹 보안 기능이 현재 시스템에 연동되어 작동한다

 - 모든 요청은 인증이 되어야 자원에 접근이 가능

 - 인증 방식은 폼 로그인 방식 httpBasic 로그인 방식을 제공

 - 기본 로그인 페이지 제공

 - 기본 계정 한 개 제공 - username: user / password : 랜덤 문자열

 

 

 

기본 스프링 시큐리티의 한계

: 계정 추가, 권한 추가, DB 연동, 기본적인 보안 기능 외에 시스템에서 필요로 하는 더 세부적이고 추가적인 보안 기능이 필요

 

 

 

 

3.  사용자 정의 보안 기능 구현

SecurityConfig

(사용자 정의 보안 설정 클래스)

 

                      ↓  상속

 

WebSecurityConfigurerAdapter                                →                  HttpSecurity

(스프링 시큐리티의 웹 보안 기능 초기화 및 설정)                             (세부적인 보안 기능을 설정할 수 있는 API 제공)

                                                                                                            ▶ 인증 API / 인가 API

 

 


인증 API

  • http.formlogin()
  • http.logout()
  • http.csrf()
  • http.httpBasic()
  • http.SessionManagement()
  • http.RememberMe()
  • http.ExceptionHandling()
  • http.addFilter()

 

인가 API

  • http.authorizeRequests()
  • http.antMatchers(/admin)
  • http.hasRole(USER)
  • http.permitAll()
  • http.authenticated()
  • http.fullyAuthentication()
  • http.access(hasRole(USER))
  • http.denyAll()

 

 

 

 

4.  SecurityConfig 설정

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
    @Override
    protected void configure(HttpSecurity http) throws Exception{
    	http
        	.authorizeRequests()
                .anyRequest().authenticated()
        
        .and()
        	.formLogin();
    }
}

 

 

 

 

 

 

5.  인증 API   -  HTTP Basic 인증

HTTP 는 자체적인 인증 관련 기능을 제공하여 HTTP 표준에 정의된 가장 단순한 인증 기법이다.

간단한 설정과 Stateless 가 장점 - Session Cookie (JSESSIONID) 사용하지 않음

보호자원 접근 시 서버가 클라이언트에게 401 Unauthorized 응답과 함께 WWW-Authenticate header를 기술해서 인증 요구를 보냄

Client 는 ID: Password 값을 Base64로 Encoding한 문자열을 Authorization Header에 추가한 뒤 Server에 Resource를 요청 

ID, Password가 Base64로 Encoding 되어 있어 ID, Password가 외부에 쉽게 노출되는 구조이기 때문에 SSL이나 TLS는 필수적이다.

 

protected void configure(HttpSecurity http) throws Exception {
	http.httpBasic();
}

 

 

 

 

 

 

 

 

 

6. 인증 API   -    Form Login 인증

protected void configure(HttpSecurity http) throws Exception {
	http.formLogin()
    	.loginPage("/login.html")  		   // 사용자 정의 로그인 페이지
        .defaultSuccessUrl("/home")  	           // 로그인 성공 후 이동 페이지
        .failureUrl("/login.html?error=true")      // 로그인 실패 후 이동 페이지
        .usernameParameter("username")             // 아이디 파라미터명 설정
        .passwordParameter("password")             // 패스워드 파라미터명 설정
        .loginProcessingUrl("/login")              // 로그인 Form Action Url 
        .successHandler(loginSuccessHandler())     // 로그인 성공 후 핸들러
        .failureHandler(loginFailureHandler())     // 로그인 실패 후 핸들러
}

 

 

 

 

 

7.  Login Form Filter 과정

 

Request

      ↓

 

UsernamePasswordAuthenticationFilter

      ↓  요청 정보가 매칭되는지 확인

 

AntPathRequestMatcher(/login)                  →            chain.doFilter 

      ↓   YES                                                     NO

 

Authentication

(Username + Password)

      ↓   인증

                                                  위임                                                 인증 실패

AuthenticationManager           →          AuthenticationProvider        →          AuthenticationException 

      ↓                                           ←

                                               인증 성공

Authentication

(User + Authorities)

     

 

SecurityContext 에 저장

     

 

SuccessHandler

 

 

 

 

 

 

 

 

 

 

8.  인증 API   -    Logout,  LogoutFilter

request( /logout )   

1. 세션 무효화

2. 인증토큰 삭제

3. 쿠키 정보 삭제

4. 로그인 페이지로 리다이렉트

 

 

protected void configure(HttpSecurity http) throws Exception {
	http.logout() 		                              // 로그아웃 처리
    	.logoutUrl("/logout")                            // 로그아웃 처리 URL
        .logoutSuccessUrl("/login")                      // 로그아웃 성공 후 이동 페이지 
        .deleteCookies("JSESSIONID", "remember-me")      // 로그아웃 후 쿠키 삭제
        .addLogoutHandler(logoutHandler())               // 로그아웃 핸들러
        .logoutSuccessHandler(logoutSuccessHandler())    // 로그아웃 성공 후 핸들러
}

 

 

 

 

 

 

 

9.  인증 API    -    Remember Me 인증

: 세션이 만료되고 웹 브라우저가 종료된 후에도 어플리케이션이 사용자를 기억하는 기능,

Remember - Me 쿠키에 대한 HTTP 요청을 확인한 후 토큰 기반 인증을 사용해 유효성을 검사하고 토큰이 검증되면 사용자는 로그인 된다.

 

사용자 라이프 사이클

1. 인증 성공 (Remember-Me 쿠키 설정)

2. 인증 실패 (쿠키가 존재하면 쿠키 무효화)

3. 로그 아웃 (쿠키가 존재하면 쿠키 무효화)

 

 

protected void configure(HttpSecurity http) throws Exception {
	http.rememberMe()
    	.rememberMeParameter("remember")    // 기본 파라미터 명은 remember-me
        .tokenValiditySeconds(3600)         // Default는 14일
        .alwaysRemember(true)               // 리멤버 미 기능이 활성화되지 않아도 항상 실행
        .userDetailsService(userDetailsService)
}

 

 

 

 

 

 

 

 

 

10.  인증  API    -    AnonymousAuthenticationFilter 

: 익명 사용자와 인증 처리 필터, 익명 사용자와 인증 사용자를 구분해서 처리하기 위한 용도로 사용

화면에서 인증 여부를 구현할 때 isAnonymoust()와 isAuthenticated()로 구분해서 사용

인증 객체를 세션에 저장하지 않는다.  

(Anonymous Authentication Token 인증 객체를 생성해서 Security Context 에 저장)

 

 

 

 

 

 

 

 

11.  인증 API    -     동시 세션 제어 / 세션 고정 보호 / 세션 정책

  • 동시 세션 제어 : 최대 세션 허용 개수 관리 ( 최대 세션 허용 개수 초과 시,  이전 사용자 세션 만료 혹은 현재 사용자 인증 실패 )
protected void configure(HttpSecurity http) throws Exception {
	http.sessionManagement()              // 세션 관리 기능 작동
    	.maximumSessions(1)               // 최대 허용 가능 세션수, -1 : 무제한 로그인 세션 허용
        .maxSessionsPreventsLogin(true)   // 동시 로그인 차단함, false : 기존 세션 만료(default)
        .invalidSessionUrl("/invalid")    // 세션이 유효하지 않을 때 이동할 페이지
        .expiredUrl("/expired")           // 세션이 만료된 경우 이동할 페이지
}

 

 

 

  • 세션 고정 보호 : 인증할 때마다 세션 쿠키를 새로 발급하여 공격자의 쿠키 조작을 방지

공격자가 웹앱에 접속한 후 쿠키를 발급받아 , 사용자가 공격자의 세션 쿠키로 로그인 시도하면 로그인이 성공하고, 공격자는 같은 쿠키 값으로 인증되어 있기 때문에 공격자는 사용자 정보를 공유

protected void configure(HttpSecurity http) throws Exception {
	http.sessionManagement()                   // 세션 관리 기능이 작동함
    	.sessionFixation().changeSessionId()   // 기본값,  none, migrateSession, newSession
}

 

 

 

 

 

 

  • 세션 정책 
protected void configure(HttpSecurity http) throws Exception{
	http.sessionManagement()
    	.sessionCreationPolicy(SessionCreationPolicy.If_Required)
}

SessionCreationPolicy.Always : 스프링 시큐리티 항상 세션 생성

SessionCreationPolicy.If_Required : 스프링 시큐리티가 필요 시 생성 (기본 값)

SessionCreationPolicy.Never : 스프링 시큐리티가 생성하지 않지만 이미 존재하면 사용

SessionCreationPolicy.Stateless : 스프링 시큐리티가 생성하지 않고 존재해도 사용하지 않음

 

 

 

 

 

 

 

 

12.  인증 API   -    ConcurrentSessionFilter

  • 매 요청마다 현재 사용자의 세션 만료 여부 체크
  • 세션이 만료로 설정되었을 경우 즉시 만료 처리
  • session.isExpired() == true : 로그아웃 처리, 즉시 오류 페이지 응답 ("This session has been expired")

 

 

 

 

 

 

 

 

 

13.  Form 인증  -  CSRF ( 사이트 간 요청 위조 ) 

위조 단계

1. 로그인 후 사용자는 쿠키를 발급 받는다.

2. 공격자가 사용자에게 링크를 전달한다.

3. 사용자가 링크를 클릭하여 공격용 웹페이지에 접속한다.

4. 사용자가 공격용 페이지를 열면, 브라우저는 이미지 파일을 받아오기 위해 공격용 URL을 연다.

5. 사용자의 승인이나 인지 없이 배송지가 등록됨으로써 공격이 완료된다.

 

 

CsrfFilter 

: 모든 요청에 랜덤하게 생성된 토큰을 HTTP 파라미터로 요구, 요청시 전달되는 토큰 값과 서버에 저장된 실제 값과 비교한 후 만약 일치하지 않으면 요청은 실패한다.

 

 

 

Client 
      <input type="hidden" name="${_csrf.parameterName}" value=${_csrf.token}"/>   
       HTTP 메소드 : PATCH, PUT, DELETE, POST


Spring Security
        http.csrf() : 기본 활성화되어 있음
        http.csrf().disabled() : 비활성화

 

 

 

 

 


 

💎 인가 API     

 

 

1. 인가 API   -    권한 설정

선언적 방식

  • URL : http.antMatchers("/users/**").hasRole("USER")
  • Method : @PreAuthorize("hasRole("USER")")   public void user(){ System.out.println("user")}

동적 방식  

  • URL
  • Method

 

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
    	.antMatcher("/shop/**")
        .authorizeRequests()
            .antMatchers("/shop/login", "/shop/user/**").permitAll()
            .antMatchers("/shop/mypage").hasRole("USER")
            .andMatchers("/shop/admin/pay").access("hasRole('ADMIN')");
            .antMatchers("/shop/admin/**").access("hasRole('ADMIN') or hasRole(‘SYS ')");
            .anyRequest().authenticated()
}

※ 주의 사항 - 설정 시 구체적인 경로가 먼저 오고 그것보다 큰 범위의 경로가 뒤에 오도록 해야한다.

("/shop/admin/pay") 경로를 설정한 후,  ("/shop/admin/**") 의 경로를 설정해야 한다.

 

 

 

 

 

 

 

 

 

 

2. 인가 API    -     표현식

  • authenticated() : 인증된 사용자의 접근을 허용
  • fullyAuthenticated() : 인증된 사용자의 접근을 허용, remeberMe 인증 제외
  • permitAll() : 무조건 접근을 허용
  • denyAll() : 무조건 접근을 허용하지 않음
  • anonymous() : 익명 사용자의 접근을 허용
  • rememberMe() : 기억하기를 통해 인증된 사용자의 접근을 허용
  • access(String) : 주어진 SpEL 표현식의 평가 결과가 true이면 접근을 허용
  • hasRole(String) : 사용자가 주어진 역할이 있다면 접근을 허용
  • hasAuthority(String) : 사용자가 주어진 권한이 있다면
  • hasAnyRole(String) : 사용자가 주어진 권한이 있다면 접근을 허용
  • hasAnyAuthority(String...) : 사용자가 주어진 권한 중 어떤 것이라도 있다면 접근을 허용
  • hasIpAddress(String) : 주어진 IP로부터 요청이 왔다면 접근을 허용

 

 

 

 

 

 

 

 

 

 


💎 인증 / 인가 API    -    ExceptionTranslationFilter

 

AuthenticationException

  • 인증 예외 처리 

       1. AuthenticationEntryPoint 호출 : 로그인 페이지 이동, 401 오류 코드 전달 등

       2. 인증 예외가 발생하기 전의 요청 정보를 저장

           RequestCache :  사용자의 이전 요청 정보를 세션에 저장하고 이를 꺼내오는 캐시 메카니즘           

           SavedRequest :  사용자가 요청했던 request 파라미터 값들, 그 당시의 헤더 값들이 저장

 

 

 

AccessDeniedException

  • 인가 예외 처리

        : AccessDeniedHandler 에서 예외 처리하도록 제공

 

 

protected void configure(HttpSecurity http) throws Exception {
	http.exceptionHandling()         // 예외처리 기능 작동
    	.authenticationEntryPoint(authenticationEntryPoint())    // 인증 실패시 처리
        .accessDeniedHandler(accessDeniedHandler())              // 인가 실패시 처리
}

 

 

 

 

 

 

 

댓글