Spring Security_로그인 기능 구현/Spring Security Basic

로그인 기능_Spring Security 기본편 (9)

김쟈워니 2024. 10. 22. 11:32

로그인 기능_Spring Security 기본편(8)에서 배운 것 

세션 관련 설정

  • 세션 소멸 시간 설정:
    • application.properties 파일에 설정:
      • server.servlet.session.timeout=[원하는 초]
      • 분 단위 설정: server.servlet.session.timeout=[숫자]m
  • 다중 로그인 설정:
    • sessionManagement() 메서드를 이용하여 설정 가능.
    • maximumSessions(int) 메서드로 다중 로그인 허용 개수 설정.
    • 다중 로그인 허용 개수 초과 시 maxSessionPreventsLogin(boolean) 설정:
      • true: 새로운 로그인 차단
      • false: 기존 세션 중 하나 삭제
  • 세션 고정 공격 보호 설정:
    • 로그인 시 세션 정보 변경 방법 설정:
      • sessionFixation().none(): 세션 정보 변경 안 함
      • sessionFixation().newSession(): 새로운 세션 생성
      • sessionFixation().changeSessionId(): 동일한 세션에서 ID만 변경

CSRF 설정

CSRF란?

**CSRF(Cross-Site Request Forgery)**는 사용자가 의도하지 않은 서버 요청을 강제로 발생시키는 공격 방식으로, 예를 들어, 회원 정보 변경, 게시글 작성, 삭제 등을 사용자 모르게 실행할 수 있음.

 

개발 환경에서 CSRF 비활성화

개발 환경에서는 보안 테스트를 위해 csrf().disable() 설정을 통해 비활성화할 수 있음.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{

        http
                .csrf((auth) -> auth.disable());


        return http.build();
    }
}

배포 환경에서의 CSRF 설정과 구현

 

배포 환경에서는 csrf().disable() 설정을 제거하여 기본적으로 CSRF 보호가 활성화됨.

POST, PUT, DELETE 요청에 대해 CSRF 토큰 검증이 필요

 

1. 개발 환경에서 추가해놓았던 csrf.disable() 구문을 삭제

 

2. 템플릿에서 CSRF 토큰 추가 (JSP 기준)

POST 요청을 처리하는 모든 템플릿(JSP)에서 CSRF 토큰을 폼에 숨겨진 필드로 추가

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

이 코드를 통해 CSRF 토큰이 폼 제출 시 서버로 자동으로 전송되어 CSRF 검증을 통과할 수 있

 

3. AJAX 또는 Fetch 요청에서의 CSRF 처리(XMLHttpRequest)

 

 메타 태그를 통해 CSRF 정보를 전달받고, JavaScript로 요청할 때 해당 정보를 헤더에 포함 시킴(setRequestHeader(header,value) 부분)

더보기

XMLHttpRequest 개념 

브라우저 내에서 서버와 비동기적으로 데이터를 주고 받기 위해 사용하는 API

페이지 전체를 새로 고침하지 않고도 데이터를 요청하고 응답

과거에는 XML이 주요 데이터형식이였기 때문에, XMLHttpRequest라는 이름이 붙지만 현재는 JSON포맷이 대중화 되어 있음

 

HTML <head> 구획에 메타 태그를 추가하여 CSRF 정보를 제공

 <meta name="_csrf" content="${_csrf.token}"/>
 <meta name="_csrf_header" content="${_csrf.headerName}"/>

 

폼 내부에도 CSRF 토큰 필드를 추가

AJAX나 Fetch 요청에서는 별도로 메타 태그에서 토큰을 가져와 헤더에 추가하지만, 폼 전송 방식과 일관성을 유지하기 위해 CSRF 토큰 필드를 폼에 추가하는 것이 좋음

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

AJAX나 Fetch 요청에서도 폼 전송을 사용할 수 있는 경우를 대비해 폼에 CSRF 토큰 필드를 추가

JavaScript에서는 메타 태그로부터 CSRF 정보를 가져와 헤더에 수동으로 추가

 

