spring-security-1일차

박상원·2024년 5월 13일

spring

목록 보기
14/15

로그인

개인이 자신을 식별(identify)하고 인증(authenticate)하여 컴퓨터 시스템에 액세스하는 프로세스

인증

자신이 누구라고 주장하는 주체(principal)를 확인하는 프로세스

비밀번호 저장 방법

단순 텍스트(plain text)

  • 절대 금물!!!

단방향 해시 함수(one-way hash function)의 다이제스트(digest)

해시 (hash)

해시 함수 (hash function)

  • 임의의 길이를 갖느느 임의의 데이터를 고정된 길이의 데이터로 매핑하는 단방향 함수

해시 함수의 리턴 값

  • 해시 값(hash value), hash code, 다이제스트(digest), 그냥 해시(hash)

비밀번호를 해시 값으로 저장하는 이유?

해시의 특성

  • 해시는 고정 길이 -> 원문이 손실된다.
    • 해시 값으로 원문 복원 불가능
  • 원문과 해시 값 사이에 선형적인 관계가 없다.

잘 알려진 해시 알고리즘

MD5 (Message-Digest algorithm 5)

  • 128비트 길이

SHA-1 (Secure Hash Algorithm 1)

  • 160비트 길이

SHA-256

  • 256비트 길이

SHA-512

  • 512비트 길이

단방향 해시 함수의 문제점

단방향 해시 함수는 임의의 길이를 같은 길이로 바꾸다 보니까 충돌이 일어날 수 있다.
그래서 역으로 바꾸는 것이 안된다.

인식 가능성 (recognizability)

  • 동일한 메시지는 동일한 다이제스트를 갖는다.

속도 (speed)

  • 해시 함수는 원래 빠른 데이터 검색을 위한 목적으로 설계된 것
  • 해시 함수의 빠른 처리 속도로 인해 공격자는 매우 빠른 속도로 임의의 문자열의 다이제스트와 해킹할 대상의 다이제스트를 비교할 수 있다.

단방향 해시 함수 보완하기

salt

  • 단방향 해시 함수에서 다이제스트를 생성할 때 추가되는 바이트 단위의 임의의 문자열
  • 특정 비밀번호의 다이제스트를 안다고 하더라도 salt가 추가되면 비밀번호 일치 여부 확인이 어려움
  • salt의 길이는 32바이트 이상이어야 추측하기 어려움

키 스트레칭 (key stretching)

  • 입력한 비밀번호의 다이제스트를 생성하고
  • 생성된 다이제스트를 입력 값으로 하여 다이제스트를 생성하고
  • 또 이를 반복하는 방법으로 다이제스트를 생성해서
  • 입력한 비밀번호를 동일한 횟수만큼 해시해야만 입력한 비밀번호의 일치 여부를 확인할 수 있는 방법

쿠키와 세션

session을 사용할 때 server를 두대를 두면 session repository가 따로 필요하다.
session repository를 db를 쓰면 느리고 비싸다.
session을 참조할 수 있게 cookie에 session 아이디를 남겨놓는다.
session에는 비밀번호를 남기면 안된다.
이유는 server가 해킹당하면 악용 위험이 있기 때문이다.

쿠키에 남기는 정보는?

  • 세션 아이디
    • 쿠키 expire time이 유효할 경우 브라우저를 껐다 켜더라도
    • 쿠키에 저장된 세션 아이디 값을 이용해 서버에 저장된 세션을 불러올 수 있다.
      • session repository 필요

쿠키에 남기면 안되는 정보는?

  • 개인정보 금물!!!
    • 클라이언트에 저장되므로 위조 및 변조 가능

쿠키는 보통 사용자의 브라우저에 남겨도 해가되지 않는 정보를 남긴다.

세션에 남기는 정보는?

  • 동일 세션에서는 굳이 반복적으로 요청하지 않아도 되는 로그인한 회원 정보

세션은 서버에서 서비스에 필요한 정보를 매번 db에서 가져오지 않기 위한 값을 저장한다.

Spring Security


Spring Security 란

  • Spring 기반 애플리케이션을 위해 선언적 보안 기능을 제공하는 보안 프레임워크
  • Servlet Filter (Servlet 기반 애플리케이션) 및 AOP 기반

Spring Security가 좋은 점은 선언적이고 비 침투적이기 때문이다.
웹 같은 경우는 filter를 기반으로 되어있고 method security는 AOP를 기반으로 구현되어 있다.

Spring Security는 원래 Spring Framework랑 따로 만들어졌다.

