SpringSecurity 01 ] 기본 API 및 Filter 이해

컴업·2022년 11월 30일
0

본 포스트는 Inflearn 정수원 선생님 강의를 정리한 것입니다.

1. 의존성 추가

Maven

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

Gradle

implementation 'org.springframework.boot:spring-boot-starter-security'

의존성을 추가하는 것 만으로도 별다른 설정 없이 Spring Security의 적용이 가능합니다.

아래 그림과 같이 아무런 세팅 없이 /를 받는 Controller 하나만 만들고 접속해도 기본적인 Form 로그인 기능과 페이지를 제공하는 것을 알 수 있습니다.

@RestController
public class SecurityController {

    @GetMapping("/")
    public String index() {
        System.out.println("index page");
        return "home";
    }

}

기본 로그인페이지

📌 기본 설정

의존성 추가만으로 설정되는 초기 세팅값은 다음과 같습니다.

1. 모든 요청에 대한 인증 요청
- 모든 페이지에 대한 로그인이 필요합니다.

2. FORM 로그인 방식
- html form 태그 방식의 로그인 기능을 제공합니다.

3. 기본 계정 제공
- 바로 사용할 수 있는 계정을 한 개 제공합니다.
- ID : user
- PWD : 서버 기동 시 콘솔창에 출력

- 기본 계정은 application.yml 파일에 아래 설정을 추가함으로서 변경 가능합니다.

spring.security.user.name=user
spring.security.user.password=1111

✏️ 의존성 추가만으로 Spring Security가 적용되는 것은 한편으로는 굉장히 불편할 수 있습니다.
이런 경우 아래와 같이 SpringBootApplication 어노테이션에 SecurityAutoConfiguration 클래스를 exclude처리 할 수 있습니다.

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class SpringSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityApplication.class, args);
    }

}

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

사용자 설정은 WebSecurityConfigurerAdapter 클래스를 상속받아 설정합니다.
이 클래스는 내부에서 HttpSecurity 클래스를 생성하고, 이 클래스가 제공하는 인증, 인가와 관련된 다양한 API를 이용해 설정합니다.

✏️ 인증과 인가

  • 인증, Authentication : 아이디 등을 통해 등록된 유저인지 확인하는 절차.
  • 인가, Authorization : 인증 받은 사용자가 현재 요청한 자원에 대한 권한을 확인하는 절차.

[설정 예시]
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 인가 정책
        http
            .authorizeRequests()
            .anyRequest().authenticated();
        // 인증 정책
        http
            .formLogin();
    }
}

📌 인증 정책

3. Form 로그인

우리가 일반적으로 만날 수 있는 로그인 형태, HTML Form 태그를 통해 인증한다.

Form 로그인 API

Form login API doc.

