로그인 기능 구현
회원가입 기능을 구현했으니 로그인 기능을 구현해보려고 한다.
/security/SecurityConfig.java에 login url 설정
// 로그인 url을 지정 ~/member.login, 로그인 성공하면 루트로 변경
.formLogin((fl) -> fl.loginPage("/member/login").defaultSuccessUrl("/"))
- 로그인 페이지와 로그인 성공시 이동 페이지를 지정해주었다
/repository/MemberRepository.java find* 메서드 추가
package com.eunji.backboard.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.eunji.backboard.entity.Member;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long>{
Optional<Member> findByUserName(String username);
Optional<Member> findByEmail(String email);
}
- db에 있는 데이터와 로그인 입력 데이터가 일치하는지 확인하기 위해 repository에 메서드를 추가
/controller/MemberController.java에 login() 메서드 추가
@GetMapping("/login")
public String login() {
return "member/login";
}
- PostMapping은 생성할 필요 ❌
- SpringSecurity에서 제공해주기 때문.
/templates/member/login.html 생성
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
<div layout:fragment="main-content" class="card container my-3 form-register">
<div class="my-3 border-bottom">
<h4>로그인</h4>
</div>
<form th:action="@{/member/login}" method="post">
<div th:if="${param.error}">
<div class="alert alert-danger">
사용자 이름, 비밀번호를 확인해주세요.
</div>
</div>
<div class="mb-3">
<label for="username" class="form-lable">사용자이름</label>
<input type="text" name="username" class="form-control">
</div>
<div class="mb-3">
<label for="password" class="form-lable">비밀번호</label>
<input type="password" name="password" class="form-control">
</div>
<div class="d-flex justify-content-center">
<button type="submit" class="btn btn-sm btn-primary mx-2">로그인</button>
<a href="/" class="btn btn-sm btn-secondary">취소</a>
</div>
</form>
</div>
</html>
/security/MemberRole.java 생성
package com.eunji.backboard.security;
import lombok.Getter;
@Getter
public enum MemberRole {
// 방법1
// ADMIN("관리자", "ROLE_ADMIN"), USER("사용자","ROLE_USER");
// MemberRole(String key, String value) {
// this.key = key;
// this.value = value;
// }
// private String key;
// private String value;
// 방법2
ADMIN("ROLE_ADMIN"), USER("ROLE_USER");
MemberRole(String value) {
this.value = value;
}
private String value;
}
- 권한으로 처리하기 위해 ENUM 클래스로 만듦
/entity/Member.java에 MemberRole 추가
@Enumerated(EnumType.STRING) // Enum타입이 STRING인 이유: "ROLE_ADMIN", "ROLE_USER"이기 때문
@Column(length = 12)
private MemberRole role;
DB를 확인하면 ROLE 컬럼이 추가된 것을 확인할 수 있다.
/service/MemberService.java setMember() 메서드 수정
public Member setMember(String username, String email, String password) {
Member member = Member.builder().username(username).email(email).regDate(LocalDateTime.now()).build();
// BCryptPasswordEncoder 매번 새롭게 객체를 생성한다.
// 이것보다는 Bean 등록해놓고 쓰는 게 유지보수를 위해서 더 좋음
// BCryptPasswordEncoder pwdEncoder = new BCryptPasswordEncoder();
member.setPassword(passwordEncoder.encode(password)); // 암호화한 값을 DB에 저장
member.setRegDate(LocalDateTime.now());
member.setRole(MemberRole.USER); // 일반사용자 권한
this.memberRepository.save(member);
return member;
}
- member.setRole(MemberRole.USER) 코드 추가
- DB 확인 결과 ROLE 컬럼에 USER가 들어간 것을 확인할 수 있다.
/service/MemberSecurityService.java 생성
package com.eunji.backboard.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
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 com.eunji.backboard.entity.Member;
import com.eunji.backboard.repository.MemberRepository;
import com.eunji.backboard.security.MemberRole;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class MemberSecurityService implements UserDetailsService{
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Member> _member = this.memberRepository.findByUsername(username);
if(_member.isEmpty()) {
throw new UsernameNotFoundException("사용자가 없습니다.");
}
Member member = _member.get();
List<GrantedAuthority> authorities = new ArrayList<>();
if("admin".equals(username)) {
authorities.add(new SimpleGrantedAuthority(MemberRole.ADMIN.getValue()));
} else {
authorities.add(new SimpleGrantedAuthority(MemberRole.USER.getValue()));
}
return new User(member.getUsername(), member.getPassword(), authorities);
}
}
- Spring Security가 로그인 유효성 검사를 해줌
/security/SecurityConfig.java 계정 관리자 빈 추가
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception{
return authenticationConfiguration.getAuthenticationManager();
}
/templates/layout.html 로그인/로그아웃 토글 메뉴 추가
<li class="nav-item">
<!-- 로그인을 안했을 때 -->
<a th:href="@{/member/login}" sec:authorize="isAnonymous()" class="nav-link">로그인</a>
<!-- 로그인된 이후에 표시 -->
<a th:href="@{/member/logout}" sec:authorize="isAuthenticated()" class="nav-link">로그아웃</a>
</li>
<li class="nav-item">
<a th:href="@{/member/register}" sec:authorize="isAnonymous()" class="nav-link">회원가입</a>
</li>
- 로그인 성공시 nav바에 로그아웃으로 바뀌는 것을 확인할 수 있다.
로그아웃 기능 구현
/security/SecurityConfig.java 로그아웃 코드 추가
.logout((logout) -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
);
'Spring Boot > STUDY' 카테고리의 다른 글
[Spring Boot] JPA 프로젝트 - 수정 기능 구현 (0) | 2024.06.24 |
---|---|
[Spring Boot] JPA 프로젝트 - 게시글 등록 시 작성자 추가 (0) | 2024.06.21 |
[Spring Boot] JPA 프로젝트 - 회원가입 기능 구현 (1) | 2024.06.21 |
[Spring Boot] JPA 프로젝트 - Spring Security (0) | 2024.06.20 |
[Spring Boot] JPA 프로젝트 - 페이징 기능 구현 (0) | 2024.06.20 |