스프링 부트 환경에서 Spring Security의 CSRF Token 해제
1. 회원가입 페이지 구현
로그인 페이지에서 가입하기 버튼 클릭 시 회원가입 페이지로 이동한다.
AuthController.java
package com.GStagram.domain.auth;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class AuthController {
@GetMapping("/auth/login")
public String loginPage() {
return "auth/login";
}
@GetMapping("/auth/signup")
public String signupPage() {
return "auth/signup";
}
}
signup.mustache
{{>layouts/header}}
<div class="container">
<main class="loginMain">
<!--회원가입섹션-->
<section class="login">
<article class="login__form__container">
<!--회원가입 폼-->
<div class="login__form">
<!--로고-->
<h1><img src="/images/logo.png" alt=""></h1>
<!--로고end-->
<!--회원가입 인풋-->
<form class="login__input" action="/auth/signup" method="post">
<input type="text" name="username" placeholder="유저네임" required="required" maxlength="30"/>
<input type="password" name="password" placeholder="패스워드" required="required" />
<input type="email" name="email" placeholder="이메일" required="required" />
<input type="text" name="name" placeholder="이름" required="required" />
<button>가입</button>
</form>
<!--회원가입 인풋end-->
</div>
<!--회원가입 폼end-->
<!--계정이 있으신가요?-->
<div class="login__register">
<span>계정이 있으신가요?</span>
<a href="/auth/login">로그인</a>
</div>
<!--계정이 있으신가요?end-->
</article>
</section>
</main>
</div>
{{>layouts/footer}}
2. 회원가입 기능 구현
Http 메서드를 POST 방식으로 할 것이기 때문에 form 태그에 액션이 회원가입 페이지로 이동하는 경로와 같다
즉, 회원가입 페이지로 이동하는 /auth/signup 요청은 GET 요청이고
회원가입 버튼을 누르고 실행할 /auth/signup 액션은 POST 요청이다.
AuthController.java
package com.GStagram.domain.auth;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class AuthController {
@GetMapping("/auth/login")
public String loginPage() {
return "auth/login";
}
@GetMapping("/auth/signup")
public String signupPage() {
return "auth/signup";
}
@PostMapping("/auth/signup")
public String signup() {
System.out.println("signup이 실행됨");
return "auth/login"; // 회원가입이 완료되면 로그인 페이지로 이동한다.
}
}
이 후 회원가입을 테스트 해보면 로그인 페이지로 경로이동은 되지만,
콘솔창에 "signup이 실행됨"이 출력되지 않는다.
(원래는 403에러로 인해 에러 페이지가 출력되야 하지만, 시큐리티에서 에러페이지 허용을 안했기 때문에 로그인 페이지로 이동된다.)
그 이유는 CSRF토큰 때문이다.
3. CSRF 토큰
클라이언트가 회원가입 페이지에서 데이터를 입력하여 웹서버에 전송할 때,
웹서버를 보호하고 있는 시큐리티가 입구에서 시큐리티 CSRF토큰 검사를 실시하게 된다.
CSRF토큰 검사는 클라이언트가 웹서버가 응답해준 회원가입창을 통해서
정상적인 경로로 회원가입을 진행하였는지를 확인하는 것이다.
CSRF토큰에는 정상적인 경로로 인증을 받았다는 정보가 담겨있다.
이후 웹서버가 클라이언트에게 응답해 줄 때, signup.jsp 파일안의 form태그내부
input태그에 어떤 난수를 심어서 같이 보내준다.
<!--회원가입 인풋-->
<form class="login__input" action="/auth/signup" method="post">
<input type="text" name="username" placeholder="유저네임" required="required" maxlength="30" csrf="???"/>
<input type="password" name="password" placeholder="패스워드" required="required" csrf="???" />
<input type="email" name="email" placeholder="이메일" required="required" csrf="???"/>
<input type="text" name="name" placeholder="이름" required="required" csrf="???"/>
<button type="submit">가입</button>
</form>
클라이언트가 응답받을땐 우리가 따로 작성하진 않았어도 CSRF가 붙어서 전해지는 것이다.
이 상태에서 인증이 필요한 요청을 할 때, 시큐리티가 해당 요청에서 CSRF를 보고 판단하게 된다.
가령 우리가 GET요청이 아닌 요청을 할 때 사용했던
postman같은 프로그램으로 회원가입, 로그인을 요청했을 때에는
웹서버가 제공한 정상적인 경로로 접근한 것이 아니므로 CSRF 토큰 검사에서 fail이 뜨게 된다.
CSRF에 대한 좀더 자세한 내용은 아래 블로그에 정리가 잘 되어있다.
4. CSRF 토큰 해제
보안을 위해서라면 CSRF 토큰 검사가 있어야 하지만, 개인 프로젝트이기 때문에 여기서는 CSRF토큰을 비활성화한다.
(추후 필요하면 활성화 한 후, 코드 리팩토링)
SecurityConfig.java로 돌아가서
http.csrf().disable();을 추가한다.
package com.GStagram.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity // 해당 파일로 시큐리티를 활성화
@Configuration // IOC
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// super 삭제 - 기존 시큐리티가 가지고 있는 기능이 다 비활성화됨.
// 인증이 되지 않은 모든 사용자는 로그인 페이지로 이동
http.csrf().disable();
http
.authorizeRequests() // URL별 관리
.antMatchers("/auth/**", "/css/**","/images/**","/js/**","/h2-console/**").permitAll() //유저 관련 페이지만 접근 가능
.anyRequest().authenticated() // 설정된 값들 이외 나머지 URL은 인증된 사용자만 접근 가능
.and()
.formLogin()
.loginPage("/auth/login") // GET
.defaultSuccessUrl("/");
}
}
이후 다시하면 회원가입 테스트를 진행하면
정상적으로 작동하는 것을 볼 수 있다.