[스프링시큐리티] 커스텀 Login 화면과 csrf
스프링 시큐리티에서는 기본적으로 Login 화면을 제공한다.
이 로그인 화면을 커스텀 하기 위해서는
1. 스프링 시큐리티 Config 설정
-> Bean으로 등록했던 스프링 시큐리티 설정값에서 로그인 페이지와 처리 url을 표기해준다
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//인증 방식
http
.formLogin() //폼 로그인 방식
.loginPage("/login") // 사용자 정의 로그인 페이지
.loginProcessingUrl("/loginProc") // 로그인 프로세스 url
.defaultSuccessUrl("/", false) // 로그인 성공 후 원래 페이지로 리다이렉트
.failureUrl("/login") //로그인 실패 시 이동할 경로
.usernameParameter("userNickName") // 아이디 파라미터 설정
.passwordParameter("password") //패스워드 파라미터 설정
.permitAll();
2. html 페이지 작성, 파라미터와 처리 url을 동일하게 맞춰준다.
<form action="/loginProc" class="form-signin" method="post">
<div class="form-group">
<input type="text" class="form-control form-control-user"
id="userNickName" name="userNickName"
placeholder="userNickName">
</div>
<div class="form-group">
<input type="password" class="form-control form-control-user"
id="password" name ="password" placeholder="Password">
</div>
<div class="form-group">
<div class="custom-control custom-checkbox small">
<input type="checkbox" class="custom-control-input" id="customCheck">
<label class="custom-control-label" for="customCheck">Remember
Me</label>
</div>
</div>
<button type="submit" class="btn btn-primary btn-user btn-block">
Login
</button>
<hr>
<a href="index.html" class="btn btn-google btn-user btn-block">
<i class="fab fa-google fa-fw"></i> Login with Google
</a>
<a href="index.html" class="btn btn-facebook btn-user btn-block">
<i class="fab fa-facebook-f fa-fw"></i> Login with Facebook
</a>
</form>
3. 컨트롤러를 만들어준다.
-> Login Page이동을 위한 컨트롤러만 만들어주면 된다.
Login처리를 위한 컨트롤러는 별도로 작성하지 않아도 1에서 등록한 loginProcessinUrl과 2에서 작성한 action 태그 값만 같으면 스프링 시큐리티가 알아서 처리해준다.
@Controller
@Slf4j
public class LoginController {
@GetMapping("/login")
public String login(){
return "member/login/login";
}
4. 근데 이렇게 하면 로그인 처리가 정상적으로 작동하질 않는다.
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' is not supported
라고 오류가 뜨는데 메소드 URL이나 전달 방식이 오류가 있는게 아니라
스프링 시큐리티에서 지원하는 CSRF설정에서 막혀서 그렇다.
5. CSRF 는 웹 서비스 취약점 중 하나로
"웹 서비스에 로그인한 사용자가 실제로 의도하지 않았음에도 공격자가 준비한 악성 웹 페이지로 인해 웹 서비스에 위조된 요청을 보내게 되는 것입니다"
라고 한다. 스프링 시큐리티에서는 이 CSRF 공격을 막기 위해 자동으로 토큰을 발급하고 검증하는데, 위 요청에서는 CSRF 토큰을 함께 전달하지 못하기 때문에 요청이 막히게 된다.
6. 해결방법
(1) csrf 미사용( 비추 )
-> 스프링 시큐리티 설정에서 csrf 설정을 disbale 해준다. 간단하지만 보안상 좋지 않다.
http.csrf().disable();
(2) 타임리프 사용
-> 타임리프는 스프링 시큐리티와 함께 사용할 경우 <form> 태그를 사용할 때, 자동으로 csrf 파라미터가 폼에 추가되어 토큰이 전송된다. 그렇기 때문에 타임리프를 사용할 경우 csrf 설정으로 막혀있는지 조차 모를 수 있음.
(3) 메타데이터에서 직접 받아서 함께 전달
-> 메타데이터에서 직접 토큰 값을 받아 form태그에 추가하여 전달한다. jQuery를 사용하면 조금 더 간편
<meta name="_csrf_parameter" content="${_csrf.parameterName}">
<meta name="_csrf" content="${_csrf.token}">
function getMetaContent(name) {
const meta = document.querySelector(`meta[name="${name}"]`);
return meta ? meta.content : '';
}
const csrfParameterName = getMetaContent('_csrf_parameter');
const csrfToken = getMetaContent('_csrf');
const form = document.querySelector('.form-signin');
const csrfInputElement = document.createElement('input');
csrfInputElement.type = 'hidden';
csrfInputElement.name = csrfParameterName;
csrfInputElement.value = csrfToken;
form.appendChild(csrfInputElement);
-> 비동기통신을 활용한 방법
<meta name="_csrf_parameter" content="${_csrf.parameterName}">
<meta name="_csrf" content="${_csrf.token}">
$(document).ready(function() {
var csrfParameterName = $('meta[name="_csrf_parameter"]').attr('content');
var csrfToken = $('meta[name="_csrf"]').attr('content');
// AJAX 호출 설정
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader(csrfParameterName, csrfToken);
}
});
// AJAX 호출
$.ajax({
url: '/example-endpoint',
type: 'POST',
data: {
// 데이터
},
success: function(response) {
// 성공 시 처리
},
error: function(xhr, status, error) {
// 실패 시 처리
}
});
});