http
.formLogin()
      .loginPage(/login.html")   				// 사용자 정의 로그인 페이지
      .defaultSuccessUrl("/home")				// 로그인 성공 후 이동 페이지
	  .failureUrl(/login.html?error=true)		// 로그인 실패 후 이동 페이지
      .usernameParameter("username")			// 아이디 파라미터명 설정
      .passwordParameter(“password”)			// 패스워드 파라미터명 설정
      .loginProcessingUrl(/login")				// 로그인 Form Action Url (default url /login)
      .successHandler(loginSuccessHandler())	// 로그인 성공 후 핸들러
      .failureHandler(loginFailureHandler())	// 로그인 실패 후 핸들러
      .permitAll();
  • loginPage : 폼 로그인 세팅
  • defaultSuccessUrl : 로그인 성공 후 redirect할 URL을 지정한다.
  • failurlUrl : 로그인 실패 후 redirect할 URL을 지정한다.
  • usernameParameter : html form 양식 중 사용자 ID의 name을 지정한다.
  • passwordParameter : html form 양식 중 사용자 PWD의 name을 지정한다.
  • loginProcessingUrl : html form 양식 중 form 태그의 action 경로를 지정한다.
  • successHandler : 로그인 성공 후 후 처리를 위해 실행할 핸들러
  • failureHandler : 로그인 실패 후 후 처리를 위해 실행할 핸들러
  • permitAll : 위 세팅에서 사용되는 모든 경로에 대한 인가를 승인한다. (모든 사용자가 접근 가능하도록 설정)

✏️ defaultSuccessUtl (or failurlUrl) 그리고 successHandler (or failureHandler)를 모두 세팅 하였을 때 Handler 선 실행 후 URL로 redirect한다.

Form 로그인 Filter

  • UsernamePasswordAuthenticationFilter
    - form login 기능을 사용하는지 확인

  • AntPathRequestMatcher(/login)
    - requset 경로가 폼 로그인 요청 경로 (/login)이 맞는지 확인한다.

  • AuthenticationManager
    - AuthenticationProvider에게 인증 작업을 위임한다. (나중에 자세하게)

  • AuthenticationProvider
    - 실제 인증과 관련된 작업을 처리하는 클래스로 로그인 성공 시 Authentication 객체를 생성, 실패시 예외를 발생시킨다.

  • Authentication
    - 계정 정보를 갖는 클래스, SecirutyContext안에 저장된다.
    - SecurityContextHolder.getContext().getAuthentication() 으로 조회가능

Form 로그인 다이어그램


5. 로그 아웃

POST 요청을 통해 세션무효화, 인증 토큰 삭제, 쿠키 정보 삭제 등등을 수행

로그 아웃 API

Logout API doc.

http
.logout()									// 로그아웃 처리 
	.logoutUrl(/logout")					// 로그아웃 처리 URL (post)
	.logoutSuccessUrl(/login")			// 로그아웃 성공 후 이동페이지
	.deleteCookies("JSESSIONID“, "remember-me") 	// 로그아웃 후 쿠키 삭제
	.addLogoutHandler(logoutHandler())		 // 로그아웃 핸들러
	.logoutSuccessHandler(logoutSuccessHandler()) 	// 로그아웃 성공 후 핸들러
  • logoutUrl : 로그아웃 POST 요청을 받을 URL 주소 지정.
  • logoutSuccessUrl : 로그아웃 성공 후 redirect할 URL 주소 지정.
  • deleteCookies("JSESSIONID", "remember-me", ...) : 삭제할 쿠키명 지정
  • addLogoutHandler : spring security에서 기본적으로 제공하는 핸들러 이외에 추가적인 핸들러를 적용할 때 사용.
  • logoutSuccessHandler : 로그 아웃 성공 후 후 처리 핸들러

로그 아웃 Filter

  • AntPathRequestMatcher("/logout") : 지정한 로그아웃 URL로 POST 요청 확인.
  • Authentication : 인증 객체에서 사용자 정보를 받아온다.
  • SecurityContextLogoutHandler : SecurityContext 삭제, 세션 무효화, 쿠키 삭제 로그아웃 수행.

로그 아웃 Diagram

6. Remember Me 인증

Remember Me 란?

세션 만료 혹은 브라우저창을 닫아도 어플리케이션의 로그인 상태를 유지하는 기능.

Remember Me API

RememberMe API doc.

http
	.rememberMe()
		.rememberMeParameter(“remember”)	// html 태그 네임 지정, 기본 파라미터명은 remember-me
		.tokenValiditySeconds(3600)			// Default 는 14일
		.alwaysRemember(true)				// 리멤버 미 기능이 활성화되지 않아도 항상 실행
		.userDetailsService(userDetailsService)
  • rememberMbParameter : html 체크박스 name을 지정한다. (default remember-me)
  • tokenValidtySeconds : 토큰 유지기간을 지정한다. (defaul 14일)
  • alwaysRemember : 리멤버 미 기능을 항상 활성화 한다.
  • userDetailService : 사용자 정보를 조회하기 위해 내부저긍로 사용하는 객체

7. Remember Me 인증 필터

  • RememberMeAuthenticationFilter : Authentication(인증객체)가 null이고 remember-me 쿠키가 존재하는 경우 이 체인을 탄다.
  • RememberMeService : 두 가지 구현체가 존재한다.
    - Token방식 : 앞서 지정한 일 수 만큼 유지되는 토큰을 메모리에 저장하여, 사용자 토큰과 비교하는 방식.
    • PersistentToken : 토큰 정보를 DB에 저장해 영구적으로 사용하는 방식.

Remember Me Diagram

1. remember-me 쿠키에서 토큰을 추출한다.
2-1. Token이 존재하지 않는경우 다음 필터를 실행한다.
2-2. Token이 존재하는 경우 Token을 Decode하여 정상적인 토큰인지 검증한다. (유효성 체크)
3. 메모리 혹은 DB에 존재하는 토큰과 일치여부를 확인한다.
4. 토큰에 User 계정이 존재하는지 확인한다.
5. 새로운 Authentication 생성한다.

8. AynonumousAuthenticationFilter

인증을 받지 않은 사용자(SecurityContext안에 Authentication 객체가 null인 경우)가 자원

9. 세션 관리

동시 세션 제어

이미 사용자의 세션이 존재한 상태로 새로운 로그인 요청이 들어올 경우 두 세션을 관리하는 정책을 결정한다.

http.sessionManagement().maximumSessions(1)					// 최대 허용 가능 세션 수 , -1 : 무제한 로그인 세션 허용.maxSessionsPreventsLogin(true);		// 동시 로그인 차단함,  false : 기존 세션 만료(default)
  • maximumSessions : 최대 허용 가능 세션 수, -1 : 무제한
  • maxSessionPreventsLogin(boolean) : 동시로그인 차단, false : 기존 세션을 만료 시킨다. (default)

세션 고정 보호

해킹 공격 방식
1) 공격자가 서버로부터 받은 세션쿠키를 사용자에게 전달한다.
2) 사용자는 공격자 세션쿠키로 로그인에 성공해 해당 세션에대한 인증과 인가를 받는다.
3) 공격자가 같은 세션을 사용해 서버에 접근한다.

-> 이를 보호하기위해 로그인시 늘 새로운 세션을 발급하도록 한다.

http.sessionManagement()
                .sessionFixation().changeSessionId() 
  • sessionFixation().changeSessionId()
    - 인증 성공시 사용자의 세션은 그대로 두고 세션 Id만 변경
    - 기존 세션에서 설정한 설정값들 사용가능
    - Servlet 3.1 이상 default

  • sessionFixation().migrateSession()
    - 인증 성공시 새로운 세션 발급 및 새로운 세션 Id 발급
    - 기존 세션에서 설정한 설정값들 사용가능
    - Servlet 3.1 이하 default

  • sessionFixation().newSession()
    - 인증 성공시 새로운 세션 발급 및 새로운 세션 Id 발급
    - 기존 세션에서 설정한 설정값들 사용 불가능

  • sessionFixation().none()
    - 세션 고정 보호 사용하지 않음


세션 정책

protected void configure(HttpSecurity http) throws Exception {

	http.sessionManagement()
		.sessionCreationPolicy(SessionCreationPolicy. If_Required )

}
  • SessionCreationPolicy. Always
    - 스프링 시큐리티가 항상 세션 생성.

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

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

  • SessionCreationPolicy. Stateless
    - 스프링 시큐리티가 생성하지 않고 존재해도 사용하지 않음.
    - JWT등 세션을 사용하지 않는 경우.


10. 세션 제어 필터

SessionManagementFilter

  1. 세션 관리
  2. 동시적 세션 제어
  3. 세션 고정 보호
  4. 세선 생성 정책

ConcurrentSessionFilter

SessionManagementFilter와 연계해 동시적 세션 제어에 관여한다.

  • 매 요청 마다 현재 사용자의 세션 만료 여부 체크.
  • 세션이 만료되었을 경우 즉시 만료 처리.
session.isExpired() == true

-> 로그 아웃 처리, 오류 페이지 응답.

1. 사용자 로그인 후 동일한 사용자가 다시 로그인하여 새로운 세션이 생성되었을 때 기존 세션을 만료시키는 전략.
2. 이전 사용자가 다시 접근 할 때 세션 만료 여부를 확인하여 로그아웃 및 오류 페이지 응답.

사용자가 로그인 할 때 SessionManagementFilter가 가진 세가지 전략을 사용해 세션처리한다.

이후 ConcurrentSessionFilter가 세션을 모니터링하고 만료시킨다.


11. 권한 설정과 표현식

1) 선언적 방식

URL

http.antMatchers("/users/**")

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

인가가 필요한 url 입력. 위에 설정한 모든 antMatchers를 통과해야 해당 자원에 접근이 가능하다.

** 구체적인 경로가 먼저 오고 보다 큰 범위가 뒤에 오도록 설정한다.

Method

@PreAuthorize("hasRole('USER')")
public void user(){}
(나중에 자세히)

2) 동적 방식 - DB 연동 프로그래밍

