[스프링부트JPA] 쇼핑몰 로그인/로그아웃 구현하기

JEONG SUJIN·2023년 1월 29일
0

스프링 시큐리티를 이용하여 로그인/로그아웃 기능을 구현

UserDetailsService

  • UserDetailService 인터페이스는 데이터베이스에서 회원정보를 가져오는 역할을 담당
  • loadUserByUsername() 메서드가 존재하며, 회원정보를 조회하여 사용자의 정보와 권한을 갖는 UserDetails인터페이스를 반환

UserDetail

스프링 시큐리티에서 회원의 정보를 담기 위해서 사용하는 인터페이스 UserDetails이다.
인터페이스를 직접 구현하거나 스프링 시큐리티에서 제공하는 User 클래스를 사용.
User클래스는 UserDetails인터페이스를 구현 하고있는 클래스

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

로그인 기능 구현을 위해 기존에 만들었던 UserService가 UserDetailsService 를 구현

UserService.java

  • UserService가 UserDetailsService를 구현
  • UserDetailsService 인터페이스의 loadUserByUsername오버라이딩 한다. 로그인할 유저의 email을 파라미터로 받는다
  • UserDetail을 구현하고 있는 User 객체를 반환해준다. User 객체를 생성하기 위해 생성자로 회원의 이메일, 비밀번호, Role을 파라미터로 넘겨준다.
@Service
@Transactional
@RequiredArgsConstructor
public class UsersService implements UserDetailsService {

	private final UsersRepository usersRepository;
	
	public Users saveUser(Users users) {
		validateDuplicateUser(users);
		return usersRepository.save(users);
	}
	
	//이미 가입된 회원일 경우 예외처리
	private void validateDuplicateUser(Users users) {
		Users findUser = usersRepository.findByEmail(users.getEmail());
		
		if(findUser != null) {
			
			throw new IllegalStateException("이미 가입된 회원입니다.");
		}	
	}
	
	@Override
	public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException{
		Users users = usersRepository.findByEmail(email);
		
		if(users == null) {
			throw new UsernameNotFoundException(email);
		}
		
		return User.builder()
                .username(users.getEmail())
                .password(users.getPassword())
                .roles(users.getRole().toString())
                .build();
		
	}

SecurityConfig.java

@Configuration // 빈 등록(Ioc관리)
@EnableWebSecurity
public class SecurityConfig {

@Autowired
	UsersService usersService;

@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http.formLogin()
				.loginPage("/user/login") //로그인페이지 URL
				.defaultSuccessUrl("/") //로그인 성공시 이동할 페이지
				.usernameParameter("email") // 로그인시 사용할 파라미터 이름 설정
				.failureUrl("/user/login/error") // 로그인 실패시 이동할 URL
				.and()  
				.logout()
				.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))//로그아웃 URL
				.logoutSuccessUrl("/"); //로그아웃 성공시 URL
		
		return http.build();
}

@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

}

UserLoginForm.html

로그인페이지에서는 회원의 아이디. 비밀번호 입력란과 회원가입 버튼도 같이 생성

<!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>
.fieldError {
	color: #bd2130;
}
</style>
</th:block>


<div layout:fragment="content">


	<form role="form" method="post" action="/user/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"
				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='/user/new'">회원가입</button>
		<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">

	</form>

</div>

</html>

UserController.java

@GetMapping("/login")
	public String loginUser() {
		return "user/userLoginForm";
	}
    
@GetMapping("/login/error")
	public String loginError(Model model) {
		model.addAttribute("loginErrorMsg", "아이디 또는 비밀번호를 확인해주세요.");
		return "/user/userLoginForm";
	}    

로그인화면 !

로그인 실패

로그인 테스트

시큐리티 테스트 의존성 라이브러리 넣기

<!-- 시큐리티 테스트 의존성  -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
			<version>${spring-security.version}</version>
		</dependency>

UsersControllerTest.java

test패키지에 UsersControllerTest클래스 생성

  • MockMvc 테스트를 위해 @AutoConfigureMockMvc 어노테이션 선언
  • MockMvc 클래스를 이용해 실제 객체와 비슷하지만 테스트에 필요한 기능만 가지는 가짜 객체. MockMvc객체를 이용하면 웹 브라우저에서 요청을 하는 것 처럼 테스트 할 수 있다.
  • 로그인 테스트 진행을 위해서 로그인 전 회원 등록 메서드 생성
  • 회원가입 메서드를 실행 후 가입된 회원 정보로 로그인이 되는지 테스트를 진행.
  • userParameter() 를 이용하여 이메일을 아이디로 세팅하고 로그인 URL에 요청한다.
  • 로그인이 성공하여 인증되었다면 테스트 코드가 성공한다.
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
 class UsersControllerTest {

	@Autowired
	private UsersService usersService;
	
	@Autowired
	private MockMvc mockMvc;
	
	@Autowired
	PasswordEncoder passwordEncoder;
	
	
	//회원등록
	public Users createUsers(String email, String password) {
		UsersFormDto usersFormDto = new UsersFormDto();
		usersFormDto.setName("정수진");
		usersFormDto.setEmail(email);
		usersFormDto.setAddress("부산광역시 수영구 광안동");
		usersFormDto.setPassword(password);	
		
		Users users = Users.createUser(usersFormDto, passwordEncoder);
		return usersService.saveUser(users);
	}
	
	@Test
	@DisplayName("로그인 성공 테스트")
	public void loginSuccessTest() throws Exception {
		String email = "test@naver.com";
		String password = "1234";
		this.createUsers(email, password);
		mockMvc.perform(formLogin()
				.userParameter("email")
				.loginProcessingUrl("/user/login")
				.user(email)
				.password(password))
				.andExpect(SecurityMockMvcResultMatchers.authenticated());
                
                System.out.println("로그인 성공 테스트 :: " + email);
		        System.out.println("로그인 성공 테스트 :: " +  password);

	}
}

테스트성공과 콘솔창에 테스트가 잘 나오는걸 확인할 수 있다.!

메뉴바에서 로그인 했을경우, 로그인 하지않았을 경우, 상품등록의 경우 관리자만 보여지도록 해야한다.

pom.xml

라이브러리를 추가해야 하므로 의존성 추가해준다.

<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

header.html

  • xmlns:sec="http://www.thymeleaf.org/extras/spring-security" 태그를 사용하기 위해 네임스페이스를 추가
  • 관리자계정(ADMIN_ROLE)으로 로그인한 경우 상품등록, 상품관리메뉴 보여준다.
  • 장바구니와 구매이력 페이지의 경우 로그인(인증) 했을 경우에만 보여주기
  • 로그인하지 않은 상태면 로그인 메뉴 보여준다.
  • 로그인한 상태이면 로그아웃 메뉴를 보여준다.
<!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="/user/login">로그인</a>
                </li>
                <li class="nav-item" sec:authorize="isAuthenticated()">
                    <a class="nav-link" href="/user/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>

회원가입을 하고 로그인을 하면 장바구니, 구매이력, 로그아웃 나온다.

기존에 User.java에 Role.USER -> Role.ADMIN 으로 변경하고 회원가입을하면 어드민으로 설정이되서

상품등록이과 상품관리도 나오는걸 확인할 수 있다.

profile
기록하기

0개의 댓글