로그인 / 로그아웃 기능 구현

Codren·2021년 9월 24일
0

Spring Boot 쇼핑몰

목록 보기
13/32

Section 1. 로그인 / 로그아웃

1. UserDetailsService

  • 데이터베이스에서 회원 정보를 가져오는 인터페이스
  • loadUserByUsername() 메소드를 통해 회원정보를 조회 -> UserDetails 인터페이스 반환




2. UserDetails

  • 회원 정보를 담는 인터페이스
  • 직접 구현하거나 스프링 시큐리티에서 제공하는 User 클래스 사용 (구현체)




3. MemberService 로그인 / 로그아웃 구현

  • UserDetailsService 인터페이스를 구현하고 loadUserByUsername() 메소드 오버라이딩
  • Builder 패턴을 이용하여 UserDetail 인터페이스를 구현한 User 객체 생성 후 반환




4. SecurityConfig 인증 filter 추가

  • configure(HttpSecurity) 메소드를 통해 로그인 및 로그아웃 URL 지정
  • http.formLogin() - http 를 통해 들어오는 form 기반 request 를 이용하여 Login 을 처리
  • form 태그에서 사용자의 ID 부분은 default 값으로 "username" 필드

  • AuthenticationManagerBuilder 를 통해 AuthenticationManager 를 생성하여 인증 처리 수행
  • UserDetailsService 인터페이스를 구현하고 loadUserByUsername 메소드를 오버라이딩한 memberService 객체를 이용하여 User 객체를 얻어낸 뒤, 지정된 비밀번호 암호화 방식으로 비밀번호가 일치하는지 검증




5. 로그인 페이지

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/layout1}">

<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
    <style>
        .error {
            color: #bd2130;
        }
    </style>
</th:block>

<div layout:fragment="content">
    <form role="form" method="post" action="/members/login">
        <div class="form-group">
            <label th:for="email">이메일주소</label>
            <input type="email" name="email" class="form-control" placeholder="이메일을 입력해주세요">
        </div>
        <div class="form-group">
            <label th:for="password">비밀번호</label>
            <input type="password" name="password" id="password" class="form-control" placeholder="비밀번호 입력">
        </div>
        <p th:if="${loginErrorMsg}" class="error" th:text="${loginErrorMsg}"></p>
        <button class="btn btn-primary">로그인</button>
        <button type="button" class="btn btn-primary" onClick="location.href='/members/new'">회원가입</button>
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
    </form>
</div>
</html>




6. spring-security-test 의존성 주입

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
    <version>${spring-security.version}</version>
</dependency>




7. 로그인 테스트

  • MemberControllerTest 클래스 생성 및 DI
  • @AutoConfigureMockMvc - Controller 를 테스트할 때, 서블릿 컨테이너를 모킹하여 가상의 클라이언트로부터 Request 요청을 보내는 역할 수행

  • 로그인 성공 테스트
  • perform() 메소드 - 브라우저에서 서버에 URL 요청을 하는 것처럼 Reqeust 작성
formLogin() - form 태그 기반의 로그인 인증 방식(http.formLogin())을 테스트하기 위해 form 태그 기반 POST request 객체를 생성
loginProcessingUrl() - 요청 URL 설정 (formLogin() 메소드의 매개변수로 설정해도됨)
userParameter("email") - formLogin 방식에서 Request 메시지 Body 부분은 "username"=value 가 default 값이기 때문에 "email"=value 로 변경 
user().password() - Requset 메시지 Body Data 설정

  • 로그인 실패 테스트

참고 블로그1, 참고 블로그2




8. thymeleaf-extras-springsecurity5 의존성 추가

  • 로그인한 상태에서는 로그아웃만 노출, 상품 등록 메뉴는 관리자로 로그인 했을 때만 노출 되도록 인증 및 권한에 따라 설정 변경을 도와주는 라이브러리
<dependency>
      <groupId>org.thymeleaf.extras</groupId>
      <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>




9. header Navbar 부분 수정

  • xmlns:sec 네임 스페이스 지정
  • isAnonymous() - 게스트
  • isAuthenticated() - 로그인
  • hasAnyAuthority('ROLE_ADMIN') - ADMIN
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<div th:fragment="header">
    <nav class="navbar navbar-expand-sm bg-primary navbar-dark">
        <button class="navbar-toggler" type="button" data-toggle="collapse"
                data-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <a class="navbar-brand" href="/">Shop</a>

        <div class="collapse navbar-collapse" id="navbarTogglerDemo03">
            <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
                <li class="nav-item" sec:authorize="hasAnyAuthority('ROLE_ADMIN')">
                    <a class="nav-link" href="/admin/item/new">상품 등록</a>
                </li>
                <li class="nav-item" sec:authorize="hasAnyAuthority('ROLE_ADMIN')">
                    <a class="nav-link" href="/admin/items">상품 관리</a>
                </li>
                <li class="nav-item" sec:authorize="isAuthenticated()">
                    <a class="nav-link" href="/cart">장바구니</a>
                </li>
                <li class="nav-item" sec:authorize="isAuthenticated()">
                    <a class="nav-link" href="/orders">구매이력</a>
                </li>
                <li class="nav-item" sec:authorize="isAnonymous()">
                    <a class="nav-link" href="/members/login">로그인</a>
                </li>
                <li class="nav-item" sec:authorize="isAuthenticated()">
                    <a class="nav-link" href="/members/logout">로그아웃</a>
                </li>
            </ul>
            <form class="form-inline my-2 my-lg-0" th:action="@{/}" method="get">
                <input name="searchQuery" class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
                <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
            </form>
        </div>
    </nav>
</div>
</html>




10. 인증 및 권한에 따른 화면

  • Guest 상태

0개의 댓글