본문 바로가기

개인프로젝트(수강프로그램)

스프링 시큐리티를 이용한 로그인 & 로그아웃 구현

- SecurityConfiguration

 

회원정보는 회원이 로그인 한 후에만 보여져야 할 때 시큐리티 설정

 

현재 등록과 이메일권한 페이지만 허용해주었고 나머지는 허용하지 않았기에 다른 페이지는 로그인 창이 뜬다

@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //페이지 권한설정
        http.authorizeHttpRequests()
                .antMatchers(
                        "/"
                        , "/member/register"
                        , "/member/email-auth"
                    )
                        .permitAll(); // 지정한 권한을 모두 허용하겠다

        http.formLogin() // 로그인 관련설정
                .loginPage("/member/login")
                .failureHandler(null) // 로그인 실패시 처리하는 곳
                .permitAll();

       super.configure(http);
    }

 

 

- UserAuthenticationFailureHandeler

 

failureHandler를 구현하기 위한 클래스

 

public class UserAuthenticationFailureHandeler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        // 에러가 발생했으니 request에 에러 메세지를 넣을 거임
        setUseForward(true);
        setDefaultFailureUrl("/member/login?error=true");
        request.setAttribute("errorMessage", "로그인 실패!");


        super.onAuthenticationFailure(request, response, exception);
    }
}

그 후

- SecurityConfiguration 에 failureHandler 로그인 실패처리 메서드를 넣고 빈을 등록해준다

@Bean
UserAuthenticationFailureHandeler getFailureHandeler(){
    return new UserAuthenticationFailureHandeler();
}

@Override
protected void configure(HttpSecurity http) throws Exception {

    //페이지 권한설정
    http.authorizeHttpRequests()
            .antMatchers(
                    "/"
                    , "/member/register"
                    , "/member/email-auth"
                )
                    .permitAll(); // 지정한 권한을 모두 허용하겠다

    http.formLogin() // 로그인 관련설정
            .loginPage("/member/login")
            .failureHandler(getFailureHandeler()) // 로그인 실패시 처리하는 곳
            .permitAll();

   super.configure(http);
}

 

- UserDetails

 

UserDetailsService 인터페이스는 DB에서 유저 정보를 가져오는 역할을 한다

Spring Security에서 사용자의 정보를 담는 인터페이스는 UserDetails 인터페이스이다. 우리가 이 인터페이스를 구현하게 되면 Spring Security에서 구현한 클래스를 사용자 정보로 인식하고 인증 작업을 한다.

 

 


먼저 MemberService에서 UserDetailService를 상속받고 구현해준다

public interface MemberService extends UserDetailsService {

    boolean register(MemberRequestDto requestDto);
    // uuid에 해당하는 계정을 활성화
    boolean emailAuth(String uuid);

}

 

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        Optional<Member> optionalMember = memberRepository.findById(username);
        if(!optionalMember.isPresent()){
            throw new UsernameNotFoundException("회원정보가 존재하지 않습니다");
        }
        Member member = optionalMember.get();

        // return 객체의 파라미터가 이름, 비밀번호 , role인데 role은 직접 설정해야함
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

        grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));

        return new User(member.getUserName(), member.getPassword(),grantedAuthorities);
    }
}

 

 

스프링 시큐리티에서 원하는 것은 id,pw,role 이니 해당값을 반환타입인 User에 담아서 리턴해준다

 

- SecurityConfiguration

마지막으로 passwordEncoder 생성

 

// MemberService를 시큐리티에 알려주기 위한 메서드
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    auth.userDetailsService(memberService)
            .passwordEncoder(getPasswordEncoder());

    super.configure(auth);
}

 


 

현재 로그인까지 구현되었지만 막상 서버를 실행하고 로그인을 하면 로그인이 되질 않는다

 

그 이유는 비밀번호를 암호화를 시켰기 때문에 현재 설정된 번호인 123을 쳐도 처리가 되지 않는것

그렇기에 SecurityConfiguration에서 썻던 방식대로 service단에 저장을 해야한다

 

@Override
public boolean register(MemberRequestDto requestDto) {

    Optional<Member> validation = memberRepository.findById(requestDto.getUserId());

    if(validation.isPresent()){
        return false;
    }
    
    // BCrypt로 암호화하는 과정
    String encPassword = BCrypt.hashpw(requestDto.getPassword(), BCrypt.gensalt());
    String uuid = UUID.randomUUID().toString();


    Member member = Member.builder()
            .userId(requestDto.getUserId())
            .userName(requestDto.getUserName())
            .phone(requestDto.getPhone())
            .password(encPassword)
            .regDt(LocalDateTime.now())
            .emailAuthYn(false)
            .emailAuthKey(uuid)
            .build();

    memberRepository.save(member);

 

- MemberController

 

@RequestMapping("/member/login")
public String login(){
    return "/member/login";
}

 

+) RequestMapping(요청 매핑) 이란?

요청이 왔을 때 어떤 컨트롤러가 호출이 되어야 하는지 알려주는 지표 같은 것이다.

 

@RequestMapping(value = "/hello-basic"

이렇게 매핑을 하면 localhost:8080/hello-basic으로 url을 입력했을 경우에 이것에 해당하는 메서드가 실행된다.

 

 

 


 

로그아웃

- SecurityConfiguration

@Override
protected void configure(HttpSecurity http) throws Exception {

    http.csrf().disable();

    http.logout()
            .logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
            .logoutSuccessUrl("/") // 로그아웃되면 이동하는 페이지
            .invalidateHttpSession(true); // 세션초기화

 

 


이메일 인증을 받지 못한 사람 구분

 

현재 이메일 부분이 통과하지 않았음에도 로그인이 되는 상황 발생

- MemberServiceImpl

 

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

    Optional<Member> optionalMember = memberRepository.findById(username);
    if(!optionalMember.isPresent()){
        throw new UsernameNotFoundException("회원정보가 존재하지 않습니다");
    }
    Member member = optionalMember.get();

    // 이메일 인증절차에 관한 로직
    // true가 아닐때 예외처리
    if(!member.isEmailAuthYn()){
        throw new MemberNotEmailAuthException("이메일 활성화 이후에 로그인 진행해주세요");
    }
    
    // return 객체의 파라미터가 이름, 비밀번호 , role인데 role은 직접 설정해야함
    List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
    grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));

    return new User(member.getUserName(), member.getPassword(),grantedAuthorities);
}

 

- MemberNotEmailAuthException

 

에러 execption을 만들어준다, 여기서 발생한 에러는 failureHandler로 갈 것이기에 어떤 에러가 발생했는지 작성한다

 

public class MemberNotEmailAuthException extends RuntimeException {
    public MemberNotEmailAuthException(String error) {
        super(error);
    }
}

 

- UserAuthenticationFailureHandeler

 

public class UserAuthenticationFailureHandeler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        String msg = "로그인에 실패!!";

        if (exception instanceof InternalAuthenticationServiceException){
            msg = exception.getMessage();
        }


        // 에러가 발생했으니 request에 에러 메세지를 넣을 거임
        setUseForward(true);
        setDefaultFailureUrl("/member/login?error=true");
        request.setAttribute("errorMessage", "로그인 실패!");


        super.onAuthenticationFailure(request, response, exception);
    }
}

 

이후 로그인이나 회원정보 페이지를 들어가려면 회원가입이 되었다 하더라도 이메일 유저인증을 통해야만 입장할 수 있다