사이먼's 코딩노트
[SpringBoot] 로그인 구현 본문
[로그인 구현하기]
- 회원가입 기능을 구현하였다면, 이번에는 로그인 기능을 구현해봅시다.
- 현재 DB에서 site_user라는 테이블에 회원 데이터가 저장되어 있고, 테이블에 저장된 ID와 비밀번호로 로그인을 진행한다.
- 먼저 스프링 시큐리티가 적용된 SecurityConfig.java 클래스에 로그인을 위한 URL을 아래와 같이 설정한다.
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(
(authorizeHttpRequests) -> authorizeHttpRequests
// 로그인 없이도 열람이 가능한 페이지
.requestMatchers(new AntPathRequestMatcher("/question/list")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/user/signup")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/user/login")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/style.css")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/")).permitAll()
// 로그인이 완료되면 나머지 페이지들도 열람 가능
.anyRequest().authenticated()
)
.formLogin((formLogin) -> formLogin
.loginPage("/user/login")
.loginProcessingUrl("user/login")
.defaultSuccessUrl("/"))
;
return http.build();
}
- 상단에 추가된 requestMatchers() 메서드를 통해 로그인 없이도 열람이 가능한 URL 주소를 미리 설정한다.
- 지금의 경우 로그인 없이 열람이 가능한 화면은 질문 목록, 회원가입, 로그인, Root URL과 스타일시트이다. 스타일시트는 폰트적용이나 기본 디자인을 위해서 반드시 포함해야한다.
- formLogin() 메서드는 스프링 시큐리티의 로그인 설정을 담당하는 부분으로, 설정 내용은 로그인 페이지의 URL은 /user/login이고 로그인 성공 시에 이동할 페이지는 Root URL임을 의미한다.
- loginPage("user/login")은 GET 방식으로 요청되면 스프링 시큐리티에게 우리가 만든 로그인 페이지 URL을 전송한다.
- 이 코드를 작성하지 않으면 로그인 페이지는 기본으로 되어있는 "/login"이 된다.
- loginProcessingUrl("/user/login")은 POST 방식으로 요청되면 스프링 시큐리티에게 로그인 폼 처리 URL을 알려준다.
[로그인 URL 매핑 추가]
- 스프링 시큐리티에 로그인 URL을 /user/login으로 설정했다면 UserController.java 클래스에서 해당 URL을 매핑해야한다.
- 아래는 해당 코드를 UserController.java 클래스에서 추가한 모습이다.
@GetMapping("/login")
public String login() {
return "login_form";
}
- @GetMapping 어노테이션을 통해 /user/login URL로 들어오는 GET 요청을 login() 메서드가 처리한다.
- 매핑한 login() 메서드는 login_form.html 템플릿을 return 하도록한다.
- 실제 로그인을 진행하는 @PostMapping 방식의 메서드는 스프링 시큐리티가 대신 처리하기 때문에 직접 컨트롤러에 코드를 추가할 필요는 없다.
[login_form.html 템플릿 생성]
- 아래는 실제로 페이지에 로그인 화면이 표출될 수 있도록 login_form.html 템플릿을 생성하고 작성한 코드이다.
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
<form th:action="@{/user/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>
- 사용자 ID와 비밀번호로 로그인할 수 있는 템플릿을 작성하고 스프링 시큐리티의 로그인이 실패할 경우에는 시큐리티의 기능으로 인해 로그인 페이지로 리다이렉트된다.
- 로그인 페이지의 매개변수로 error가 전달되는 경우, 사용자 ID 또는 비밀번호를 확인해 주세요. 라는 에러 메시지를 출력하도록 하였다.
- 로그인 실패 시 매개변수로 error가 전달되는 것은 스프링 시큐리티의 규칙이라고 생각하면 좋다.
- 스프링 시큐리티는 로그인 실패 시 localhost:8090/user/login?error와 같이 error 매개변수를 전달하고 이 때, 템플릿에서 th:if="${param.error}"를 통해 error 매개변수가 전달되었는지 확인할 수 있다.
[UserSecurityService 서비스 생성]
- 지금 상태에서 로그인을 수행할 수는 없다. 그 이유는 스프링 시큐리티에 무엇을 기준으로 로그인해야 하는지 아직 설정하지 않았기 때문이다.
- 스프링 시큐리티를 통해 로그인을 수행하는 방법에는 여러 가지가 있지만 우리는 이미 회원 정보를 DB에 저장했기 때문에 DB에서 회원 정보를 조회하여 로그인하는 방법을 사용할 것이다.
- 그렇기 때문에 DB에서 사용자를 조회하는 서비스인 UserSecuritySerivce.java 클래스를 생성하고, 그 서비스를 스프링 시큐리티에 등록해야 한다.
- 먼저 서비스를 생성하기 전에 UserRepository를 수정하고 UserRole.java 클래스를 생성하는 과정이 필요하다.
- 아래는 UserRepository.java 클래스에서 사용자 ID로 SiteUser 엔티티를 조회하는 findByUsername() 메서드를 추가한 코드이다.
Optional<SiteUser> findByusername(String username);
- 스프링 시큐리티는 인증뿐만 아니라 권한도 관리하기 때문에 사용자 인증 후 사용자에게 부여할 권한과 관련된 내용도 필요하다.
- 그러므로 사용자가 로그인한 후, Admin 또는 User와 같은 권한을 부여해야 한다.
- 아래는 UserRole.java 클래스를 추가하여 작성한 코드이다.
package com.sbs.sbb.User;
import lombok.Getter;
@Getter
public enum UserRole {
ADMIN("ROLE_ADMIN"),
USER("ROLE_USER");
UserRole(String value) {
this.value = value;
}
private String value;
}
- UserRole은 enum 자료형으로 작성하였다.
- 관리자를 의미하는 Admin과 사용자를 의미하는 User라는 상수를 만들었고, ADMIN은 'ROLE_ADMIN', USER는 'ROLE_USER'라는 값을 부여하였다.
- UserRole의 Admin과 User 상수는 값을 변경할 필요가 없기 때문에 @Getter만 사용할 수 있도록 하였다.
- 준비가 완료되었다면 UserSecurityService.java 클래스를 생성하여 아래와 같이 코드를 작성한다.
package com.sbs.sbb.User;
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;
@RequiredArgsConstructor
@Service
public class UserSecurityService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<SiteUser> _siteUser = this.userRepository.findByusername(username);
if (_siteUser.isEmpty()) {
throw new UsernameNotFoundException("사용자를 찾을수 없습니다.");
}
SiteUser siteUser = _siteUser.get();
List<GrantedAuthority> authorities = new ArrayList<>();
if ("admin".equals(username)) {
authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.getValue()));
} else {
authorities.add(new SimpleGrantedAuthority(UserRole.USER.getValue()));
}
return new User(siteUser.getUsername(), siteUser.getPassword(), authorities);
}
}
- 스프링 시큐리티가 로그인 시 사용할 UserSecurityService는 스프링 시큐리티가 제공하는 UserDetailsService 인터페이스를 구현해야 한다.
- UserDetailsService는 loadUserByUsername() 메서드를 구현하도록 강제하는 인터페이스이다.
- loadUserByUsername() 메서드는 사용자명으로 스프링 시큐리티의 사용자 객체를 조회하여 return 하는 메서드이다.
- loadUserByUsername() 메서드는 사용자명으로 SiteUser 객체를 조회하고, 만약 사용자명에 해당하는 데이터가 없을 경우에는 UsernameNotFoundException을 발생시킨다.
- 사용자명이 'admin'인 경우에는 Admin 권한인 'ROLE_ADMIN'을 부여하고 그 외의 경우에는 User 권한을 부여하였다.
- 마지막으로 User 객체를 생성하여 return 하는데, 이 객체는 스프링 시큐리티에서 사용하며 User 생성자에는 사용자명, 비밀번호, 권한 리스트가 전달된다.
- 참고로 스프링 시큐리티는 loadUserByUsername() 메서드에 의해 return된 User 객체의 비밀번호가 사용자로부터 입력받은 비밀번호와 일치하는지 검사하는 기능을 내부에 가지고있다.
[로그인 화면 링크 추가]
- 마지막으로 로그인 페이지에 진입할 수 있도록 로그인 링크인 /user/login을 내비게이션 바에 추가해봅시다.
- 아래는 navbar.html 에서 수정된 코드이다.
<li class="nav-item">
<a class="nav-link" th:href="@{/user/login}">로그인</a>
</li>
- th:href="@{/user/login}"와 같이 링크를 수정해주면 '로그인' 버튼을 클릭했을 때 로그인 화면이 성공적으로 표시된다.
- 모든 코드 작성을 마쳤다면 로컬 서버를 다시 실행시켰을 때 아래 사진과 같은 로그인 화면이 표시된다.
- 포스팅 가장 상단에서 적용했던 것을 다시 기억해보면, 스프링 시큐리티를 통해 로그인을 하지 않았을 때 열람이 되지 않는 페이지가 있다는 것을 참고하면서 테스트를 해보면 좋을 것 같다.
반응형
'Java > SpringBoot' 카테고리의 다른 글
[SpringBoot] 질문 수정 및 삭제 (0) | 2024.05.15 |
---|---|
[SpringBoot] 로그아웃 / 작성자 추가 (0) | 2024.05.14 |
[SpringBoot] 회원가입 구현 (0) | 2024.05.13 |
[SpringBoot] 게시물 번호 정렬 / 답변 개수 표시 (0) | 2024.05.13 |
[SpringBoot] 내비게이션 바 / 페이징 (2) | 2024.05.13 |