- 인증 (
Authentication) :
。사용자의 신원을 증명하는 과정 ( ex.ID/PW 로그인,JWT등 )
▶ 사용자의 로그인 정보를 받아서 ID정보가 일치하는지 확인하는 과정.
Multiple Authentication: 복수의 인증이 존재하는경우
▶ID/PW로 1차 인증 후SMS로 2차 인증.
- 인가 (
Authorization) :
。로그인을 통해Authenticated된 사용자가 Server의 특정 Resource에 Access 가능한 권한이 있는지 확인하는 과정.
▶ 적절한Access권한을 지니고있는지 판단.
Encoding:
。데이터를 특정Data type으로 변환하는 과정.
▶ 주로 미디어 데이터를 압축 및Streaming하거나,Text데이터를 안전하게 전송하거나 저장할 수 있도록Data type을Binary로 변경할 때 사용.
。Encoding은Decoding을 통해 원래의 데이터로 복원이 가능.
。데이터의 호환성을 보장하며, 주로
Encoding대표 예시 : (Base64,Wav,MP3)
base64
。이진Binary Data를 64개로 구성된ASCII문자 데이터로Encoding하여 변환.
▶ 데이터(JSON,XML,Binary Data등 )를TEXT로 변환하여 안전하게 전송이 가능.
。단순Encoding방식이므로, 보안성이 없어 암호화 목적으로는 사용하지 않음.
▶Basic Authentication의header를 획득 시decoding하여 쉽게 사용자의ID와PW를 획득할 수 있으므로.
。양방향변환으로서Base64로Encoding한Text데이터는 다시Decoding하면서 원본으로 복원 할 수 있다.
Base64활용
image,fileData의binary data를Text로Encoding후 전송하여 다시binary data로Decoding하여 활용.
HTTP Basic Authentication:Basic Base64인코딩한ID:PW
。JavaScript에서 생성 시window.btoa(문자열)사용. (Binary to ACSII)
const ba = 'Basic ' + window.btoa(ID + ':' + PW)
▶ 현재는JWT사용.
Hashing
。Data를 고정된 길이의 고유한Hash로 변환하는 과정으로 복원이 불가능한 단방향 변환.
▶ 주로 보안 및 데이터 무결성 검증에 사용
。단방향변환으로 원본 입력값으로 복원이 불가능
。입력값 크기에 관계없이 고정된 길이의Hash생성. (SHA-256: 64자 )
。충돌방지를 예방하기 위해 서로 다른 입력값으로Hasing시 동일한Hash를 갖지 않도록 설계.
。Bcrypt,Scrypt을 통한 암호화 알고리즘을 사용하여 보안성 존재.
▶ 현재는SHA-256을 사용하지 않는다.
Spring Security -BCrypt hashing활용
Hashing활용
- 비밀번호 저장용도
。DB에 비밀번호를 특정Hash알고리즘으로Hashing하여Hash값으로 저장 및 로그인 시 DB에 저장된Hash값을 참조하여 입력된 비밀번호를Hashing하여 비교를 통해 검증 수행.
。Hash값은 원본으로 복원이 불가능하므로,DB에 저장 시 공격자가DB에 Access 하더라도Hash값만 참조가능하고 원본PW은 확인 불가능.
- 데이터 무결성 검증
。파일이 변조되었는지 확인하는 용도로 사용
▶ 데이터와 데이터의 Hash를 함께 전송하여 전송후의 데이터의 Hash가 서로 일치하는지 확인.
- 디지털 서명 및 인증서
Encryption
。데이터의 기밀성을 보호하도록 데이터를 안전하게 변환하여 보호하는 과정.
▶Encryption된 데이터는 암호화 key 없이는 원본데이터를 알 수 없게 구축.
。Encoding과 동일하게Decryption을 수행하여 원본값으로 복호화가 가능하지만 암호키가 필요.
。주로 보안목적으로 활용됨.
Encryption예시
AES( Advanced Encryption Standard ) :
。동일한 키로 암호화와 복호화가 수행되는 대칭키 암호화 방식
- 대칭키 암호화 방식 ( Symmetric Encryption ) :
DES,AES,RC4, ...
。단일 공유키(Secret Key)로 암호화, 복호화 수행
▶Encryption,Decryption에서 발신자와 수신자가 동일한 암호화 키를 사용.
。구현이 간단하여 빠르고 효율적이지만, 공유키를 안전하게 공유하는 것이 어렵다.
▶ 암호문을 타인과 공유 시 공유키를 적절하게 공유할 방법도 찾아야 하므로.
RSA( Rivest-Shamir-Adleman ) :RSA,ECC,DSA
。공개키(Public Key)로 데이터를 암호화하고 개인키(Private Key)로 복호화하는 비대칭키 암호화 방식
▶JWT Token에서 활용하는 방식.
- 비대칭키 암호화 방식 ( Asymmetric Encryption )
。공개키 (Public Key)와 개인키(Private Key) 한 쌍을 사용하여 암호화, 복호화를 수행 방식
▶ 발신자가Encryption시 공개키를 사용하고, 수신자는Decryption시 개인키를 사용
。대칭키 암호화 방식에 비해 데이터를 안전하게 공유가 가능하지만 복잡한 연산을 수행하므로 연산속도가 느리고, 키 길이가 길어야 보안이 보장.
。공개키 (Public Key) :Encryption시 활용하며 누구나 알 수 있고 사용가능.
。개인키(Private Key) :Decryption시 활용하며 본인만 알 수 있음.
▶ 개인키는 반드시 안전하게 보관해야한다.
。데이터를 위조할 수 없도록 보장하므로 디지털 서명이 가능.
보안의 원칙
- 기본 실패 ( Fail-Safe Defaults ) :
。System이 Fail하더라도 기본적으로 안전한 상태를 유지하도록 설정한다는 원칙.
▶ 권한이 명시적으로 허가되지않은 모든 자원에 대한 접근을 차단하는것을 기본으로 설정.
ex) 방화벽에서 모든트래픽차단 후 허용할 트래픽만 부분적으로 추가하는 방식.
- 최소권한 ( Least Privilege )
。각 사용자 또는 Process에게 항상 작업수행에 필요한 수준의 최소한의 접근권한을 할당.
▶ 해당 원칙을 따르면 보안사고가 발생해도 피해최소화가 가능
ex) 일반사용자 수준에는 관리자권한없이 실행되도록 설정 및 관리장계정이 필요한 작업이 있는경우에만 관리자권한을 부여.
- 메카니즘의 효율성 ( Economy of Mechanism )
。보안 Architecture의 설계는 이해가 쉽도록 최대한 단순해야한다.
▶ 복잡한 보안시스템의 경우 Error가 발생할 가능성이 높고 유지보수가 어려우므로.
ex) 인증시스템을 여러단계로 나누는 대신, 단순하면서도 강력한 단일 인증기법을 적용
- 완벽한 조율 ( Complete Mediation )
。보안시스템에 대한 모든 접근요청은 항상 검증해야한다.
▶ 기존에 캐시된 인증정보를 사용하는 것이 아닌, 매번 새로운 인증절차를 거쳐야함.
ex) 사용자가 로그인한 후에도, 중요한 작업(예: 계좌 이체)을 수행할 때마다 비밀번호를 다시 입력하게 하는 방식.
- 개방 설계( Open Design )
。보안은 비밀유지에 의존해서는 안된다.
。보안 시스템의 설계는 비밀유지가 아닌, 강력한 보안알고리즘과 보안정책에 기반해야하는 원칙.
▶ 보안알고리즘이 공개되더라도 안전하게 보안시스템이 유지되어야한다.
ex) 오픈소스 암호화 알고리즘 (JWT등)은 코드는 공개되어있더라도 여전히 안전하게 사용가능.
- 권한분리 ( Separation of Privilege )
。중요한 작업 수행 시 복수 이상의 독립적인 권한이 필요하도록 설계해야한다는 원칙.
▶ 하나의 권한이 노출되더라도 전체 시스템이 위험에 빠지지 않도록 보호하는 역할을 수행.
ex) 금융 시스템에서 한 사람이 결제를 승인할 수 없고, 다른 사람이 별도로 확인해야 결제가 이루어지도록 설정.
+- 최소 공통 Mechanism ( Least Common Mechanism )
。서로 다른 사용자 또는 프로세스가 공유되는 보안 Mechanism의 수와 사용은 최소화해야한다.
▶ 공유하는 보안Mechanism이 많을 경우 하나가 뚫릴 경우, 전체 시스템이 연쇄적으로 위험해질 수 있다.
ex) 여러 사용자가 동일한 세션을 공유하지 않고, 각 사용자에게 개별 세션을 할당하는 방식.
- 심리적 수용성( Psycological acceptability / Easy to use )
。사용자가 보안시스템을 사용하는 경우 편리하게 사용할 수 있어야한다는 원칙.
▶ 너무 복잡한 보안정책의 경우 사용자가 기피하거나 비밀번호를 다른곳에 저장하는 등 보안취약점을 생성할 수 있으므로.
ex ) 너무 복잡한 비밀번호 정책 대신, 다중 인증(MFA)을 도입하여 보안성을 유지하면서도 사용자의 편의를 높이는 방식.
Spring Security
。
Spring 생태계의REST API와Web Application의 보안(인증: Authentication / 인가: Authorization 등)을 제공하는 Spring Framework.
▶ 보안에 필요한 기능을 제공함으로써 개발자가 보안 관련 logic을 따로 작성할 필요는 없음.
。Complete Mediation : Applicaiton에 Dependency가 적용되는 경우 Applciation의 모든 Resource가 보호되어 접근하는 모든API호출에 대해Security Filter Chain에 의한Authentication을 수행.
▶ 기존에Controller Method에 의해 정의된 API 외에도 존재하지않는 API를 호출하는 경우에도Login Form을 통해 자격증명을 요구.
。Spring Security 6.x부터는 람다기반 설정방식 권장
。Spring Filter Chain을 통해 Customize가 가능한 유연한 Security System을 제공하여 사용자에게 적절한Authentication과Authorization이 부여.
- 기본 로그인페이지와 로그아웃 페이지 URL 제공
。Spring Securitydependency 추가 시 기본적으로 제공되는 기능.
localhost:8080/login:Spring Login Form도출
localhost:8080/logout: 기존 로그인된 자격증명을 로그아웃
。Session Cookie가 삭제되어 차후HTTP Request전송 시Authentication Header에 포함되지않아서 요청이 거절됨.
▶ HTML의<a class="nav-link" href="/logout">으로 간단하게 로그아웃 버튼을 생성할 수 있다.
- Spring Security Dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>Spring Security 원리 Spring MVC - Dispatcher Servlet
- 기존
Spring Application의Spring MVC디자인 패턴에서REST API호출을 위한Dispatcher Servlet으로 전송되는 모든HTTP Request를Spring Security가 Intercept하여Spring Filter Chain을 통해HTTP Request의Authorization과Authentication을 검증 후Dispatcher Servlet으로 전달하여 최종적으로Contoller로 전달.
Spring Filter Chain에 의한Authentication검증 순서
1.HTTP Request의Authentication Header의 정보를 포함한Authenticationinstance를AuthenticationManager객체.authenticate(Authentication객체)로 전달.
。해당Authenticationinstance는Credentials정보만 포함한 미완성된 instance.
。AuthenticationManager에 의해Authentication Header의 인증방식 ( ex.JWT)에 해당하는AuthenticationProvider를 식별 및 적용 ( ex.JwtAutheticationProvider)
2.AuthenticationManager객체.authenticate(Authentication객체)에서AutheticationProvider에 의해 사용자 인증정보의 검증을 수행
。UserDetailsService를 호출하여 매개변수로 전달된Authenticationinstance의Credentials정보에서 포함된username에 해당하는 사용자정보를DB에서 조회 후UserDetailsinstance로 return.
▶Authenticationinstance의Credentials의 Password와UserDetailsinstance의 Password를 서로 비교하여 검증을 수행.
3. 검증의 결과값 확인
。Password의 비교 후 검증이 완료된 경우,principal,authorities,credentials정보를 포함한 완전한Authenticationinstance를 반환.
。검증에 실패한 경우AuthenticationException발생.
Authenticationinterface :
org.springframework.security.core.Authentication
。Spring Security에서Authentication정보를 지시하는 Interface
▶ 사용자의 인증상태 , 권한 , 인증된 사용자정보 등을 포함.
。HTTP Request의Authentication Header에 포함된 사용자 인증정보를Controller Method의 매개변수로 사전에AuthenticationManager객체.authenticate(Authentication객체)에 의해 사용자인증정보가 검증되어principal,authorities,credentials정보를 포함한Authenticationinstance로서 mapping되어 전달됨.
SecurityContextHolder,SecurityContext,Authentication관계SecurityContextHolder └── SecurityContext (보안 컨텍스트) └── Authentication (인증 정보) ├── Principal (사용자 정보) ├── Credentials (비밀번호, JWT 등) └── Authorities (권한 정보)Spring
Authenticationinstance 구성
▶ Client에서 전송된HTTP Request의HTTP Basic Authentication인증정보가 포함된Authentication instance
principal:
。인증된 사용자의 세부정보를 의미.
▶ 보통UserDetailsinstance를 포함.
credentials
。사용자가 인증 시 입력한 정보 ( ex.username,password)
▶ 인증이 완료된 경우, 보안을 위해null로 설정됨.
authorities
。principal에 정의된 사용자가 보유한 역할, 권한 목록을 지시.
▶ 주로"ROLE_USER" , "ROLE_ADMIN"등의 권한정보를 포함.
。검증전의credentials정보만 포함된Authenticationinstance를AuthenticationManager객체.authenticate(Authentication객체)로 전달 시 사용자인증정보를 검증 후 인증성공 시principal,authorities정보를 추가로 포함한 완전한Authenticationinstance를 return.
AuthenticationManager
。Spring Security의 사용자 인증(Authentication)의 검증을 담당하는 Interface.
▶AuthenticationManager수많은AuthenticationProvider들을 등록 및 관리.
。특정 인증방식의 수많은AuthenticationProvider를 등록 시 사용자가 다양한 인증방식(JWT등 )을 통한 로그인을 수행해도 해당 인증방식을 제공하는 등록된AuthenticationProvider를 자동으로 식별하여 인증을 수행.public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }
AuthenticationManager객체.authenticate(Authentication객체)
。credentials정보만 포함된 미완성된Authenticationinstance를 전달하여 사용자의Authentication정보를 검증 후 인증 성공 시principal,authorities가 추가된 완전한Authenticationinstance를 반환하고, 인증에 실패 시AuthenticationException발생.
AuthenticationProvider
。Spring Security의 특정 사용자인증방식을 처리하는 Interface.
▶AuthenticationManager에 등록하여 사용자가 해당AuthenticationProvider가 제공하는 인증방식의 로그인요청을 보낼 경우AuthenticationManager에 의해 식별되어 인증을 수행하게됨.
。AuthenticationManager에 의해 식별되어Authenticationinstance의Credential의Password와UserDetailsService를 호출하여 해당Credential의username에 해당하는 사용자정보를DB에서UserDetailsinstance로 가져온 후 해당Password를 서로 비교하여 사용자인증의 검증을 수행.
▶ 검증이 성공할 경우,principal,authorities정보를 추가로 포함한 완전한Authenticationinstance를 return.
AuthenticationProvider종류
JwtAutheticationProvider:
。JWT기반 인증을 처리하는AuthenticationProvider.
UserDetailsService:
。Spring Security에서 사용자 인증정보의 조회를 위해 DB에서 저장된 사용자정보를 조회하는 역할을 수행하는Core Interface.
。 주로AutheticationProvider에 의해 호출되어DB에서 저장된 사용자정보를 조회 및 해당하는 사용자정보를UserDetailsinstance로 return하는 역할을 수행.
▶AutheticationProvider에 의해HTTP Request의Auhtentication Header정보를 포함한Authenticationinstance의Credentials와UserDetailsinstance의Password를 비교하여 사용자인증의 검증을 수행.
UserDetailsService.loadUserByUsername("username"):
。사용자가 자격증명을 전달 시DB에서 해당 자격증명의 ID 문자열 데이터에 해당하는 사용자 정보를 조회 후UserDetails객체로 반환.
▶AutheticationProvider에 의해Authenticationinstance의Credentials와UserDetailsinstance의Password를 비교하여 사용자인증의 검증을 수행.
SecurityContextHolder:
。현재 실행중인thread의SecurityContext를 저장 및 관리하는 Class.
▶ThreadLocal을 통해Thread별로SecurityContext를 저장.
。인증이 완료된Authenticationinstance가 저장된SecurityContext객체를 저장.
▶Application어디에서든지 현재 로그인한 사용자 인증정보를 가져올 수 있음.
SecurityContext:
。현재 사용자의 인증정보를 포함하는Container역할의 객체
▶AuthenticationProvider에 의해 인증이 완료된Authenticationinstance를 저장
。SecurityContextHolder를 통해 접근 가능.
SecurityContextHolder,SecurityContext,Authentication관계SecurityContextHolder └── SecurityContext (보안 컨텍스트) └── Authentication (인증 정보) ├── Principal (사용자 정보) ├── Credentials (비밀번호, JWT 등) └── Authorities (권한 정보)
WebSecurityConfiguration:
。Spring Security의 웹보안 Configuration을 담당하는@ConfigurationClass.
▶@Bean Method를 생성 후SecurityFilterChaininstance를 활용하여Authentication,Authorization,CSRF Protection등의 보안정책을 해당Configuration Class에서 설정이 가능.
SpringBootWebSecurityConfiguration
。Servlet Application을 보호하는 역할의 웹보안Configuration Class.
▶ 웹보안을 위한 Default로 설정된Configuration Class.
。Spring은 기본적으로FilterChain을 통해 명시적으로 인증된HTTP Request에 대해서만 허용 및formLogin()과httpBasic()이 기본적으로 활성화 설정되도록 설정됨.
▶CsrfFilter도 기본적으로 활성화 설정이 되어있음.
SecurityFilterChain:
import org.springframework.security.web.SecurityFilterChain;
。Spring Security에서 제공하는 보안 filter 모음 역할의Interface.
(Authentication Filter,Authorization Filter,CORS Filter,CSRF Filter,ExceptionTranslation Filter등)
▶Spring Security의 핵심이 되는 기능을 제공하며 사용자에게 적절한Authentication과Authorization이 부여됨
。HttpServletRequest에 대응하는 FilterChain을 정의.
。SecurityFilterChain의 구현 Class로서HttpSecurity가 존재하며HttpSecurity객체.build()로 생성된HttpSecurity instance를SecurityFilterChain의 instance로서 활용이 가능.
▶@Configuration이 선언된 Class에서@Bean Method의 매개변수로 전달한HttpSecurity객체에서 Custom된FilterChain를 적용한HttpSecurity객체의 Instance를SecurityFilterChainInstance로 반환.
。Filter Chain의 경우 filter에 따른 Validation이 순서대로 이루어짐.
CORS,CSRF등의 Filter : 기본필터Authentication Filter: 적절한 자격증명 보유여부 FilteringAuthorization Filter: 접근하려는 Resource에 적절한 접근권한이 있는지 Filtering
Security Filter Chain핵심적인 기능
- Controller에 Mapping된 모든
URL이 보호.- 승인되지 않은 요청에 대해서는 로그인 양식이 표현됨.
▶ URL로Http Request가 전송되었을때,SecurityFilterChain의 필터를 통해 사용자가 인증(Authenticate)되지 않은 경우,Login Form을 표시하는 역할을 수행.
- 실습용 Spring Project 생성
。Gradle Build Tool설정. Gradle 관련
。dependency :Spring Web,Spring Security
。Spring Dev Tooldependency 추가 시 다음 구문을build.gradle의dependencies { }에 추가.
implementation 'org.springframework.boot:spring-boot-devtools'
- 정적 개발용 자격증명 설정 및 실습용
REST API정의// application.properties spring.security.user.name=user123 spring.security.user.password=pw456▶ 해당 개발용 자격증명의 경우
In-Memory DB에 저장되어 배포환경으로는 부적합.import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; @RestController public class Security_Filter_Chain { private List<Todo> TODOS = new ArrayList<Todo>( List.of(new Todo("wjdtn1", "UOS"), new Todo("wjdtn2","KHU"))); @GetMapping(path="/todos") public List<Todo> returnTodos(){ return TODOS; } } record Todo (String username, String description) {}。
record를 통해TodoClass instance를 쉽게 생성.
。List.of(요소):immutable List생성 시 사용하는 Method.
Spring Application의REST API에 접근하는 방법
Spring의 Login Form을 통한 로그인
。사용자가 로그인 시Spring은 해당 사용자에 대해Session Cookie를 발급하고 이후 사용자가 수행하는 모든 API호출에 대하여 일일이 로그인 할 필요없이 해당Session Cookie를HTTP Request에 포함되어 함께 전송.
▶ 전송된Session Cookie는Spring Security에 의해 자동으로 인증이 수행됨.
HTTP Basic Authentication을Authentication Header에 포함하여HTTP Request전송
。ID:PW를Base64로 인코딩하여 앞에Basic을 명시한Base64코드(Basic Base64코드)을HTTP Request의Authorization Header로 포함하여 Authentication을 수행.
CSRF Token을 사용하여CSRF Protection을 해결하여POST,PUT,PATCH,DELETEMethod 사용하도록 설정.
。Spring Security를 Application에 선언할 경우 기본적으로CSRF Protection이 활성화.
▶GET이외HTTP Request Method를 통해 요청 시 발생하는CSRF Protection이 발생.
。JWT Token을 사용하는STATELESS의 경우에만CSRF Protection비활성화를 수행하여REST API에POST,PUT,PATCH,DELETEMethod를 수행하며 , 그외의 경우CSRF Token을 발급 및 활용하여POST,PUT,PATCH,DELETEMethod를 수행
▶ Server에서HTTP Request요청 직전마다 매번 Client에게 발급하는CSRF Token을 Client에서HTTP Request에Authentication Header로서 포함하여 전송
。Thymeleaf를 통해 Web Application을 build 시Spring Security는 자동으로 모든Form에CSRF Token을 생성하여 추가된다.
REST API사용 시CSRF Token생성 및 활용
。Server에서Client의 이전HTTP Request에서Session과 연결된CSRF Token을 발급 및Client는 매번Http Request를 전송할때마다 이전HTTP Request에서 발급된 고유한CSRF Token을X-CSRF-TOKENHeader로 포함하여 함께 전송하는 방식.
CSRF Token참조 및 전송
。@RestControllerClass에서Controller Method의 매개변수의HttpServletRequestinstance를 통해 해당REST API호출시의HTTP Request에서 보통form에서type=hidden으로 숨겨진<input>field의name="_csrf"를 참조하여CSRF-TOKEN을CsrfToken으로 캐스팅하여 반환하는Controller Method생성
import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SpringSecurityHttpServlet { @GetMapping("/csrf-token") public CsrfToken getCsrfToken(HttpServletRequest request) { return (CsrfToken) request.getAttribute("_csrf"); } }
。JWT또는HTTP Basic Authentication을Authentication header를 포함하여 해당API를 호출할 경우 ,Spring에서 발급한CSRF TOKEN이 포함된form에서CSRF TOKEN을 획득하여 반환.
。CSRF Token을 가져 온 후HTTP Request의 기존Authentication Header와 추가적으로X-CSRF-TOKENHeader를 통해CSRF Token값을 포함하여POST HTTP Request로 API호출 시 정상적으로HTTP Response가 반환됨을 관측가능
HttpServletRequest:
。Java Servlet API에서 제공하는 Interface.
▶Client가Web Application에 전송한HTTP Request정보를 포함.
。주로Spring MVC Controller에서URL Parameter,Header,Session정보를 가져올 때 사용됨.
▶Controller Class에서Controller Method의 매개변수의HttpServletRequestinstance를 통해 해당REST API호출시의HTTP Request의CSRF Token,Request Parameter,Header,Session정보를 획득.
HttpServletRequestMethod
HttpServletRequest객체.getParameter("Request Parameter명"):
。HTTP Request의Request Parameter값 가져오는 Method.
▶@RequestParam과 동일한 역할을 수행.
HttpServletRequest객체.getParameterValues("중복된 Request Parameter명"):
。HTTP Request의 동일 이름의 여러Request Parameter의 값을 배열로서 가져오는 Method.
HttpServletRequest객체.getHeaders("Header 종류 이름"):
。HTTP Request의 특정Header을 가져오는 Method.
ex )request.getHeader("Content-Type")
HttpServletRequest객체.getCookies():
。HTTP Request의Cookie를 가져오는 Method.
HttpServletRequest객체.getMethod():
。HTTP Request의HTTP Request Method의 정보를 가져오는 Method.
HttpServletRequest객체.getRequestURI():
。HTTP Request가 요청한URI을 가져오는 Method
HttpServletRequest객체.getContextPath():
。Web Application의Context경로를 반환하는 Method.
HttpServletRequest객체.getRemoteAddr():
。HTTP Request를 요청한 Client의 IP 주소를 반환하는 Method.
HttpServletRequest객체.getSession():
。HTTP Request의Session을 가져오는 Method.
SecurityFilterChain을 통해CSRF Protection을 비활성화 설정
。Spring은CsrfFilter가 기본적으로 활성화되어있으므로, 비활성화 설정.
▶ 이때 , 반드시Session Policy를Session을 사용하지않는STATELESS로 설정된 경우에만 비활성화.import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; @Configuration public class BasicAuthSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // 모든 HTTP Request에 대해 인증된 사용자만 접근가능하도록 접근권한을 부여. return http.authorizeHttpRequests((auth)->{ auth.anyRequest().authenticated(); }) // HTTP Basic Authentication 활성화 , formLogin() 사용 X .httpBasic(Customizer.withDefaults()) // Session Policy를 Session 비활성화로 설정. .sessionManagement((session)->{ session.sessionCreationPolicy(SessionCreationPolicy.STATELESS); }) // CSRF 비활성화 .csrf((csrf)->{ csrf.disable(); }) // HttpSecurity Instance를 생성 // → SecurityFilterChain instance로서 return. .build(); } }。
CsrfFilter를 비활성화 할 경우, 각HTTP Request에X-CSRF-TOKENheader를 통한CSRF Token을 추가로 포함할 필요없이JWT또는HTTP Basic Authentication을Authentication header에 포함하여POST HTTP Request를 전송해도 정상적으로HTTP Response가 반환.
。이때,formLogin()은 정의하지 않았으며Session이 비활성화 되었으므로, 이후Authentication header없이HTTP Request를 수행할 경우 자격증명 요구 시 로그인 & 로그아웃기능은 작동하지 않고,Basic Authetication Form을 요구하도록 설정됨
▶Session비활성화 된STATELESS의 경우CSRF Protection를 비활성화하고, Session 기반 인증을 수행하여Session을 통해 로그인 상태를 유지하는formLogin()을 사용할 필요가 없으므로 비활성화 설정.
Spring Security에서 사용자의 자격증명 저장하기
。기존에application.properties에서 정적으로 ID와 PW를 고정하여 정의한 개발용자격증명 삭제.
InMemoryUserDetailsManager를 활용하여In-Memory DB에 사용자 자격증명 생성 및 저장
InMemoryUserDetailsManager , UserDetails를 활용한 계정 생성
。InMemoryUserDetailsManager,UserDetails,User사용.
。InMemoryUserDetailsManager는 인메모리 저장방식이므로 , 테스트환경에서만 적합하고, 배포환경에서는 부적합.
▶ 차후JDBC / JPA를 활용한DB에 자격증명을 저장 및 참조하는 방식을 사용.import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; // 생성한 자격증명의 role 지정 용도의 Enum enum roles { ADMIN,USER } @Configuration public class SpringSecurityConfig { @Bean public InMemoryUserDetailsManager inMemoryUserDetailsManager() { // User Class의 User.roles() 설정용 Role은 Enum으로 설정하는게 좋다. roles role1 = roles.USER; roles role2 = roles.ADMIN; return new InMemoryUserDetailsManager( getUserDetails("wjdtn1","1234", role1), getUserDetails("wjdtn2","4567", role2) ); } private UserDetails getUserDetails(String username, String password,roles role){ return User.builder().passwordEncoder((input)-> passwordEncoderB().encode(input) ).username(username).password(password).roles(role.toString()).build(); } @Bean public PasswordEncoder passwordEncoderB() { return new BCryptPasswordEncoder(); } }。
BCryptPasswordEncoder를 instance를 생성하여PasswordEncodertype으로Spring Bean으로서 return하는 Method(passwordEncoderB())를 정의.
。passwordEncoderB()이 반환하는BCryptPasswordEncoderinstance의.encode(문자열)method를 이용해 입력된 문자열을 input하여BCrypt hashing을 통해 encode 된 문자열을 반환하는 람다식으로User.passwordEncoder()의 생성자로 설정.
。자격증명 인증 시BCryptPasswordEncoder의 알고리즘에 의해 사용자가 임의로 입력한 PW가BCrypt알고리즘으로 인코딩이 되고,User.builder().passwordEncoder()에 의해 기존에 저장된 "1234" PW가BCrypt알고리즘으로Encoding되어 서로 매칭하여 비교하는 방식임.
▶Base64와 다르게Decoding이 불가능하므로Encoding후 서로 비교해서Validation을 수행해야한다.
。 이후InMemoryUserDetailsManager를 통해 저장된 자격증명을 통해HTTP Basic Authentication으로Authentication Header에ID, PW를 입력 후HTTP Request에 포함하여API호출시 다음처럼 성공된 응답값을 가져온다.
▶ 개발용 자격증명이 아닌, 임의로 생성한 자격증명으로 접근권한을 획득.
Spring JDBC를 활용하여DB에 사용자 자격증명 생성 및 저장
。JdbcUserDetailsManager(DataSource객체)를 활용하여 사용자자격증명을DB에 저장.
▶InMemoryUserDetailsManager대체.
Spring JDBC,PostgreSQL DB관련 dependency 추가하기 PostgreSQL DB DataSource 연결
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
。build.gradle의dependencies { }에 정의 한 후Reload All Gradle Projects실행.implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation group: 'org.postgresql' , name: 'postgresql' , version: '42.2.23' // PostgreSQL Driver runtimeOnly 'org.postgresql:postgresql'
JDBC를 활용한application.properties에PostgreSQL DBDataSource설정 정의
。Spring Boot가 기본적으로 자동으로 연결할PostgreSQL DB의DataSource를 설정하기위해application.properties에 postgresql에 관한JDBC를 정의.
▶@Bean을 선언하여 직접DataSource instance를 생성하는 방법은 아래에서 소개.
。Spring Boot가 자동으로 연결할DataSource를 설정 시application.properties에 기존에 정의된h2-db설정을 삭제해야한다spring.datasource.url=jdbc:postgresql://localhost:5432/GeoDB spring.datasource.username=postgres spring.datasource.password=wjd747 spring.jpa.database-platform=org.hibernate.dialect.PostgresSQLDialect spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update
PostgreSQL DB에 다음 구문을 입력하여 Table 생성
。해당 SQL 구문은org/springframework/security/core/userdetails/jdbc/users.ddl의Spring Security의JDBC기반 사용자인증을 위해 자격증명을 저장하는 기본 테이블을 생성하는 구문.
。users: 사용자 ID, 사용자 PW 저장
。authorities: 역할(권한) 을 저장.
▶authorities는 자식으로서users의username를 외래키로서 참조
JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATIONCREATE TABLE users ( username VARCHAR(50) NOT NULL PRIMARY KEY, password VARCHAR(500) NOT NULL, enabled BOOLEAN NOT NULL ); CREATE TABLE authorities ( username VARCHAR(50) NOT NULL, authority VARCHAR(50) NOT NULL, CONSTRAINT fk_authorities_users FOREIGN KEY(username) REFERENCES users(username) ); CREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);
Spring JDBC의PostgreSQL DBDataSourceinstance 생성하기 DataSource Instance 생성
。PostgreSQL에서JdbcUserDetailsManager(DataSource객체)을 통한 DB에 사용자 자격증명을 추가할때의 용도로 활용할DataSourceinstance 정의하기.
▶ 사용자 자격증명 저장이 필요없는 경우, 설정안해도application.properties에서Spring Boot에 자동연결설정된DataSource를 활용하여 기본적으로CRUD를 수행 가능.
。@Configuration이 선언된Configuration Class에서DataSourceinstance를 반환하는@Bean Method를 생성.
▶postgresql DataSource를 정의하기위해application.properties에서 작성된JDBC내용과 유사.@Bean public DataSource pgdataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:postgresql://localhost:5432/GeoDB"); config.setUsername("postgres"); config.setPassword("wjd747"); return new HikariDataSource(config); }
DataSource:javax.sql.DataSource
。JDBC에서 특정DB의DB Connection을 관리하는 Interface.
▶Application이DB에 접근 시DataSource를 통해 Connection을 관리.
。최적화된DB Connection Pool을 제공하고,Spring Boot에서Auto-Configuration이 가능.
HikariCP:
。Spring Boot에서 기본적으로 활용하는 고성능JDBC Connection Pool을 관리하는DataSourceInterface를 구현한 Class.
▶new HikariConfig()를 통해DB Connection이 구현된DataSourceinstance 생성.
。JDBC Connection Pool을 통해DriverManagerDataSource와 달리DB Connection을 재사용.
Connection Pool:
。DB Connection을 매번 생성 시 성능 저하 발생하는 단점을 보완.
▶Connection Pool을 사용 시 일정 수의DB Connection을 사전에 생성 및 재사용 가능하여 불필요한DB Connection생성을 방지 가능.
DriverManagerDataSource:
。Spring에서JDBC Connection을 위한DataSourceInterface를 구현한 Class.
▶new DriverManagerDataSource()를 통해DB Connection이 구현된DataSourceinstance 생성.
。 매번 새로운DB Connection을 생성하며 연결을 재사용하지 않고 사용하므로HikariCP에 대체됨.
JdbcUserDetailsManager(DataSource객체)를 통해DB에 사용자 자격증명 생성 및 저장 JdbcUserDetailsManager
。기존의InMemoryUserDetailsManager()를 통해In-Memory DB에 사용자 자격증명을 추가한 코드에서JdbcUserDetailsManager(DataSource객체)로 Refactoring.
。PostgreSQL DB DataSourceinstance를 생성하는@Bean Method구현 및 해당 method를 통해 생성된 instance로JdbcUserDetailsManager(DataSource객체)의 instance를 생성하여UserDetailsinstance를 추가한 후Spring Bean으로 반환.
。PasswordEncoder의 해싱알고리즘 구현 Class ( =BCryptPasswordEncoder)의 생성자에Hashing작업량의 강도를 설정하는 숫자를 설정.
▶ 기본값 : 10import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.JdbcUserDetailsManager; import javax.sql.DataSource; // 생성한 자격증명의 role 지정 용도의 Enum enum roles { ADMIN,USER } @Configuration public class SpringSecurityConfig { @Bean public JdbcUserDetailsManager inMemoryUserDetailsManager() { // User Class의 User.roles() 설정용 Role은 Enum으로 설정하는게 좋다. roles role1 = roles.USER; roles role2 = roles.ADMIN; // PostgreSQL DB의 DataSource를 이용해 JdbcUserDetailsManager instance 생성. JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager(pgdataSource()); // 생성한 JdbcUserDetailsManager instance에 UserDetails Instance 추가 userDetailsManager.createUser(getUserDetails("wjdtn1","1234", role1)); userDetailsManager.createUser(getUserDetails("wjdtn2","4567", role2)); // JdbcUserDetailsManager Instance를 Spring Bean으로서 반환 return userDetailsManager; } private UserDetails getUserDetails(String username, String password,roles role){ // User.passwordEncoder(람다식)을 통해 input된 문자열을 PasswordEncoder 구현 Class를 통해 // `Hashing`으로 Encoding을 수행하여 Hash값을 반환. return User.builder().passwordEncoder((input)-> passwordEncoderB().encode(input) ).username(username).password(password).roles(role.toString()).build(); } @Bean public PasswordEncoder passwordEncoderB() { // PasswordEncoder 구현 Class의 생성자에 `Hashing` 작업량의 강도를 설정하는 숫자를 설정. // 10이 기본값. return new BCryptPasswordEncoder(10); } // PostgreSQL DB의 DataSource instance를 생성 후 반환. @Bean public DataSource pgdataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:postgresql://localhost:5432/GeoDB"); config.setUsername("postgres"); config.setPassword("wjd747"); return new HikariDataSource(config); } }。
UserDetailsinstance를 통해 사용자 자격증명 생성 시User.roles("역할")의 경우Enum으로 고정된 상수로서 참조하여 가져오는게 좋다.
。DB에서JDBC를 통한 사용자인증을 위한 기본 테이블로서 생성한usersDB Table에 사용자 자격증명이 성공적으로 추가된 것을 확인 가능.
。 Password는BCryptPasswordEncoder의 알고리즘에 의해Encoding.
▶Decoding은 불가능하고 추후 사용자가 로그인 시 입력하여Encoding된 비밀번호와 비교대상이 된다.
。authorities의username는users의username의 외래키.
。User.roles("역할명")를 통해 설정된 역할의 경우authorities table에서ROLE_역할명으로 저장됨.
User.builder().passwordEncoder((input)-> passwordEncoderB().encode(input) ).username(username).password(password) .roles(role.toString(),"DEVELOPER").build();▶ 사용자자격증명 당 복수 이상의 역할을 가질 수 있다.
。이후DB에 저장된 자격증명을 통해HTTP Basic Authentication을 통해HTTP Request를 전송 시 성공적으로HTTP Reponse를 반환하여 Application의 Resource에 접근이 가능함을 확인 가능.
Spring Security에서 비밀번호 저장 시 주의점
SHA-256같은Hash알고리즘은 더이상 안전하지 않음.
。bcrypt,scrypt등의Hash알고리즘 활용.
Adaptive One-Way function적용 및Work Factor를 최소 1초가 소요되도록 설정.
Adaptive One-Way function:bcrypt,scrypt,argon2...
。Hashing알고리즘은 단방향변환으로서 데이터에서Hash를 생성하지만, 역방향으로Hash에서 데이터를 생성할 수 없음을 의미.
▶Hash값은 원본으로 복원이 불가능하므로,DB에 저장 시 공격자가DB에 Access 하더라도Hash값만 참조가능하고 원본PW은 확인 불가능.
Work Factor:
。사용자가 입력한PW를Hashing후 기존에DB에 저장된Hash값과 비교를 수행하는 검증의 소요시간.
。PW저장의 경우Hashing이 지나치게 빠르게 수행될 경우 공격자가Hashing을 수행하면서brute force algorithm을 적용할 수 있음.
▶Hashing Algorithm이 너무 빠르게 작동하지 않도록Adaptive One-Way function을 활용하여Work Factor과정이 최소 1초가 소요되도록 설정
Brute Force Algorithm
。가능한 모든 경우의 수를 단순하게 전부 탐색함으로써 정답을 찾는 알고리즘 기법.
▶ 직관적이고 구현이 간단하지만, 경우의 수가 많아지면 연산량이 급격하게 증가하는 단점이 존재.
UserDetails:
。 사용자별 세부 정보를 관리 및 load하는Core Interface.
▶ 개별 사용자의 이름, PW, 권한 등의 세부정보를 해당UserDetails를 구현한 Class(UserClass )의 instance 내에 저장.
。@Configuration이 선언된Configuration Class에서@Bean Method를 통해UserClass를 활용하여 사용자자격증명을 포함한UserDetailsinstance를 생성 후JdbcUserDetailsManager(DataSource객체)에 포함하여Spring Bean으로서 반환함으로써DataSource를 통해 연결된 DB의 기본 Table에 전달하여 사용자 자격증명을 저장.
InMemoryUserDetailsManager
new InMemoryUserDetailsManager(UserDetails 객체1, UserDetails 객체2....)
。UserDetails를 구현한 Class로서Spring Security에서 제공하는Spring Boot Memory내부에서 사용자 정보를 관리.
▶ 해당 Class를Spring Bean으로 등록하여 Application에서 정보를 입력한 계정을In-Memory방식으로 저장이 가능.
▶H2-DB와 동일하게In-Memory방식으로서 Application의 Process가 종료될 경우 정보들이 없어지면서 초기화되므로JdbcUserDetailsManager(DataSource객체)를 사용한다.
。개별 사용자 계정정보를 담은UserDetailsinstance를 해당InMemoryUserDetailsManagerinstance에 저장.
▶InMemoryUserDetailsManager()의 생성자는 가변매개변수를 받을 수 있으므로, 여러개의UserDetailsinstance를 넣을수 있음.
JdbcUserDetailsManager(DataSource객체): 활용
var JdbcUserDetailsManager객체 = new JdbcUserDetailsManager(DataSource객체)
。Spring Security에서 사용자 자격증명을DB에서 조회 및 관리할 수 있도록 하는JDBC기반 사용자인증 관리 Class.
▶InMemoryUserDetailsManager의In-Memory DB에 저장되는 방식을DB에 저장할 수 있도록 대체.
。JdbcUserDetailsManager를 사용하기 위해서 특정 DB와 연결을 구현한DataSourceinstance를 필요로 함.
JdbcUserDetailsManagerMethod
JdbcUserDetailsManager객체.loadUserByUsername("username"):
。DB에서 입력된"username"에 해당하는 사용자 정보를 조회 후UserDetailsinstance로서 반환.
JdbcUserDetailsManager객체.createUser(UserDetails객체):
。DB에 새로운 사용자 자격증명 추가.
JdbcUserDetailsManager객체.updateUser():
。DB에서 사용자 정보를 조회
JdbcUserDetailsManager객체.deleteUser():
。DB에서 사용자 정보를 삭제
JdbcUserDetailsManager객체.changePassword("이전비밀번호", "새비밀번호"):
。DB에서 사용자 비밀번호 변경
User:
。UserDetails를 구현한 구현체 Class 객체.
▶UserDetails는 Interface이므로 직접 Instance를 생성할 수 없으므로UserClass의Builder로ID, PW, Role를 지정하여UserDetails의 Instance 생성.
UserClass Method
User.username("계정명"),User.password("비밀번호"),User.roles("역할명")
。UserDetails에ID, PW, Role정보를 설정.
。User.roles("역할명")의 역할의 경우Enum을 통해 고정된 상수 집합을 정의하여 설정하는게 좋다.
。User.roles("역할명1","역할명2",...)으로 복수의 역할명도 지정 가능.
User.build():
。UserDetailsInstance를 생성
User.withDefaultPasswordEncoder()
。여build()를 통해UserDetailsinstance 생성.
▶ID, PW, Role정의 후User.build()를 통해UserDetailsInstance를 생성하여 반환함으로써 자격증명을 생성 및 저장.
。현재는 deprecated된 코드로User.builder()를 통해BCryptPasswordEndcoder()**의 비밀번호 알고리즘으로 대체.
UserDetails userDetails = User.withDefaultPasswordEncoder() .username("wjdtn") .password("123456") .roles("USER","ADMIN") // 특정 작업을 수행하기 위한 역할 설정 .build();
User.builder():
。User.withDefaultPasswordEncoder()에서 비밀번호 알고리즘(.passwordEncoder(매개변수로 문자열을 입력받아 특정PasswordEncoder에 의해 인코딩하여 반환하는 람다식)을 추가 정의하여UserDetails의 instance를 생성하는 Custom Method.
▶User.withDefaultPasswordEncoder()를 대체.
User.passwordEncoder(매개변수로 문자열을 입력받아 특정PasswordEncoder에 의해 인코딩하여 반환하는 람다식):
。특정PasswordEncoder( ex.BCryptPasswordEncoder)를 통해 문자열을 인코딩하여 반환하는 람다식을 정의하여 해당UserDetailsinstance의PasswordEncoder를 정의.
。Spring Security 6.x부터는 람다기반 설정방식 권장UserDetails userDetails = User.builder() .passwordEncoder(input -> passwordEncoderB().encode(input)) .username("wjdtn") .password("123456") .roles("USER","ADMIN") // 특정 작업을 수행하기 위한 역할 설정 .build(); @Bean public PasswordEncoder passwordEncoderB(){ return new BCryptPasswordEncoder(); }。
BCryptPasswordEncoder를 instance를 생성하여PasswordEncodertype으로Spring Bean으로서 return하는 Method(passwordEncoderB())를 정의.
。passwordEncoderB()이 반환하는BCryptPasswordEncoderinstance의.encode(문자열)method를 이용해 입력된 문자열을 input하여BCrypt hashing을 통해 encode 된 문자열을 반환하는 람다식으로User.passwordEncoder()의 생성자로 설정.
。자격증명 인증 시BCryptPasswordEncoder의 알고리즘에 의해 사용자가 임의로 입력한 PW가BCrypt알고리즘으로 인코딩이 되고,User.builder().passwordEncoder()에 의해 기존에 저장된 "1234" PW가BCrypt알고리즘으로Encoding되어 서로 매칭하여 비교하는 방식임.
▶Base64와 다르게Decoding이 불가능하므로Encoding후 서로 비교해서Validation을 수행해야한다.
PasswordEncoder:
。입력된 비밀번호 문자열에Adaptive One Way Function(Hash알고리즘 :scrypt,bcrypt,argon2, ... )을 적용하여Hashing을 수행하는 기능을 상속하는Spring Security의Interface
▶PasswordEncoder를 구현한 Class는 문자열에 대해Hashing을 수행하며 수행된Hash값은DB에 저장됨.
。PasswordEncoder를 구현한 Class는Hashing을 통한 단방향변환을 수행하므로Encoding처럼 복원이 불가능.
▶Encoder명칭에 혼동 주의.
PasswordEncoder구현 Class :Hash Algorithm
。BCryptPasswordEncoder,Argon2PasswordEncoder,SCryptPasswordEncoder
▶ 구현 Class는User.builder()의User.passwordEncoder(람다식)을 활용하여PW Hashing을 수행.private UserDetails getUserDetails(String username, String password,roles role){ // User.passwordEncoder(람다식)을 통해 input된 문자열을 PasswordEncoder 구현 Class를 통해 // `Hashing`으로 Encoding을 수행하여 Hash값을 반환. return User.builder().passwordEncoder((input)-> passwordEncoderB().encode(input) ).username(username).password(password).roles(role.toString(),"DEVELOPER").build(); } @Bean public PasswordEncoder passwordEncoderB() { // PasswordEncoder 구현 Class의 생성자에 `Hashing` 작업량의 강도를 설정하는 숫자를 설정. return new BCryptPasswordEncoder(10); }▶
Hash알고리즘의 Instance를 생성하여PasswordEncoder의 구현체로서Spring Bean으로 반환하는@Bean Method를 활용.
BCryptPasswordEncoder(강도):
new BCryptPasswordEncoder(강도)
。Spring Security에서 제공하면서BCrypt hashing Algorithm을 사용하면서PasswordEncoder Interface를 구현한 Class.
▶BCrypt hashing function을 활용해서 비밀번호를 암호화 및 DB의 저장된 비밀번호와 일치 여부를 확인해주는 Method를 가지는 Class.
。생성자에Hashing작업량의 강도를 설정하는 숫자를 반영. ( 기본값 :10)
。PasswordEncoder구현Class.encode(),PasswordEncoder구현Class.matchers(),PasswordEncoder구현Class.upgradeEncoding()의 Method가 존재.