AJAX 요청 예시 (jQuery 사용)

   <script>
        // CSRF 토큰 및 헤더 정보를 가져와서 AJAX 요청에 포함
        const token = document.querySelector('meta[name="_csrf"]').content;
        const header = document.querySelector('meta[name="_csrf_header"]').content;

        // AJAX 요청 (예: jQuery 사용)
        function sendPostRequest() {
            $.ajax({
                url: "/some-endpoint",
                type: "POST",
                contentType: "application/json",
                data: JSON.stringify({ key: "value" }),
                beforeSend: function(xhr) {
                    // CSRF 토큰을 헤더에 추가
                    xhr.setRequestHeader(header, token);
                },
                success: function(response) {
                    console.log("성공:", response);
                },
                error: function(error) {
                    console.error("오류:", error);
                }
            });
        }
    </script>

 

Fetch 요청 예시

 

 <script>
        // Meta 태그에서 CSRF 토큰 및 헤더 정보를 가져옴
        const csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content');
        const csrfHeader = document.querySelector('meta[name="_csrf_header"]').getAttribute('content');

        // Fetch 요청을 사용하여 POST 요청을 보내는 함수
        function sendPostRequest() {
            fetch('/some-endpoint', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    [csrfHeader]: csrfToken // CSRF 헤더와 토큰을 추가
                },
                body: JSON.stringify({ key: 'value' }) // 요청 본문
            })
            .then(response => {
                if (!response.ok) {
                    throw new Error('네트워크 응답에 문제가 있습니다.');
                }
                return response.json();
            })
            .then(data => {
                console.log('성공:', data);
            })
            .catch(error => {
                console.error('오류 발생:', error);
            });
        }
    </script>

 

 

  • POST 요청에서는 폼에 <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />을 항상 추가하여 CSRF 토큰이 자동으로 전송되도록 합니다.
  • AJAX 및 Fetch 요청에서는 JavaScript를 통해 메타 태그에서 CSRF 토큰을 가져와 헤더에 추가합니다. 이를 통해 서버는 요청의 정당성을 확인할 수 있습니다.

 

 

실습예시

더보기

실습 예시

1. login 은 post 요청

2. signup에서 회원중복 요청은 fetch 요청

 

loginPage.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Login Page</title>
</head>
<body>
    <form action="/loginProc" method="POST" name="loginForm">
        <input type="text" name="username" placeholder="ID" required>
        <input type="password" name="password" placeholder="Password" required>
        <!--csrf 활성화 이후 추가 구문  -->
        <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
        <button type="submit">Login</button>
    </form>
</body>
</html>

- csrf.token 을 숨겨진 상태로 추가 

 

signup.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>

<!-- CSRF 토큰 정보를 메타 태그로 포함 -->
<meta name="_csrf" content="${_csrf.token}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />


</head>
<body>
	<form action="/signupProc" method="POST" name="signupForm">
		<input type="text" name="username" placeholder="ID">
		<button type="button" onclick="checkUsername(event);">아이디 중복
			확인</button>
		<input type="password" name="password" placeholder="Password">
		<input type="hidden" name="${_csrf.parameterName}"
			value="${_csrf.token}" />
		<button type="submit">회원가입</button>
	</form>


	<script type="text/javascript">
const csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content');
const csrfHeader = document.querySelector('meta[name="_csrf_header"]').getAttribute('content');

console.log('CSRF Header:', csrfHeader); // 디버깅용 로그
console.log('CSRF Token:', csrfToken);

async function checkUsername(event) {
    event.preventDefault(); // 기본 폼 제출 방지

    const username = document.querySelector('input[name="username"]').value;

    try {
        const response = await fetch('/checkUsername', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                [csrfHeader]:csrfToken//csrf토큰 헤더에 추가
            },
            body: JSON.stringify({ username: username })
        });

        // 409 상태 코드일 때 에러 처리
        if (response.status === 409) {
            const message = await response.text(); // 서버 응답 메시지
            alert(message); // 이미 사용 중인 아이디 메시지 표시
        } else if (response.ok) {
            // 정상적인 응답일 때
            const message = await response.text();
            alert(message); // 사용 가능한 아이디 메시지 표시
        } else {
            // 그 외 다른 상태 코드 처리
            alert('알 수 없는 오류가 발생했습니다.');
        }
    } catch (error) {
        console.error('Error:', error);
        alert('서버 요청 중 오류가 발생했습니다.');
    }
}

	
	
</script>
</body>
</html>

- csrf 메타 태그 추가 및, fetch 요청시 csrf 토큰과 헤더를 함께 넣을 수 있게 넣고 전달

- DOMContentLoaded 고려하여 script 태그를 body 태그 아래에 배치.