개발/java

[Spring Security] Spring Security 의 동작과정과 구현

ebang 2024. 4. 14. 22:22
반응형

본 글은 Spring Boot 3 & Spring Framework 6 마스터하기! (https://www.udemy.com/course/spring-boot-and-spring-framework-korean/) 를 보고 정리한 글입니다.

글또 x Udemy 이벤트로 수강하게 된 강의인데, 좋은 기회를 얻게 되어 감사드리는 마음입니다!

컴퓨터에 강한 나라 인도 출신 강사님이 설명해주시는 spring framework 강의로, Hibernate, React, Docker AWS 등 spring framework 뿐 아니라 이를 활용해서 하나의 어플리케이션을 만들기 위한 전반적인 과정을 담고 있습니다.
가장 좋은 점은 정말 핵심 이론만 쏙쏙 골라서 설명한 후 , 코드를 작성하는 방식이라는 점입니다.
군더더기 없이 빠르게 웹 전반과 개발 전략에 대해 학습할 수 있어서 좋았습니다.

Spring Security 가 요청에 대해 인증, 인가하는 방식 : 필터 체인을 활용

순서

  1. CORS , CSRF
  2. Authentication
  3. Authorization

Spring Security

  1. Everything is Authenticated. (including non-exisiting resources)
  2. Spring Security enables form based authentication by default(login form, logout form, logout url)
    -> 폼 기반 인증시 세션을 사용함. 로그인 시, session이 생성되어 session cookie 가 저장됨. 그리고 사이트에 요청을 보낼 때마다 session cookie 를 함께 보내게 된다.
  3. Spring Security also use Basic Authentication
    • REST API 보호에 사용되나, 단점이 많아 프로덕션 환경에서 사용되지는 않음.
    • request header 에 사용자 id, pw 를 암호화해서 저장해서 전송 (단점: 디코딩이 쉬움, 만료기한이 없음, 사용자 권한 정보는 담을 수 없음)

CSRF

Cross Site Request Forgery
[CSRF 공격 막기 - Spring Security] (https://docs.spring.io/spring-security/reference/features/exploits/csrf.html#csrf-protection%5D)
악성 사이트에서 브라우저에 저장된 로그인 쿠키 정보를 이용해서 사이트에 위험한 요청을 보내는 것.
세션을 사용하는 경우 문제가 되는 위조이다.

  • Synchronizer Token Pattern
    • 요청마다 새 토큰을 만들어서 전송 (브라우저가 자동으로 세션 쿠키를 웹사이트로 요청 시 포함하는 것과 다르게, 올바른 요청에서만 해당 토큰을 넣어서 사용할 수 있게끔 하는 것. 즉 쿠키 외의 다른 정보를 요청에 포함하는 방식. 악성 사이트는 respnose body를 읽지는 못하기 때문에[same origin policy] html hidden field에 넣은 채로 사용자에게 응답을 제공하면 이 패턴을 안전하게 사용할 수 있다. )
    • 어차피 애플리케이션의 상태를 바꾸지 않는 GET 요청 같은 read-only http method 에는 csrf 토큰을 사용하지 않는다. 괜히 유출만 되기 때문.
    • 이전에 들었던 토큰을 이용해서 PUT, POST 요청을 해야만 요청이 수행되게끔.
    • 요청을 보내면 해당 세션에 대해 csrf token을 만들어서 서버가 hidden field에 보내준다. 이 값을 이용해서 브라우저가 요청을 해야만 POST, PUT 요청을 하면 요청이 수행된다.
    • form 에서 사용할 수 있지만, 매 페이지에 hidden field로 csrf 토큰을 만들어두어야 한다.
      Spring Security 는 기본적으로 thymeleaf 등을 이용해서 html을 반환할 때 자동적으로 내부에 CSRF 토큰을 넣는다.
    • SameSite Attribute
      • CSRF 위조를 막는 새로운 방법. 외부 사이트에서 해당 사이트와 관련된 쿠키를 보내지 못하도록 하는 방식이다.
      • Spring Security 는 session cookie 의 생성을 직접적으로 조절하지는 않는다. spring Session 이 servlet-based-application에서 sameSite 속성을 제공한다.
      • Cookie 의 SameSite 속성에 들어갈 수 있는 값
        • Strict : 같은 사이트에서 오는 요청에만 쿠키를 포함시킨다.
        • Lax : 같은 사이트 혹은 top-level navation 이면서 read-only 메소드 요청일 때만 쿠키 포함을 허용한다.
      • samesite 속성이 strict 일 경우, 타 사이트에서 해당 사이트로의 접근 자체가 이미 로그인되어 있어도 인증이 안되는 문제가 발생할 수 있다. 또한 브라우저가 samesite 속성을 지원해야 한다는 특징이 있다.
  • RESt API에서 CSRF 토큰 방식 해제하기
  1. SpringBootWebSecurityConfiguration

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnDefaultWebSecurity
    static class SecurityFilterChainConfiguration {

        @Bean
        @Order(SecurityProperties.BASIC_AUTH_ORDER)
        SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
            http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
            http.formLogin(withDefaults());
            http.httpBasic(withDefaults());
            return http.build();
        }

    }

요청에 대해서 인증하는 기본 필터 체인을 가지고 있다.

별도로 필터 체인을 생성해서 사용하기 위해서는 보안설정을 따로 만들면 된다.
HttpSecurity 를 입력받아서 SercurityFilterChain을 반환하는 securityFilterChain Bean을 만들자.
HttpSecurity 는 필터체인 설정에 유용한 클래스이다.

        http.sessionManagement(
                session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );

        http.csrf(AbstractHttpConfigurer::disable);

ALWAYS 는 항상 세션을 설정하고, STATELESS 는 stateless하게 즉 세션을 생성하지 않고 사용도 하지 않는다.
csrf 해제하기

CORS

  • 브라우저는 기본적으로 다른 url 에 AJAX 요청을 보내는 것을 막는다.
  • CORS 설정을 통해 도메인간 요청을 설정할 수 있는 규격이 바로 CORS 이다.
  • CORS 설정의 두가지 방식 ( 글로벌 설정, 매 컨트롤러마다 로컬 설정)
    • 글로벌 설정 : WebMvcController 에 addCoresMapping 콜백 함수를 설정해둔다.
    • 로컬 설정 : Controller 클래스에 @CrossOrigin (모든 url에 대해 허용) 혹은 @CrossOrigin(origins= "url")
  • 사용자 자격 증명 : memory(프로덕션 환경에서는 비추), Database, LDAP(Lightweight Driectory AccessProtocol) 등에 저장
    1. 메모리 : 여러 사용자 설정해보기
      • UserDetailsService : 사용자별 데이터를 로드하는 코어 인터페이스
      • User 객체에서 여러가지 설정을 한 build 결과물을 만든다. (role에 들어가는 String 은 ENUM으로 관리하는 것이 좋겠다.)
      • @Bean public UserDetailsService userDetailService() { UserDetails user = User.withUsername("ebang") .password("{noop}dummy") .roles("USER") .build(); UserDetails admin = User.withUsername("whhat") .password("{noop}dummy") .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); }
  1. JDBC 에서 자격증명 사용해보기
  • Datasource : JDBC를 통해서 데이터베이스와 연결하는 인터페이스
    • 애플리케이션이 데이터 베이스에 대한 연결을 가져오고 반환하는데 사용. (연결정보를 담고 있다.)
  • InMemoryUserDetailsManager 대신 JDBCUserDetailsManager 를 사용해서 사용자 자격 증명을 사용한다.
    • UserDetails 를 만들어두고, JDBCUserDetailsManager 에 넣어서 createUser 한다.
    • DataSource를 통해서 User 관련한 table을 만들고 미리 유저를 만들어 설정할 수 있다.
    • SpringSecurity 가 role 앞에 "ROLE_"을 붙여서 authority를 생성한다.
  • password encryption