Spring Security 주요 기능

  • 다양한 인증(Authentication) 지원
  • 권한 관리(Authorization)
  • 취약점 공격 방어

취약점 공격 방어 기능

Security Http Response Headers

  • Cache Control
  • Content Type Options
  • X-Frame-Options
  • X-XSS-Protection
  • HSTS (HTTP Strict Transport Security)
  • ...

보통 cache를 사용하는 이유는 서버의 부담을 줄이기 위해서이다.
HTTP 헤더에 Cache-Control에 있는 cache는 브라우저에 있는 cache를 의미하는 것이다.
X-는 공식 헤더가 아니라서 해석 못하는 브라우저가 있을 수도 있다.

기본값 해제 설정

http
      .headers()
          .defaultsDisabled()

Cache Control

기본값

Cache-Control: no-cache, no-store, max-age=0, must-revalidate

설정

http
      .headers()
          .cacheControl()/*.disable()*/

Content Type Options

기본값

X-Content-Type-Options: nosniff

설정

http
      .headers()
          .contentTypeOptions()/*.disable()*/

X-Frame-Options

기본값

X-Frame-Options: DENY

설정

http
      .headers()
          .frameOptions()/*.deny()*/.sameOrigin()

X-XSS-Protection

기본값

X-XSS-Protection: 1; mode=block

설정

http
      .headers()
          .xssProtection
              /*.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK)*/.disable()

HSTS (HTTP Strict Transport Security)

기본값

  • https 요청일 때만 적용
Strict-Transport-Security: max-age=31536000 ; includeSubDomains

설정

http
      .headers()
          .httpStrictTransportSecurity()
              .includeSubdomains(true)
              .maxAgeSeconds(31536000)         /* 365일 */

해당 헤더는 https요청을 강제하는 헤더이기 때문에 http를 지원하는 경우 설정하면 매우 치명적으로 작용하는 헤더이다.

CSRF 방어

CSRF (Cross Site Request Fogery: 사이트간 요청 위조)

  • CSRF Token
    • cf.) Spring Security JSP Tag Library

설정

http
      .csrf()
          /*.disable()*/

웹 요청 ACL 스프링 표현식


권한 부여

  • application.yml 파일
spring
  security:
    user:
      name: academy
      password: 12345
      roles:
        - MEMBER

Expression-Based Access Control

기본 제공 표현식

표현식설명
hasRole('권한'), hasAuthority('권한')해당 권한을 가졌는가?
hasAnyRole('권한1', '권한2'), hasAnyAuthority('권한1', '권한2')지정한 권한 중 하나라도 가졌는가?
permitAll()모두 허용
denyAll()모두 거부
isAnonymous()익명 사용자인가?
isAuthenticated()인증된 사용자인가?

인증 기본 개념


role과 authority의 차이는 role은 권한 앞에 ROLE을 붙여서 매칭하고 authority는 입력받은 권한 자체로 매칭을 한다.
authority의 앞에 ROLE
을 붙이는 것이 관례이다.

Authentication / Authorization

Authentication (인증: authn)

  • 자신이 누구라고 주장하는 주체를 확인하는 프로세스
    • 주체(principal) : 사용자, 디바이스, 다른 시스템
  • (the process of determining whether who or what it declares itself to be)

Authorization (인가, 권한 부여: authz)

  • 어플리케이션 내에서 주체(principal)가 어떤 행위를 수행하도록 허락되었는지 여부를 결정하는 프로세스

Authentication -> Authorization

  • 권한 체크 전에 먼저 로그인하라고 하는 이유

Authentication

Authentication

public interface Authentication {
  Object getPrincipal();    // 주체
  Object getCredentials();  // 자격증명
  Collection<? extends GrantedAuthority> getAuthorities();
  // ...
}

GrantedAuthority

  • 어플리케이션 내에서 주체에 부여된 권한

아이디/비밀번호 인증 관련 주요 개념

앞서 보았던 Authentication과 Principal

  • Principal의 type이 Object
interface Authentication {
    Object getPrincipal();
}
  • 주체 (principal)은 객체 -> 대개의 경우 UserDetails 객체로 cast됨

UserDetails

public interface UserDetails extends Serializable {
  String getUsername();
  String getPassword();
  Collection<? extends GrantedAuthority> getAuthorities();
  // ...
}
  • 데이터베이스에 저장되어 있는 사용자의 정보와 SecurityContextHolder에 저장될 정보 사이의 Adapter라고 생각하면 됨

인증 처리

