[Zero-base] 6-13. 스프링 시큐리티를 이용한 로그인&로그아웃 구현
Dependency - Spring Security 추가
- 빈 프로젝트 생성
Dependencies:
- Spring Security
Copy:
- dependency - spring-boot-starter-security
Paste:
- dependency - spring-boot-starter-security
- Maven Load
프로젝트 실행
- Using generated security password: 3485b72e-14b1-42bd-a441-1c8ca10dba02
- Security 라이브러리에 따라서 login 절차가 생김
- Username: user
- Password: 3485b72e-14b1-42bd-a441-1c8ca10dba02
- 로그인 이후 메인 페이지 접근 가능
- 일단 로그아웃 처리
SecurityConfiguration 클래스 생성
- 로그인을 관리할 클래스 생성
Override (Ctrl + O):
- configure
메인 페이지에서 '회원정보' 링크 추가
- 메인 페이지에서 '회원정보' 링크 추가
- '회원정보' 링크가 추가된 모습
- 메인페이지의 링크를 맵핑
- 주소는 /member/info
'회원정보' 페이지 생성
- 접속 권한 획득 여부 확인 목적으로 특별한 기능이 없는 페이지
접속 권한 지정
로그인 하지 않은 상태에서 접근 가능한 페이지
- 메인페이지(/)
- 회원가입페이지(/member/register)
- 계정인증페이지(/email-auth)
- 로그인페이지(/member/login)에서 error가 발생한 경우
- 로그인에 실패했다는 에러 메세지 노출
@Bean
UserAuthenticationFailureHandler getFailureHandler() {
return new UserAuthenticationFailureHandler();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService)
.passwordEncoder(getPasswordEncoder());
super.configure(auth);
- UserDetailService를 상속받음
- Override: loadUserByUsername
Override를 구현
- memberRepository.findById(username)을 받아와서 회원 정보 존재 여부 확인
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Member> optionalMember = memberRepository.findById(username);
if (!optionalMember.isPresent()) {
throw new UsernameNotFoundException("회원 정보가 존재하지 않습니다.");
}
Member member = optionalMember.get();
if (!member.isEmailAuthYn()) {
throw new MemberNotEmailAuthException("계정 활성화 이후에 로그인 해주세요.");
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(member.getUserId(), member.getPassword(), grantedAuthorities);
}
Bean으로 묶어줌
- getPasswordEncoder()로 받아와서 BCryptPasswordEncorder()로 보냄
@Bean
PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService)
.passwordEncoder(getPasswordEncoder());
super.configure(auth);
}
- 로그인페이지 맵핑
로그인페이지 구현
form을 post방식으로 만들어줌
- 아이디, 비밀번호 입력칸과 로그인 버튼 생성
<form method="post">
<div>
<input type="text" name="username" placeholder="아이디(이메일)을 입력" required/>
</div>
<div>
<input type="password" name="password" placeholder="비밀번호 입력" required/>
</div>
<div>
<button typre="submit"> 로그인 </button>
</div>
</form>
- 생성된 로그인 페이지
로그인페이지 테스트
- 이메일, 비밀번호 입력 - 로그인 실패
- 일단 테스트를 위하여 http.csrf().disable()
http.csrf().disable();
- 이메일, 비밀번호 입력 - 로그인 실패
- Post 방식을 지원하지 않음
- Get과 Post방식을 모두 지원하는
- RequestMapping으로 변경
@RequestMapping("/member/login")
public String login() {
return "member/login";
}
- 로그인 실패 시 에러메시지를 노출할 부분 추가
<div th:text="${errorMessage}"></div>
- 로그인 실패한 경우 에러 메시지 노출
비밀번호 저장 방식 변경
- BCrypt.hashpw를 사용하여 암호화된 비밀번호로 저장
String encPassword = BCrypt.hashpw(parameter.getPassword(), BCrypt.gensalt());
String uuid = UUID.randomUUID().toString();
//builder pattern
Member member = Member.builder()
.userId(parameter.getUserId())
.userName(parameter.getUserName())
.phone(parameter.getPhone())
.password(encPassword)
.regDt(LocalDateTime.now())
.emailAuthYn(false)
.emailAuthKey(uuid)
.build();
memberRepository.save(member);
- 다시 회원가입 (비밀번호 '1111' 입력)
- 일단 회원가입은 성공
- DB에서도 password가 암호회된 모습을 확인할 수 있음
권한에 따른 페이지 접근 테스트
- 가입시킨 계정으로 로그인
- 메인 페이지에서 회원 정보 페이지 이동 : 성공
로그아웃 기능 구현
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true);
로그아웃 기능 테스트
- 로그인 상태에서 로그아웃 링크 클릭
- 로그아웃 상태에서 회원정보 링크 클릭
- 로그인 페이지에 접근되나,
- 로그아웃 상태이므로 로그인 페이지 노출
계정 활성화 이전 회원정보 페이지 접근 제한
if (!member.isEmailAuthYn()) {
throw new MemberNotEmailAuthException("계정 활성화 이후에 로그인 해주세요.");
}
- 클래스가 없어서 발생하는 에러
- 클래스 생성
- 에러가 발생되지 않도록 RuntimeException을 상속받음
- 로그인 실패한 경우 에러메시지 노출 구현
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
String msg = "로그인에 실패하였습니다.";
if (exception instanceof InternalAuthenticationServiceException) {
msg = exception.getMessage();
}
setUseForward(true);
setDefaultFailureUrl("/member/login?error=true");
request.setAttribute("errorMessage", msg);
System.out.println("로그인에 실패하였습니다.");
계정 활성화 이전 회원정보 페이지 접근 테스트
- 계정 활성화 이전 회원 정보 페이지에서 로그인하면
- 계정 활성화를 선행하라는 에러 메시지 노출