어제에 이어 로그인/회원가입 페이지 구현 코드를 하나하나 뜯어보면서 어떻게 작동되는지 알아보자
오늘은 어제 필터단에서 이해 되지 않았던 부분들에 대해 생각하다가 내가 핵심을 놓치고 있었다는 걸 알았다.
JWT 토큰이 발행되는 시점은 로그인이 완료된 시점이다. 따라서 JWT 토큰이 유효하다면(시크릿 키와 일치, 만료기한, 적절한 형태) 비밀번호를 체크할 필요가 없다. 그 이유는 JWT 토큰이 발급된 사용자는 이미 "로그인 된" 사용자 이기때문.
개념적으로 알고 있었는데 그걸 잊고 있었다. 아무튼 앞으로 JWT 인증은 로그인(사용자 이름과 비밀번호)인증이 아니라 토큰의 유효성을 인증한다고 생각하면 된다.
어제에 이어 4번부터 다시 보자.
개선된 회원가입/로그인 방식의 개선방향을 간략하게 따져보자
1. DB에 비밀번호가 그대로 저장된다면 보안에 취약하다. DB에는 비밀번호가 평문이 아닌, 암호화된 형태로 존재해야 한다. 외부의 공격으로 부터 비밀번호를 지켜야 하고, 추가적으로 DB에 접근하는 관계자들도 사용자의 비밀번호를 알아선 안되기 때문!
2. 로그인 상태를 유지하는건 서버에 많은 부담을 준다. 따라서 로그인 상태를 쭉 유지 하는 것이 아닌, 한번의 인증을 거친 후 클라이언트에게 인증받은 사용자라는 토큰을 던지고, 클라이언트는 그 토큰을 다시 서버에게 던지면서 통신을 하게 된다. 이렇게 되면 요청시에만 클라이언트와 서버거 연결이 되기 때문에 서버에 부하가 적어진다.
3. 한단계를 더 나아가서 이제는 인증을 담당하는 필터를 하나 씌운다. 이제는 서버에서 토큰이 유효한 토큰인지 인증을 하는 것이 아닌, 필터단에서 토큰의 유효성 인증을 하고, 인증이 된 토큰을 갖는 클라이언트들만 서버로 연결이 될 수 있도록 필터를 추가한다!
4. 회원가입도 제한적으로 받으려고 한다. 예를들어 회원가입시 아이디는 반드시 적어줘야 하며, 이메일은 이메일의 형식(~@~.~)을 갖췄을 때만 회원가입을 승인해주는 것. 만약 요구된 형식대로 정보가 입력 되지 않는다면, 로그인 페이지로 다시 클라이언트를 보내버린다.
5. 마지막으로 관리자 권한이 있는 사용자만 들어갈 수 있는 페이지를 만든다. 이때 관리자가 아닌 일반 사용자가 접근을 할 때는 다른 페이지로 보내도록 한다.
이중 4번부터 다시 보자!
[Validation]
자바는 NULL이 들어올 때 예외가 쉽게 발생한다. 내 스프링 첫 과제도 NULL 때문에 데이터가 제대로 넘어가지 않아 작동을 하지 않았다.ㅠㅠ... 만약 클라이언트로 부터 날라온 요청에 NULL값이 있다고 가정해보자. 근데 만약 그 NULL값이 사용자 이름이라면? 아니면 비밀번호라면? 그 사용자는 로그인 할 수 있는 정보의 핵심이 빈 채로 회원가입이 되기 때문에 요청이 들어오기 온 순간부터 미리 막아줘야 한다. 그때 사용하는 것이 Validation이고, 스프링에서는 Bean Validation을 이용하여 정보에 제한을 둔다. Entity에서 컬럼에 속성을 추가하는 어노테이션을 달아줬던 것 처럼, Bean Validation도 RequestDto 클래스에 어노테이션을 이용하여 필드값에 제한을 둘 수 있다.
@NotBlank
private String name;
@Email
private String email;
@Positive(message = "양수만 가능합니다.")
private int price;
@Negative(message = "음수만 가능합니다.")
private int discount;
@Size(min=2, max=10)
private String link;
@Max(10)
private int max;
@Min(2)
private int min;
이런식으로 각 형태에 맞게끔! 각 어노테이션은 직관적이니 설명은 하지 않겠다.
Validation으로 제한된 값을 받는 클래스를 매개변수로 호출할 때는 @Validation 어노테이션을 선언해준다.
public ProductRequestDto testValid(@RequestBody @Valid ProductRequestDto requestDto) {
return requestDto;
이렇게 제한적으로 요청을 받을 수 있었다. 이런 형식을 지키지 않는 데이터가 요청이 들어오면 그때는 예외처리를 해줘야 한다. 예외가 발생하면 BindingResult 클래스에 담긴다. BindingResult 객체는 List 형식으로 담을 수 있다. 그렇게 담인 예외들의 로그를 찍어준다거나, 다른 페이지로 넘길 수 있다.
public String signup(@Valid SignupRequestDto requestDto, BindingResult bindingResult) {
// Validation 예외처리
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
if(fieldErrors.size() > 0) {
for (FieldError fieldError : bindingResult.getFieldErrors()) {
log.error(fieldError.getField() + " 필드 : " + fieldError.getDefaultMessage());
}
return "redirect:/api/user/signup";
}
userService.signup(requestDto);
return "redirect:/api/user/login-page";
}
회원가입 시 예외가 발생하면? 회원가입 페이지로, 예외가 발생하지 않으면? 로그인 페이지로 돌아가게 만들었다.
[Spring Security]
스프링에서 보안(인증과 인가)을 담당하는 하위 프레임워크이다. 필터단에서 인증과 인가를 허가해 DispatcherServlet에 가기 전에 한번 걸러주는 역할을 한다. 우리는 여기서 인가에 대해 다룰 예정.
이부분은 아직 이해가 잘 되지 않아 내일 다시 공부해야한다!