cs/java-spring-boot

[Zero-base] 6-13. 스프링 시큐리티를 이용한 로그인&로그아웃 구현

Lomo 2022. 2. 17. 22:30
반응형

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

 

 

 

localhost:8080

  • Security 라이브러리에 따라서 login 절차가 생김

 

 

 

  • Username: user
  • Password: 3485b72e-14b1-42bd-a441-1c8ca10dba02

 

 

 

  • 로그인 이후 메인 페이지 접근 가능

 

 

 

localhost:8080/logout

  • 일단 로그아웃 처리

 

 


SecurityConfiguration 클래스 생성

 

SecurityConfiguration.java

  • 로그인을 관리할 클래스 생성

 

 

SecurityConfiguration.java

Override (Ctrl + O):

  • configure

 

 


메인 페이지에서 '회원정보' 링크 추가

 

index.html

  • 메인 페이지에서 '회원정보' 링크 추가

 

 

 

  • '회원정보' 링크가 추가된 모습

 

 

 

  • 메인페이지의 링크를 맵핑
  • 주소는 /member/info

 

 


'회원정보' 페이지 생성

 

info.html

 

 

 

localhost:8080/member/info

  • 접속 권한 획득 여부 확인 목적으로 특별한 기능이 없는 페이지

 

 


접속 권한 지정

 

SecurityConfiguration.java

로그인 하지 않은 상태에서 접근 가능한 페이지

  • 메인페이지(/)
  • 회원가입페이지(/member/register)
  • 계정인증페이지(/email-auth)

 

 

 

SecurityConfiguration.java

 

 

UserAuthenticationFauilerHandler.java

  • 로그인페이지(/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);

 

 

 

MemberService.java

  • UserDetailService를 상속받음

 

 

 

  • Override: loadUserByUsername

 

 

 

MemberServiceImpl.java

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);
}

 

 

 

SercurityConfiguration.java

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);
}

 

 

 

MemberController.java

  • 로그인페이지 맵핑

 

 


로그인페이지 구현

 

login.html

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>

 

 

 

localhost:8080/member/login

  • 생성된 로그인 페이지

 

 


로그인페이지 테스트

  • 이메일, 비밀번호 입력 - 로그인 실패

 

 

 

SecurityConfiguration.java

  • 일단 테스트를 위하여 http.csrf().disable()
http.csrf().disable();

 

 

  • 이메일, 비밀번호 입력 - 로그인 실패

 

 

 

  • Post 방식을 지원하지 않음

 

 

 

MemberController.java

  • Get과 Post방식을 모두 지원하는
  • RequestMapping으로 변경
@RequestMapping("/member/login")
public String login() {

return "member/login";
}

 

 

 

login.html

  • 로그인 실패 시 에러메시지를 노출할 부분 추가
<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);

 

 

 

localhost:8080/member/register

  • 다시 회원가입 (비밀번호 '1111' 입력)

 

 

 

  • 일단 회원가입은 성공

 

 

 

  • DB에서도 password가 암호회된 모습을 확인할 수 있음

 

 


권한에 따른 페이지 접근 테스트

 

  • 가입시킨 계정으로 로그인

 

 

 

  • 메인 페이지에서 회원 정보 페이지 이동 : 성공

 

 


로그아웃 기능 구현

 

SecurityConfiguration.java

http.logout()
        .logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
        .logoutSuccessUrl("/")
        .invalidateHttpSession(true);

 

 


로그아웃 기능 테스트

 

  • 로그인 상태에서 로그아웃 링크 클릭
  • 로그아웃 상태에서 회원정보 링크 클릭

 

 

 

  • 로그인 페이지에 접근되나,
  • 로그아웃 상태이므로 로그인 페이지 노출

 

 


계정 활성화 이전 회원정보 페이지 접근 제한

 

MemberServiceImpl.java

if (!member.isEmailAuthYn()) {
    throw new MemberNotEmailAuthException("계정 활성화 이후에 로그인 해주세요.");
}

 

 

 

  • 클래스가 없어서 발생하는 에러

 

 

 

  • 클래스 생성

 

 

 

MemberNotEmailAuthException.java

  • 에러가 발생되지 않도록 RuntimeException을 상속받음

 

 

 

UserAuthenticationFailureHandler.java

  • 로그인 실패한 경우 에러메시지 노출 구현
@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("로그인에 실패하였습니다.");

 

 


계정 활성화 이전 회원정보 페이지 접근 테스트

 

  • 계정 활성화 이전 회원 정보 페이지에서 로그인하면
  • 계정 활성화를 선행하라는 에러 메시지 노출

 

 


반응형