[Spring Security] 3. 로그인 구현

Beomsu Son·2023년 6월 23일
0
post-thumbnail

1. loginProcessingUrl()


// SecurityConfig.java
.formLogin((formLogin) -> {
		formLogin.loginPage("/loginForm");

		**// "/login" 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행.
		// 결과적으로 컨트롤러에 따로 "/login"을 구현하지 않아도 괜찮다.
		// 이 로그인 과정에서 필요한 것이 있기 때문에 auth 패키지를 파서 PrincipalDetails 을 만들어줘야한다.
		formLogin.loginProcessingUrl("/login");**

		// 로그인이 끝나면 리다이렉트할 url
		formLogin.defaultSuccessUrl("/");
})

SecurityConfig에서 loginProcessingUrl() 메서드를 작성해주면 해당 Url로 요청이 될 시 SpringSecurity가 직접 알아서 로그인 과정을 진행해준다.

결과적으로 /login을 컨트롤러에 따로 구현할 필요가 없는 것!

매우 편리하지만, 이 과정에서 UserDetails가 필요하기에 따로 이를 구현한 클래스를 만들어줘야한다.

2. PrincipalDetails


public class PrincipalDetails implements UserDetails {

    private User user; // 컴포지션

    public PrincipalDetails(User user) {
        this.user = user;
    }

    // 해당 유저의 권한을 리턴하는 곳!
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collect = new ArrayList<>();

        collect.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getRole();
            }
        });
        return collect;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    // 니 계정 만료되지는 않았니?
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 니 계정 잠겨있지 않니?
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 니 계정 비밀번호 갈아낄때 되지 않았니?
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // 니 계정이 활성화 되어있니?
    @Override
    public boolean isEnabled() {
        // 우리 사이트에서 1년동안 로그인을 안하면 휴면 계정으로 변환하기로 했다면?
        // 현재 시간 - 마지막 로그인 시간으로 계산 => 1년 초과하면 false 로 return.
        // 나머지 비어있는 함수들도 다 비슷하게 구현해주면 된다.
        return true;
    }
}

다음과 같은 클래스를 만들어 주었다.

아까 1단계에서 UserDetails가 필요하다고 했는데, 그 이유를 설명하자면 다음과 같다.

  • Spring Security가 /login으로 요청이 오면 로그인을 알아서 진행시킨다.
    • 세부 과정은 이렇다.
      1. loginProcessingUrl에 넣어준 url 요청이 온다.
      2. UserDetailsService 타입으로 IoC 되어있는 객체를 찾는다.
      3. 해당 객체 안에 구현된 loadUserByUsername 함수 실행.
      4. UserDetails 객체 리턴.
      5. Spring Security가 받은 UserDetails 객체를 이용해 Authentication 객체를 만듦.
      6. Authentication 객체를 이용해 Security Session을 만듦.
      7. Security ContextHolder에 저장하여 세션을 저장.
  • 여기서 보면 중요한 건 저장하려는 세션이 Security Session이라는 것이다.
  • Security Session은 Authentication 객체를 필요로 하고, Authentication 객체는 UserDetails가 필요하다.
  • 그래서 이런 로그인을 구현하기 위해 UserDetails를 만들어 주는 것이다.
  • Security Session ⇒ Authentication ⇒ UserDetails

그렇기 때문에 이런 로그인 과정에서 사용할 UserDetails 를 구현한 구현체를 만들었는데 이것이 **PrincipalDetails** 클래스인 것이다.

코드를 보면 주석으로 각 함수들이 어떤 역할인지 직관적으로 나타냈다.

이것들을 어떤 용도로 사용해야할지 정도는 쉽게 감이 올 듯하다.

3. PrincipalDetailsService


@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    // 시큐리티 session => Authentication => UserDetails
    // 시큐리티 session(내부 Authentication(UserDetails(PrincipalDetails)))
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);

        if(user != null) {
            return new PrincipalDetails(user);
        }
        return null;
    }
}

2단계에서 언급했듯이, loginProcessingUrl()에 넣어준 url로 요청이 들어오면 알아서 Security가 로그인을 처리해준다고 했다.

그때 IoC 컨테이너에 있는 UserDetailsService 구현체의 loadUserByUsername() 으로부터 시작하여 세션을 만든다.

이 부분까지는 프로그래머가 직접 만들어야하는 부분이다. 간단하게 구현해보면 위의 코드와 같다.

User 엔티티를 username으로 찾고, 이것이 null이 아니라면 PrincipalDetails 객체를 user 객체를 이용해 생성해주고, 이를 리턴하는 식이다.

profile
생각날 때마다 기록하기

0개의 댓글