Encoding, Hashing, Encryption

  • Encoding : 데이터를 원래 형식에서 다른 형식으로 변환하는 것. (key 나 password 사용없이 가역적으로 사용)
    • 가역적으로, 다시 decoding 이 가능하기 때문에 보안을 목적으로는 사용하지 않는다.
    • 압축, 스트리밍을 목적으로 사용하며 예시로는 Base 64, MP4 등이 있다.
  • Hashing : 데이터를 해시 문자열로 변환하는 것. (비가역적. 원래 데이터를 구할 수 없다. )
    • 데이터 무결성을 검증하는데 사용.
    • 데이터, 데이터의 해쉬를 함께 전송하면 데이터의 무결성을 검증할 수 있다.
    • bcrypt, scrypt
  • Encryption : data 를 key, password 로 인코딩하는 것.
    • key나 패스워드가 있어야만 decoding이 가능하다.
    • RSA

Spring Security 를 이용해서 비밀번호 저장하기

  • SHA-256 해싱을 이용해서 비밀번호를 저장하는 것은 요즘에는 적합하지 않다.
  • 해싱 값을 구하는 것이 매우 빠르게 가능하기 때문에 브루트포스로 비밀번호를 찾을 수 있기 때문이다.
  • 추천하는 방식은 1초를 워크 팩터로 적용해 적응형 단방향 함수를 사용하는 것이다.
  •  

