본문 바로가기
웹개발

quenswer(프로젝트)-spring security(로그인,로그아웃)

by nyeongha 2024. 2. 4.

로그인 템플릿을 먼저 작성해준다.

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
  <form th:action="@{/member/login}" method="post">
    <div th:if="${param.error}">
      <div class="alert alert-danger">
        사용자ID 또는 비밀번호를 확인해 주세요.
      </div>
    </div>
    <div class="mb-3">
      <label for="username" class="form-label">사용자ID</label>
      <input type="text" name="username" id="username" class="form-control">
    </div>
    <div class="mb-3">
      <label for="password" class="form-label">비밀번호</label>
      <input type="password" name="password" id="password" class="form-control">
    </div>
    <button type="submit" class="btn btn-primary">로그인</button>
  </form>
</div>
</html>

 

먼저 권한생성을 위해 MemberRole이라는 enum자료형을 생성해준다.

@Getter
public enum MemberRole {
    Admin("admin"),Member("member");

    private String value;
    MemberRole(String value){
        this.value=value;
    }
}

 

스프링 시큐리티가 로그인시 사용할 기능을 작성해준다.

package com.example.quenswer.member;

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class MemberSecurityService implements UserDetailsService {		#UserDetailsService는 loadUserByUsername 메서드를 구현하도록 강제하는 인터페이스
    private final MemberRepository memberRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {		#loadUserByUsername 메서드는 사용자명(username)으로 스프링 시큐리티의 사용자(Member) 객체를 조회하여 리턴하는 메서드
        Optional<Member> om=memberRepository.findByUserId(username);
        if (om.isEmpty()){
            throw new UsernameNotFoundException("사용자를 찾을수 없습니다.");
        }
        Member member=om.get();
        List<GrantedAuthority> authorities=new ArrayList<>();
        if ("admin".equals(username)){
            authorities.add(new SimpleGrantedAuthority(MemberRole.Admin.getValue()));
        }else {
            authorities.add(new SimpleGrantedAuthority(MemberRole.Member.getValue()));
        }
        return new User(member.getUserId(),member.getPassword(),authorities);
    }
}

 

UserDetailsService는 spring security에서 유저정보를 가져오는 인터페이스로, UserDetails타입의 loadUserByUsername메서드를 구현해야한다.

loadUserByUsername 메서드는 username으로 Member 객체를 조회하고, 만약 사용자명에 해당하는 데이터가 없을 경우에는 UsernameNotFoundException을 발생시킨다.

그리고 사용자명이 'admin'인 경우에는 ADMIN 권한(ROLE_ADMIN)을 부여하고 그 이외의 경우에는 USER 권한(ROLE_USER)을 부여했다.User 생성자에는 사용자명, 비밀번호, 권한 리스트가 전달된다.

 

header.heml도 수정해줍니다.

<header class="p-3 text-bg-dark" th:fragment="headerFragment" xmlns:sec="http://www.w3.org/1999/xhtml">
  <div class="container">
    <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
      <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
        <svg class="bi me-2" width="40" height="32" role="img" aria-label="Bootstrap">
          <use xlink:href="#bootstrap"></use>
        </svg>
      </a>

      <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
        <li><a href="/question/" class="nav-link px-2 text-white">Home</a></li>
        <li><a href="#" class="nav-link px-2 text-white">Features</a></li>
        <li><a href="#" class="nav-link px-2 text-white">Pricing</a></li>
        <li><a href="#" class="nav-link px-2 text-white">FAQs</a></li>
        <li><a href="#" class="nav-link px-2 text-white">About</a></li>
      </ul>

      <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search">
        <input type="search" class="form-control form-control-dark text-bg-dark" placeholder="Search..."
               aria-label="Search">
      </form>

      <div class="text-end">
        <button type="button" class="btn btn-outline-light me-2" sec:authorize="isAnonymous()" th:onclick="|location.href='@{/member/login}'|">Login</button>		#sec:authorize="isAnonymous()": 로그인되지 않은 경우에 해당 요소(로그인 링크)가 표시
        <button type="button" class="btn btn-outline-light me-2" sec:authorize="isAuthenticated()" th:onclick="|location.href='@{/member/logout}'|">Logout</button>		#sec:authorize="isAuthenticated()": 로그인된 경우에 해당 요소(로그아웃 링크)가 표시
        <button type="button" class="btn btn-warning" th:onclick="|location.href='@{/member/signup}'|">Sign-up</button>
      </div>
    </div>
  </div>
</header>

로그인을 하지않은 상태(로그인표시)

로그인을 성공한 상태(로그아웃버튼 활성화)