스프링과 JPA 기반 웹 애플리케이션 개발 #23 로그인 로그아웃

Jake Seo·2021년 5월 30일
0

스프링과 JPA 기반 웹 애플리케이션 개발 #23 로그인 로그아웃

해당 내용은 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발의 강의 내용을 바탕으로 작성된 내용입니다.

강의를 학습하며 요약한 내용을 출처를 표기하고 블로깅 또는 문서로 공개하는 것을 허용합니다 라는 원칙 하에 요약 내용을 공개합니다. 출처는 위에 언급되어있듯, 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발입니다.

제가 학습한 소스코드는 https://github.com/n00nietzsche/jakestudy_webapp 에 지속적으로 업로드 됩니다. 매 커밋 메세지에 강의의 어디 부분까지 진행됐는지 기록해놓겠습니다.


로그인/로그아웃

  • 스프링 시큐리티 로그인/로그아웃 설정
        http.formLogin() 
            .loginPage("/login").permitAll(); 
        http.logout() 
            .logoutSuccessUrl("/"); 
  • 스프링 시큐리티 로그인 기본값
    • username
    • password
    • POST /login
    • POST /logout

로그인 뷰 (login.html) 구성

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
  <title>로그인</title>
  <th:block th:replace="fragments :: headLibraryInjection"></th:block>
</head>

<body class="bg-light">
<th:block th:replace="fragments :: main-nav"></th:block>
<div class="container">
  <div class="py-5 text-center">
    <p class="lead">스터디올래</p>
    <h2>로그인</h2>
  </div>
  <div class="row justify-content-center">
    <div th:if="${param.error}" class="alert alert-danger" role="alert">
      <p>이메일(또는 닉네임)과 패스워드가 정확하지 않습니다.</p>
      <p>또는 확인되지 않은 이메일을 사용했습니다. 이메일을 확인해주세요.</p>
      <p>
        확인 후 다시 입력하거나, <a href="#" th:href="@{/find-password}">패스워드 찾기</a>를 이용하세요
      </p>
    </div>

    <!-- POST /login 으로 가는 값은 스프링 시큐리티가 알아서 처리해주기 때문에, 우리가 따로 로그인 핸들러 로직을 만들 필요가 없다. -->
    <!-- 단 스프링 시큐리티가 사용자의 정보를 디비에서 끌고올 수 있어야 하기 때문에, UserDetailsService 인터페이스는 구현해야 한다. -->
    <form class="needs-validation col-sm-6" action="#" th:action="@{/login}" method="post" novalidate>
      <div class="form-group">
        <label for="username">이메일 또는 닉네임</label>
        <input id="username" type="text" name="username" class="form-control"
                placeholder="your@email.com" aria-describedby="emailHelp" required />
        <small id="emailHelp" class="form-text text-muted">
          가입할 때 사용한 이메일 또는 닉네임을 입력하세요.
        </small>
        <small class="invalid-feedback">이메일을 입력하세요.</small>
      </div>

      <div class="form-group">
        <label for="password">패스워드</label>
        <input id="password" type="password" name="password" class="form-control"
               aria-describedby="passwordHelp" required />
        <small id="passwordHelp" class="form-text text-muted">
          패스워드가 기억나지 않는다면, <a href="#" th:href="@{/email-login}">패스워드 없이 로그인하기</a>
        </small>
        <small class="invalid-feedback">패스워드를 입력하세요.</small>
      </div>

      <div class="form-group">
        <button class="btn btn-success btn-block" type="submit"
                aria-describedby="submitHelp">로그인</button>
        <small class="form-text text-muted">스터디올래에 처음 오셨다면,
          <a href="#" th:href="@{/sign-up}">계정을 먼저 만드세요.</a>
        </small>
      </div>

    </form>
  </div>

  <th:block th:replace="fragments :: footer"></th:block>
</div>

<script th:replace="fragments :: form-validation"></script>
</body>
</html>
  • 이전에 이용했던 폼 검증을 그대로 재활용했다.
  • idpassword에 대한 inputname 값은 스프링 시큐리티를 활용하려면 usernamepassword로 설정하는 것이 좋다.
  • usernamepassword 파라미터를 POST /login으로 보내면, 스프링 시큐리티가 UserDetailsService 인터페이스를 구현한 클래스를 이용하여 로그인 처리를 자동으로 해준다.
  • 로그아웃 버튼은 잘 보면 폼 서브밋 버튼이다. POST /logout 요청을 하면 스프링 시큐리티에 작성된 로그아웃 핸들러가 자동으로 로그아웃된다.

MainController 작성

GET /login 매핑 작성

    @GetMapping("/login")
    public String login() {
        return "login";
    }

메인 컨트롤러단에서는 따로 뭘 해줄 필요 없이 그냥 로그인 뷰만 잘 넘겨주면 된다.

SecurityConfig 작성

        http.formLogin() // 이것만 해도 폼 로그인 기능은 활성화됨
            .loginPage("/login").permitAll(); // 이렇게 하면 커스텀한 로그인 페이지를 설정할 수 있음

        http.logout() // 이것만 해도 로그아웃 기능은 됨
            .logoutSuccessUrl("/"); // 로그아웃 했을 때 어느 페이지로 갈지에 대한 설정임

위와 같이 설정하면, 커스텀 로그인 페이지인 /login으로 페이지 연결이 가능하고, 로그아웃 시에 방문할 페이지도 설정할 수 있다.

http.formLogin() // 이것만 해도 폼 로그인 기능은 활성화됨
            .usernameParameter("email")
            .passwordParameter("pass")
            .loginPage("/login").permitAll(); // 이렇게 하면 커스텀한 로그인 페이지를 설정할 수 있음

위와 같이 파라미터를 커스터마이징할 수도 있다.

AccountService 작성

UserDetailsService 인터페이스 상속 및 메소드 작성

public class AccountService implements UserDetailsService {
...

    @Override
    public UserDetails loadUserByUsername(String emailOrNickname) throws UsernameNotFoundException {
        Account account = accountRepository.findByEmail(emailOrNickname);
        if(account == null) {
            account = accountRepository.findByNickname(emailOrNickname);
        }

        if(account == null) {
            throw new UsernameNotFoundException(emailOrNickname);
        }

        // Principal 을 만들어주면 된다.
        // UserDetailsService 를 구현한 객체가 하나만 있으면, SpringSecurity 에 더이상 설정해줄 것이 없다.
        // 자동으로 UserDetailsService 를 상속한 빈을 가져다 쓰게 된다.
        return new UserAccount(account);
    }

위와 같이 UserDetailsService 인터페이스를 상속받고 loadUserByUsername 메소드를 작성해주면 된다.

여기서는 받은 문자열을 기준으로 accountRepository에서 해당 계정을 이메일 혹은 닉네임으로 찾는다.

그리고 마지막 반환 값은 SpringSecurityContextHolder.getContext().getAuthentication()principal이 된다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글