hyeonga_code

Spring Security 본문

Project_HYEONGARL

Spring Security

hyeonga 2024. 6. 16. 05:59
반응형

Spring Security.

공식 문서 참조

사용자 지정이 가능한 인증 및 엑세스 제어 프레임워크

Spring 기반 응용 프로그램을 보호하기 위한 사실상의 표준

 

Java 애플리케이션에 인증과 권한 부여를 모두 제공하는 데 중점을 둔 프레임워크로 사용자 지정 요구 사항을 충족하도록 얼마나 쉽게 확장할수 있는지가 중점이다.

 

기능.

- Authentication_인증 과 Authorization_권한부여 에 대한 포괄적이고 확장 가능한 지원

- 세션 고정, 클릭 재킹과 같은 공격에서 보호

- Servlet API 통합

- Spring Web MVC와 선택적 통합

 

필수.

Java 8 이상의 런타임 환경 필요

자체 포함 방식으로 작동하는 것이 목표이므로 JRE에 별도 구성 파일을 배치할 필요가 없음

 

build.gradle 추가

dependencies {
	implementation "org.springframework.boot:spring-boot-starter-security"
}

 

 

Spring Security의 인터페이스는 암호를 안전하게 저장할 수 있도록 암호의 단방향 변환을 수행하는 데 사용된다.

단방향 변환인 경우 암호 변환이 양방향이어야 하는 경우(데이터베이스에 인증하는 데 사용되는 자격 증명 저장) 유용하지 않다.

일반적으로 인증 시 사용자가 제공한 암호화 비교해야 하는 암호를 저장하는 데 사용된다.

PasswordEncoder

 

> 초기 암호는 텍스트 그대로 저장

SQL 인젝션과 같은 공격으로 사용자 이름과 암호의 대규모 데이터 덤프를 얻을 수 있는 방법을 찾아 보안을 위해 사용하게 되었다.

 

> SHA-256 과 같은 단방향 해시를 통해 암호를 실행하고 저장하도록 권장되었다.

사용자가 인증을 시도하면 해시된 암호가 입력한 암호의 해시와 비교된다.(시스템은 암호의 단방향 해시만을 저장하면 된다.)

 

레인보우 테이블이라는 조회 테이블을 만들어 매번 각 암호를 추측하는 작업을 수행하는 대신 암호를 한 번 계산하여 조회 테이블에 저장

 

> 솔트 암호를 사용하도록 권장

해시 함수에 대한 입력으로 암호만 사용하는 대신 모든 사용자의 암호에 대해 임의의 바이트(솔트)가 생성된다.

솔트 및 사용자 암호는 해시 함수를 통해 실행되어 고유한 해시를 생성하고 사용자의 암호와 함께 솔트가 일반 텍스트로 저장된다.

사용자가 인증을 시도하면 해시된 암호가 저장된 솔트의 해시 및 입력한 암호와 비교된다.

고유한 솔트는 해시가 솔트와 암호 조합마다 다르기에 레인보우 테이블이 더 이상 효과적이지 않다는 의미가 되었다.

 

> 암호화 해시는 이제 안전하지 않다.

최신 하드웨어를 사용하면 초당 수십억 개의 해시 계산을 수행할 수 있어 암호를 개별적으로 쉽게 해독할 수 있다.

 

> 적응형 단방향 함수를 활용하여 암호를 저장하는 것이 좋다

적응형 단방향 함수를 사용한 암호 유효성 검사는 의도적으로 리소스를 많이 사용한다.(의도적인 많은 CPU, 메모리 혹은 리소스를 사용)

하드웨어가 상향됨에 따라 증가할 수 있는 작업 요소를 구성할 수 있다.

시스템에서 암호를 확인하는 데 약 1초가 소요되도록 조정한다.

적응형 단방향 함수 : bcrypt, PBKD2, scrypt, argon2

 

모든 요청에 대해 사용자 이름과 암호의 유효성을 검사하면 애플리케이션의 성능이 크게 저하될 수 있다.

