로그인시에 사용할 username 과 password 를 담는 객체이다. Bean Validation 으로 검증을 수행한다. 로그인시에 잘못된 정보를 입력해도 DB에서 매핑되지 않아 로그인이 수행되지 않을 것이다. 그래도 null인 username이 존재하는 등 만약의 경우를 대비해 검증을 수행하도록 하였다.
요청 파라미터의 null 검증 Bean Validation 의 어노테이션은 세가지가 있다.
NotNull - Null 금지, "" 허용, " " 허용
NotEmpty - Null 금지, "" 금지, " " 허용
NotBlank - Null 금지, "" 금지, " " 금지
즉 NotBlank 가 가장 엄격한 null 검증 어노테이션이며 필요에 따라 사용하면 된다.
username 과 password 필드를 SignupDto 도 갖고 있는데 이를 재사용할 수는 없을까? 물론 가능하다. 하지만 로그인과 회원가입의 검증 로직이 다를 수 있고, SignupDto 의 경우 로그인에 필요없는 email 등의 필드도 갖고 있기 때문에 signinDto를 별도로 구현해 사용했다.
/auth/signin 으로 GET 요청이 들어오면 signin.html 을 반환해주도록 하였다. 이 때 th:object 를 사용하기 위해 빈 SigninDto 객체를 담아서 전달해주었다.
로그인을 생각해보자. DB 에서 username 으로 Select 를 수행해야한다. 그런데 JPA 의 기본제공 메서드는 PK 관련 Select 만 제공하고 다른 컬럼 기준 검색은 메서드화해서 제공하지 않는다. 즉 PK는 id 이기에 findById 만 기본제공한다. 그래서 findByUsername 을 별도로 정의하였다.
JPA 의 쿼리 메서드 기능은 메서드를 선언하면 메서드 이름을 분석하여 JPA NamedQuery 를 호출해서 쿼리를 자동으로 생성해준다. findByUsername(String temp) 이라는 메서드 이름을 정의하면 Select * from User where username = temp 라는 쿼리를 수행하는 메서드를 자동으로 만들어준다는 것이다.
이제 AuthService에서 로그인 메서드를 생성하자. UserRepository 에 쿼리메서드로 생성한 findByUsername 으로 username 바탕 검색을 수행한다. 검색으로 찾은 User 객체에서 password 가 일치하면 해당 객체를 반환하고 없다면 null을 반환하도록 하였다.
회원가입과 달리 파라미터로 SigninDto 를 받는다. 그 이유는 UserReposiotory 에서 Jpa 가 생성해주는 메서드가 아닌 쿼리 메서드로 생성한 메서드를 쓰기 때문이다. 여기서 파라미터가 도메인인 User 이 아닌 문자열인 String 을 받기 때문에 Service 에서도 SigninDto를 파라미터로 받을 수 있는 것이다. 컨트롤러에서도 toEntity 과정이 필요없게 된다.
글로벌 Validator 가 Bean Validation 어노테이션을 보고 검증을 수행한다. BindingResult 에 에러가 존재한다면 로그인창을 렌더링하도록 하였다. 이 때 PRG 패턴으로 redirect 후 재요청하지 않고 바로 뷰를 준 이유는, redriect 를 거쳐서 주면 요청이 다시 이루어져 BindingResult 가 사라지기 때문이다. BindingResult 를 활용해 뷰를 그리기 위해 바로 뷰를 렌더링하였다.
검증이 성공하면 signinDto로 로그인을 시도한다. 로그인 메서드는 수행결과 성공하면 User 객체를, 실패하면 null 을 반환한다. null 반환 시 아이디나 비밀번호가 맞지 않는 것으로, reject 메서드로 ObjectError 를 BindingResult 에 추가시킨다.
로그인이 성공하면 session.getSession 으로 세션을 생성한다. Http 는 Stateless 프로토콜로서 상태를 유지하려면 세션을 생성해야한다. 해당 요청의 세션이 있다면 그 세션을. 없다면 클라이언트에게 JESSIONID 를 UUID로 하는 쿠키를 발급하고 세션을 생성한다. 그 후 setAttribute 로 세션에 loginUser 를 저장한다.
사용자가 원래 이용하던 페이지로 redirect 시키면 사용자가 사용하기 편리할 것이다. 후에 인터셉터에서 로그인 필요한 페이지 접근시, 이를 요청 파라미터에 redirectUrl 로 담아 넘겨줄 것이다. 그러면 사용자는 원래 이용하던 페이지로 redirect 된다.
타임리프가 기본으로 제공하는 객체이다. hasErrors, hasGlobalErrors 등 bindingResult 관련 메서드들을 제공한다. th:each 로 모든 글로벌 에러 메시지들을 순회하며 th:text로 출력하도록 하였다. 로그인 실패시 해당 메시지가 출력된다.
로그인 실패시 BindingResult 에 ObjectError 추가 후 렌더링되며, 글로벌 에러메시지가 출력된다.