Jwt

Json Web Token

  • contains User Detail, Authorization ㅇ
  • header : type, hashing Algorithm
  • payload :
    iss : The issure,
    sub : the Subject(사)
    aud : the audience,
    exp : expire date
    iat : when was issued
  • signature :
  • Base 64로 header, payload 인코딩, signature 값까지 포함해서 해싱!
  • 암호화, 비암호화 가능
    • 대칭 암호화 : 같은 키로 암호화, 비암호화 (키 저장, 키 공유를 잘해야 함.)
    • 비 암호화 : == 공개키 암호화 기법 공개 키와 비밀 키. 공개키는 아무에게나 공개하되 개인키는 자기만 갖고 있음. 공개키가 있어도 비밀키를 만들기는 매우 어렵다.
  • jwt 는 비암호화 방법을 이용한다.
  1. jwt 만들기 (user credetntial, user data, RSA key pair)
  2. send jwt as part of request header (Authorization header, Bearer Token)
  3. JWT is verified

jwt 보안 설정

  • 스프링 부트 OAuth2 리소스 서버 사용
  1. create key pair
  • java.security.KeyPairGenerator.generateKeyPair()
  1. create RSA Key Object using Key Pair
  • numbusds -> RSA Key Object 생성
  1. create JWKsource (JSON Web Key source)
    JWKSet -> JWKSource
  2. User RSA Public Key for Decoding
  3. user JWKSource for Encoding

implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
class Oauth2ResourceServerConfiguration 서버가 지원해준다 .

  • jwt Configuration 설정 시 jwtDecoder 생성하기

Spring Security에서 인증을 하는 동작원리

    graph TD;
    start(Authentication Manager) --> mid(Authentication Provider) --> last(UserDetails Service);

(티스토리는 mermaid 가 안되네요..)

Spring Security 에서 Authentication이 의미하는 것 3가지

  1. Credential (username, password)
  2. Principal : 사용자에 관한 세부 정보
  3. Authorites : 주체가 가지고 있는 권한 정보
  • AuthenticationManager 에서 authenticate 함수가 호출될 때, 인자인 Authentication 에는 1번, Credential 만 포함되어 있을 것이다.
  • 함수가 성공적으로 끝난다면, 3번 권한도 처리된다.
  • Authentication Manager는 어떻게 사용자 이름과 패스워드가 일치하는 지 알 수 있을까? Authentication Provider 들의 상호작용을 통해서 가능하다.
    • jwt 인증의 경우, JwtAuthenticationProvider가 처리한다.
  • AuthenticationProvider 들 역시 사용자 정보를 알고 있어야 패스워드, 아이디를 대조할 수 있지 않겠나. 그런 Provider 들끼리 대화하는 인터페이스가 바로 UserDetailsService 이다.
    loadUserByUserName 함수를 오버라이딩 함으로써 가능한데, userName을 입력받고 이와 일치하는 UserDetail 을 Authentication Provider에게 리턴한다. Provider는 Credential을 비교한 후, 일치한다면 Principal, Authorities 를 채워넣게 된다.

하나의 Authentication Manager가 다수의 Authentication Provider와 소통하면서 사용자를 인증, 인가 처리한다. 그리고 UserDetails 구현체도 다수 존재할 수 있다. 유저 정보는 데이터베이스, LDAP 등에 존재할 수 있기 때문에.

  • 인증 결과가 저장되는 곳은 SecurityContextHolder.
    graph LR;
    start(SecurityContextHolder) --> mid(Security Context 여기에 저장) --> mid2(Authentication : User Detail 갖고 있음) --> last(GrantedAuthority)

spring Security 의 접근법

  • Global Security
    • FilterChain, requestMatchers
  • Method Security
    • @Pre, @Post Annotation
    • JSR-250 Annotation @EnableMethodSecurity(jsr250Enabled = true), @RolesAllowed()
반응형