Spring Security
보안을 쉽게 구현하고 관리할 수 있도록 도와주는 프레임워크 입니다
주요 기능으로는 인증(Authentication), 권한 부여(Authorization), CORS와 CSRF등과 같은 공격으로 부터
어플리케이션을 보호합니다.
위에 대한 설명을 요약하자면 대략 다음과 같습니다
- Authentication(인증)
- 시스템에 접근하려는 사용자의 신원을 확인하는 프로세스
- 일반적으로 ID/PW나 토큰 기반으로 이루어짐
- 인증 성공시 시스템에 접근할 수 있는 권한을 부여받음
- Authorization(인가)
- 인증된 사용자에게 특정 리소스나 작업에 대한 접근 권한을 부여하는 프로세스
- 예를들어 관리자는 전체 접근권한, 일반 사용자는 제한된 접근권한을 부여할 수 있음
- CORS(Cross Origin Resource Sharing)
- 다른 출처(도메인, 프로토콜, 포트)의 리소스에 접근을 허용하는 매커니즘
- 일반적으로 브라우저는 동일 출처 정책(Same Origin Policy)에 의해 다른 출처의 리소스 접근을 제한
- CSRF(Cross Site Request Forgery)
- 사용자가 의도하지 않은 요청을 웹 애플리케이션에 전송하도록 속이는 공격
- EX) 인증된사용자 → 악의적 링크 클릭 → 공격자가 설정한 기능 수행
- 방어 방법
- CSRF 토큰 사용 모든 요청마다 사용자의 세션과 연결된 고유한 토큰을 생성하여 유효성 검증
- SameSite 쿠키 속성 사용 다른 도메인에서의 요청 제한
- Referer 검증 요청이 발생한 페이지의 Referer 헤더를 확인하여 요청 출처 확인
- 사용자 입력 검증
추가로 클라이언트가 HTTP 요청을 하면 REST 컨트롤러에 도달하기전에 다양한 필터를 거치게 됩니다
다음은 spring security 적용시 어떻게 변하는지 간단하게 테스트 해보겠습니다
개발 환경
- Spring Boot 3.2.4
- spring-boot-starter-security
- spring-boot-starter-oauth2-resource-server
build.gradle
...
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'
// runtimeOnly 'com.microsoft.sqlserver:mssql-jdbc'
// runtimeOnly 'com.mysql:mysql-connector-j'
// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.1.0:jakarta'
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
}
...
config/security/SecurityConfig.java
@Configuration
public class SecurityConfig {
@Bean
UserDetailsService userDetailsService() {
var userDetails = User.builder()
.username("admin")
.password("{noop}P@ssw0rd")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
}
위 코드에서 {noop}는 패스워드를 암호화 하지 않고 사용하겠다는 뜻입니다
패스워드를 암호화 할 경우 다음 코드처럼 사용하시면 됩니다
@Configuration
public class SecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
UserDetailsService userDetailsService() {
var userDetails = User.builder()
.username("admin")
.password("P@ssw0rd")
.passwordEncoder(str -> passwordEncoder().encode(str))
.build();
return new InMemoryUserDetailsManager(userDetails);
}
}
테스트
이제 프로젝트를 실행시키고 http://localhost 로 접속하게 되면
로그인하라는 창이 나옵니다
여기에 admin / P@ssw0rd 를 입력하면 원래 가려고 했던 주소로 갈 수 있습니다.
API 테스트시 GET 요청을 제외한 문제 발생
GET 요청은 제대로 동작하는데 POST, PUT, DELETE 요청은 아래 처럼 403 에러가 발생합니다
Postman을 사용한 403 CSRF 해결방법
원인은 CSRF 때문에 그렇습니다
이걸 해결하려면 postman 같은 api를 테스트 할 수 있는 도구를 사용해야합니다
아래 링크에서 다운로드가 가능합니다
Download Postman | Get Started for Free
실행하면 다음과 같은 화면이 나올겁니다
저는 로그인을 하지 않고 사용할것이기 때문에 lightweight API client 를 클릭했습니다
그리고 http://localhost/login URL로 GET 요청을 전송하면 _csrf 값이 input 태그안에 포함되어 있을겁니다
_csrf의 value 값을 복사해줍니다
그다음 새로운 요청을 만들어서 Authorization, X-CSRF-TOKEN, Body 값을 추가해줍니다
Authorization 탭에서 Basic Auth를 선택하고 username과 password를 입력해주세요
Headers 탭에서 X-CSRF-TOKEN 키에 대한 값으로 처음에 복사했던 _csrf 값을 넣어주세요
Body 탭에서 위 그림처럼 넣어주면 됩니다
저는 postman에서 지원하는 random 값을 넣어주었습니다
하지만...
YWRtaW46UEBzc3cwcmQ=값을 확인할 수 있는데 이 값은 base64 인코딩 값이여서 보안에 취약합니다.
아래 사이트에서 Decode가 가능합니다
Base64 Decode and Encode - Online
다음 글에서는 이 문제들을 해결해보겠습니다
참조
'JAVA > SPRING' 카테고리의 다른 글
Spring Boot 3 Tutorial - 5 Security(3) JWT (0) | 2024.04.23 |
---|---|
Spring Boot 3 Tutorial - 5 Security(2) (0) | 2024.04.16 |
Spring Boot 3 Tutorial - 4 QueryDsl (0) | 2024.04.08 |
Spring Boot 3 Tutorial - 3 DBMS switch (0) | 2024.04.04 |
Spring Boot 3 Tutorial - 2 CRUD (0) | 2024.04.04 |