UserDetailsService

  • UserDetails를 가져오기 위한 DAO 구현
    • DAO(Data Access Object): DB에 접근하는 객체
public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

PasswordEncoder

  • 사용자가 입력한 비밀번호와 UserDetails.password를 검증
public interface PasswordEncoder {
    String encode(CharSequence rawPassword);
    boolean matches(CharSequence rawPassword, String encodedPassword);
    // ...
}

UserDetailsService 구현

InMemoryUserDetailsManager

@Bean
public InMemoryUserDetailsManager userDetailsService() {
    return new InMemoryUserDetailsManager(User.withUsername("user")
            .password("{noop}user")
            .authorities("ROLE_USER")
            .build());
}

JdbcUserDetailsManager

  • JdbcDaoImpl에 정의된 테이블 구조를 사용
  • JdbcTemplate 이용

Custom UserDetailsService

  • 직접 UserDetailsService interface 구현

로그인/로그아웃 커스터마이즈


formLogin() 커스터마이즈

http
    .formLogin()
        .loginPage("/login/form")
        .usernameParameter("name")
        .passwordParameter("pwd")
        .loginProcessingUrl("/login/process")
        .failureHandler(loginFailureHandler())

.loginPage()

  • 로그인 폼 URL
  • default: GET /login

.loginProcessingUrl()

  • 로그인 처리 URL
  • default: POST /login

.usernameParameter()

  • 아이디 파라미터 이름

.passwordParameter()

  • 비밀번호 파라미터 이름

.successHandler()

  • 인증 성공 처리 핸들러
  • AuthenticationSuccessHandler interface를 구현한 객체를 지정

.failureHandler()

  • 인증 실패 처리 핸들러
  • AuthenticationFailureHandler interface를 구현한 객체를 지정

logout() 커스터마이징

http
    .logout()
        .deleteCookies("A-COOKIE", "B-COOKIE")
        .invalidateHttpSession(true)
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login?logout")
        .addLogoutHandler(logoutHandler())

.logoutUrl()

  • 로그아웃 URL
  • default: /logout

.logoutSuccessUrl()

  • 로그아웃 성공 후 리다이렉트될 url
  • default: /login?logout

.deleteCookies()

  • 삭제할 쿠키의 이름을 comma로 구분해서 지정

.invalidateHttpSession()

  • 세션 삭제 여부

.addLogoutHandler()

  • 로그아웃 처리 핸들러 추가
  • LogoutHandler interface를 구현한 객체를 지정

권한 없음 403 화면 커스터마이징

http
    .exceptionHandling()
        .accessDeniedPage("/error/403")

accessDeniedPage() 설정

  • 권한 없음 오류 발생 시 리다이렉트될 url

Spring Security Thymeleaf Tag Library


Taglib 선언

  • XML Namespace 선언
<html lang="ko" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<!-- ... -->
</html>

authorize 속성

Case 1

<li sec:authorize="hasRole('ROLE_MEMBER')">

Case 2

<li sec:authorize-url="/public-project/2">

authentication 태그

<div sec:authentication="principal.username" />

csrfInput 태그

<form method="post" action="/login/process">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />

    <!-- ... -->
</form>

csrf 토큰

<!DOCTYPE html>
<html>
<head>
  <title>CSRF Protected JavaScript Page</title>
  <meta charset="UTF-8">
  <meta id="_csrf" name="_csrf" th:content="${_csrf.token}" />
  <meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}" />

  <script type="text/javascript" language="javascript">
    var csrfHeader = $("meta[name='_csrf_header']").attr("content");
    var csrfToken = $("meta[name='_csrf']").attr("content");
  </script>
 </head>
 <!-- ... -->

웹 보안 아키텍처


  • Servlet 기반 애플리케이션 아키텍처 (Servlet Filter 기반)

Spring Security의 서블릿 필터

  • DelegatingFilterProxy를 이용

DelegatingFilterProxy

  • 지정된 이름의 bean으로 서블릿 필터 처리 위임

Spring Boot 에서는

  • SecurityFilterAutoConfiguration

springSecurityFilterChain

Spring Security에서 DelegatingFilterProxy에 설정하는 서블릿 필터 bean

  • 이름
    • springSecurityFilterChain
  • 타입
    • org.springframework.security.web.FilterChainProxy

FilterChainProxy

  • 여러 보안 필터 체인의 리스트 형태로 구성된 SecurityFilterChain들로 처리를 위임
public class FilterChainProxy extends GenericFilterBean {
    private List<SecurityFilterChain> filterChains;