Spring Security는 유효성 검사 리소스를 집약적으로 만들어 보안을 확보하므로 암호의 유효성 검사 속도를 높이기 위해 할 수 있는 일이 없다. 장기 자격 증명을 단기 자격 증명으로 교환하는 것이 좋다(세션 혹은 OAuth 토큰)

 

DelegatingPasswordEncoder

현재 암호 저장소 권장 사항을 사용하여 암호가 인코딩되었는지 확인

최신 및 레거시 형식의 암호 유효성 검사를 허용

향후 인코딩 업그레이드 허용

 

PasswordEncoder passwordEncoder = 
	PasswordEncoderFactories.createDelegatingPasswordEncoder();

 

 

Custom DelegatingPasswordEncoder

	// idForEncode : 기본 인코딩 알고리즘의 식별자
String idForEncode = "bcrypt";

	// 여러 패스워드 인코더를 저장할 수 있다
Map encoders = new HashMap<>();

	// 기본 인코딩 알고리즘으로 BCryptPasswordEncoder 인스턴스를 추가
encoders.put(idForEncode, new BCryptPasswordEncoder());

	// 패스워드를 인코딩하지 않고 원문 그대로 저장
encoders.put("noop", NoOpPasswordEncoder.getInstance());

	// PBKDF2 (Password-Based Key Derivation Function 2) 알고리즘을 사용하는 인코더
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());

	// SCrypt 알고리즘을 사용하는 암호 해싱(메모리-하드 해싱 함수)
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());

	// Argon2 해시 함수 기반의 패스워드 인코더
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());

	// SHA-2560 해시 함수를 사용
encoders.put("sha256", new StandardPasswordEncoder());

	// 
PasswordEncoder passwordEncoder =
    new DelegatingPasswordEncoder(idForEncode, encoders);

 

> example

UserDetails user = User.withDefaultPasswordEncoder()
  .username("user")
  .password("password")
  .roles("user")
  .build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

// 재사용 가능
UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
  .username("user")
  .password("password")
  .roles("user")
  .build();

 

 

 

BCryptPasswordEncoder

bcrypt 알고리즘을 사용하여 암호를 해시

암호 크래킹에 대한 저항력을 높이기 위해 bcrypt는 의도적으로 느리다.

시스템에서 암호를 확인하는 데 약 1초가 걸리도록 조정해야 한다.

// 강도를 16으로 설정
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);

// 문자열 해싱
String result = encoder.encode("myPassword");

assertTrue(encoder.matches("myPassword", result));

 

BCryptPasswordEncoder 는 내부적으로 솔트(salt)를 생성하여 해시 과정에 포함하므로 같은 비밀번호라도 다른 해시 값을 생성한다.

 

 


Argon2PasswordEncoder

사용자 지정 하드웨어에서 암호 크래킹을 방지하기 위해 많은 양의 메모리를 필요로하는 의도적으로 느린 알고리즘

// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

 

 

Pbkdf2PasswordEncoder

FIPS 인증이 필요한 경우 적합한 인코더

// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

 

 

 

 

SCryptPasswordEncoder

// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

 

 

 

암호 저장소 구성.

Spring Security는 기본적으로 DelegatingPasswordEncoder를 사용하지만 Spring Bean으로 노출하여 사용자가 정의할 수 있다.

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

 

 

 

종속성.

ehcache  : Echache 기반 사용자 캐시 구현이 사용되는 경우

spring-aop  : 메소드 보안 기반

spring-beans  : Spring 구성에 필요

spring-expression  : 표션식 기반 메소드 보안에 필수(선택)

spring-jdbc  : 데이터베이스 사용하여 사용자 데이터를 저장하는 경우 필수

spring-tx : 데이터베이스 사용하여 사용자 데이터를 저장하는 경우 필수

aspectjrt  : AspectJ 지원 사용하는 경우 필수

jsr250-api  : JSR-250 메소드 보안 어노테이션 사용하는 경우 필수

 

 

로그인 요청이 들어오면 UserDetailService의 loadUserByUsername()을 자동으로 호출하므로 DB에서 조회하게 메소드를 정의해야 한다.

인증 여부를 확인하는 사용자 정보 인터페이스 UserDetails를 구현한 User가 있어야 한다.

 

반응형