스프링 시큐리티를 이용하여 로그인/로그아웃 기능을 구현
스프링 시큐리티에서 회원의 정보를 담기 위해서 사용하는 인터페이스 UserDetails이다.
인터페이스를 직접 구현하거나 스프링 시큐리티에서 제공하는 User 클래스를 사용.
User클래스는 UserDetails인터페이스를 구현 하고있는 클래스
로그인 기능 구현을 위해 기존에 만들었던 UserService가 UserDetailsService 를 구현
@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();
}
@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();
}
}
로그인페이지에서는 회원의 아이디. 비밀번호 입력란과 회원가입 버튼도 같이 생성
<!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>
@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>
test패키지에 UsersControllerTest클래스 생성
@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);
}
}
테스트성공과 콘솔창에 테스트가 잘 나오는걸 확인할 수 있다.!
메뉴바에서 로그인 했을경우, 로그인 하지않았을 경우, 상품등록의 경우 관리자만 보여지도록 해야한다.
라이브러리를 추가해야 하므로 의존성 추가해준다.
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!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 으로 변경하고 회원가입을하면 어드민으로 설정이되서
상품등록이과 상품관리도 나오는걸 확인할 수 있다.