    // ...
}

Spring Security Filter Chain

  • Spring Security는 각각의 보안 필터마다 특별한 역할을 가지고 있는 보안 필터 체인을 가지고 있다.
  • 보안 필터들 간에 의존관계가 있기 때문에 필터 순서가 중요
public interface SecurityFilterChain {
  boolean matches(HttpServletRequest request);
  List<Filter> getFilters();
}

SecurityFilters

package org.springframework.security.config.http;

enum SecurityFilters {
    FIRST(Integer.MIN_VALUE),
    CHANNEL_FILTER,
    SECURITY_CONTEXT_FILTER,
    CONCURRENT_SESSION_FILTER,
    WEB_ASYNC_MANAGER_FILTER,
    HEADERS_FILTER,
    CORS_FILTER,
    CSRF_FILTER,
    LOGOUT_FILTER,
    X509_FILTER,
    PRE_AUTH_FILTER,
    CAS_FILTER,
    FORM_LOGIN_FILTER,
    OPENID_FILTER,
    LOGIN_PAGE_FILTER,
    DIGEST_AUTH_FILTER,
    BEARER_TOKEN_AUTH_FILTER,
    BASIC_AUTH_FILTER,
    REQUEST_CACHE_FILTER,
    SERVLET_API_SUPPORT_FILTER,
    JAAS_API_SUPPORT_FILTER,
    REMEMBER_ME_FILTER,
    ANONYMOUS_FILTER,
    SESSION_MANAGEMENT_FILTER,
    EXCEPTION_TRANSLATION_FILTER,
    FILTER_SECURITY_INTERCEPTOR,
    SWITCH_USER_FILTER,
    LAST(Integer.MAX_VALUE);
}

주요 보안 필터들

ChannelProcessingFilter

  • redirect to a different protocol (http -> https)

SecurityContextPersistenceFilter

  • SecurityContext 객체를 SecurityContextHolder에 저장
    • 어디에 저장? SecurityContextRepository (default: HttpSession)
  • 요청 처리가 끝나면 제거

ConccurrentSessionFilter

  • 현재 Session 유효 여부를 파악하여 유효하지 않는 세션에 대한 후처리
  • SessionManagementFilter와 연동 처리

HeaderWriterFilter

  • 현재 요청에 HTTP Header 추가
    • Cache-Control
    • X-Content-Type-Options
    • X-Frame-Options

CsrfFilter

  • Csrf(Cross-site Request Forgery: 사이트 간 요청 위조) 공격을 막기 위한 처리

LogoutFilter

  • 특정 URI를 체크하여 Logout을 실행
  • Logout 처리 (LogoutHandler)
  • Logout 성공 후 처리 (LogoutSuccessHandler)

PRE_AUTH_FILTER

  • AbstractPreAuthenticatedProcessingFilter를 상속받아 구현
  • ex) X.509

UsernamePasswordAuthenticationFilter

  • 특정 URL에서 username, password를 통한 인증 프로세스 진행
  • 인증 처리는 AuthenticationManager에게 위임
  • 인증 성공 처리 (SuccessHandler)
  • 인증 실패 처리 (FailureHandler)

RequestCacheAwareFilter

  • 인증 성공 후 기존 요청을 찾아가기 위해 기존 요청을 저장
    • 어디에 저장? RequestCache (default: HttpSession)
      • session attribute : SPRING_SECURITY_SAVED_REQUEST

AnonymousAuthenticationFilter

  • 인증이 안 된 사용자에게 anonymousUser라는 이름의 Authentication 객체를 설정하고
  • ROLE_ANONYMOUS 권한을 부여

SessionManagementFilter

  • 세션 타임아웃, 동시 접근 제어 등을 처리

ExceptionTranslationFilter

  • 이 필터 이후의 모든 인증(AuthenticationException), 권한(AccessDeniedException) 예외 처리

ExceptionTranslationFilter는 새로운 인증 방법을 구현하였을 때 테스트로 사용하는 경우도 있다.

AuthorizationFilter

  • 권한 프로세스를 처리하는 Filter
  • authorizeHttpRequests() 내용을 기준으로 권한 처리

Custom Filter

Custom Filter로 기존 필터를 대체하거나 추가 가능

http
      .addFilterAt(yourFilter, CasAuthenticationFilter.class)
      .addFilterBefore(myFilter1, UsernamePasswordAuthenticationFilter.class)
      .addFilterAfter(myFilter2, CsrfFilter.class)

0개의 댓글