(나중에 자세히)

12. 인증 예외처리, ExceptionTeanslationFilter

AuthenticationException

: 인증 실패시 발생하는 Exception으로 아래 2작업이 동시에 이뤄진다.
1) AuthenticationEbtryPoint 호출.
2) 인증 예외가 밸상하기 전의 요청 정보를 저장.
- ReqeustCache - 사용자의 이전 요청 정보를 세션에 저장하고 이를 꺼내 오는 캐시 메카니즘.
- SavedRequest - 사용자가 요청했던 request 파라미터 값들, 그 당시의 헤더값들 등이 저장.
(/user 자원 접근 -> 인증 실패 -> 로그인 화면 redirect -> 로그인 -> /user 자원으로 다시 이동.)

AccessDeniedException

: 인가 실패시 발생하는 Exception
- AccessDeniedHandler 에서 예외 처리하도록 제공.

SecurityConfig

protected void configure(HttpSecurity http) throws Exception {
⁠      http.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())   // 인증실패 시 처리.accessDeniedHandler(new CustomAccessDeniedHandler())             // 인가실패 시 처리

		http.formLogin()
        	.successHandler(new LoginSuccessHandler())						//로그인 성공 시 처리}


public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException,
            ServletException {
        
        response.sendRedirect("/login");
        
    }
}

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException,
            ServletException {
        
        response.sendRedirect("/login");
        
    }
}

public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

		RequestCache requestCache = new HttpSessionRequestCache();
        SavedRequest savedRequest = requestCache.getRequest(request, response());		// 사용자가 인증 혹은 인가 에러로 로그인 페이지로 오기전 접근하려고 했던 정보가 이 안에 저장되어있다.
        String redirectUrl = savedRequest.getRedirectUrl();
        response.sendRedirecr(redirectUrl);
        
	}
}

** RequestCacheAwareFilter에서는 이 다음 필터부터 이전 request(requestCache.getRequest)를 사용할 수 있도록 wrapping한다.

참고


13. CSRF(사이트 간 요청 위조)

CSRF 공격 예시.

Form 인증 - CsrfFilter

  • 모든 요청에 랜덤하게 생성되는 토큰을 HTTP 파라미터로 요구.
    - 사용자측에서 사용하는 모든 HTTP에 hidden 값으로 토큰을 삽입한다.
  • 요청 시 전달되는 토큰 값과 서버에 저장된 실제 값이 일치하지 않은 경우 실패처리.

profile
좋은 사람, 좋은 개발자 (되는중.. :D)

0개의 댓글