인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

kwj3591님의 프로필 이미지
kwj3591

작성한 질문수

스프링 시큐리티

4) DB 연동 인증 처리(1) : CustomUserDetailsService

Security 듣는 중 드리는 질문

작성

·

384

·

수정됨

0

매번 여쭤보기보다 누적했다가 한번에 질문드리는게 더 좋을 것 같은데 강의를 일일이 찾기 어려워 한번에 올리겠습니다.

1. MySecurityConfig.class에 붙은 @Configuration은 어떤 일을 하나요?
>> @Configuration은 내부에 있는 @Bean 메소드를 스프링 컨테이너에서 싱글톤으로 관리될 수 있게끔 보장해주는 Annotation인 것으로 알고 있습니다. 하지만 Spring Configuration 클래스 내부에는 별다른 Bean 등록 객체가 없는 것 같은데, 추상적으로 [Spring에게 이 Security Configuration을 사용할거야]라고 알려주는 것보다 조금 더 무슨 역할을 하는지 궁금해서 질문 드립니다.

 

 

2.UserDetailService, CustomAuthenticationProvider 등을 왜 직접 Custom화 하는건가요?
>> 그 때마다 앱 내에서 사용하는 Authorities들과 [사용자]로 쓰는 객체가 매번 달라서 그렇다고 이해하면 될까요? 가령, 앱에서는 [Member, Account] 등을 사용, Spring 에서는 UserDetails란 인터페이스를 지원

 

 

3. CustomAuthenticationProvider 만 따로 Bean 주입 메소드를 작성해준 이유가 있을까요?

@Bean
public AuthenticationProvider authenticationProvider() {
    return new CustomAuthenticationProvider();
}

>> AuthenticationProvider 역할의 구현체를 이 앱에선 CustomAuthProvider 구현체를 사용할 것이다라는 메소드를 통해 Bean 등록을 해주는 모습입니다. 하지만 CustomUserDetailService 는 @Service Annotation을 통해 바로 컨테이너에 넣었습니다. 이는 @Service 내에 포함되어 있는 @Component로 인해 Bean 등록이 되는 것으로 알고 있습니다. 이 때, CustomAuthenticationProvider는 @Component로 하지 않고 저렇게 메소드로 해주는 이유가 있을까요?

 

 

4. CustomAuthenticationProvider.class 내 authenticate()에 대해서

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

    String username = authentication.getName();
    String credentials = (String) authentication.getCredentials(); // 비번

    AccountContext userDetails = (AccountContext) userDetailsService.loadUserByUsername(username); // UDS에서 알맞은 유저 가져온다 (우리가 만들어준 객체를 사용할 것이라 등록함)

    if(!passwordEncoder.matches(credentials, userDetails.getAccount().getPassword())){
        String msg = "비밀번호 불일치, 인증 실패하였습니다";
        log.error("Error while handling CustomAuthenticationProvider authenticate(): {}", msg);
        throw new BadCredentialsException("Error while handling CustomAuthenticationProvider authenticate():: " + msg);
    }

    // 일치하면 인증 완료, Authentication 온전한 결과를 넣어줘야 한다
    // Form 에서 사용하는 Authentication 객체는 하위 (근데 새로 만들어줌?)
    UsernamePasswordAuthenticationToken authenticatedToken = new UsernamePasswordAuthenticationToken(userDetails.getAccount(), null, userDetails.getAuthorities());

    return authenticatedToken;
}

다음은 강의에서 사용하신 소스코드 입니다! 이전에 해주신 이론 설명을 바탕으로 해당 코드를 작성하면서 생긴 의문은 Authentication authentication 객체입니다. " AuthenticationProvider에서 받은 [인증 전] Authentication 객체와 내보낼 [인증 후] Authentication 객체가 같은 객체이다"라고 해주셨던 것 같은데, 해당 코드 아래에서 Authentication 인증 객체를 새로 생성하는 것 같은데, 왜 그런지 알 수 있을까요?

 

5. Bean 주입 관련

3번 질문과 연관된 질문입니다! 직접적으로 Security 관련이 아닌 Bean 주입 관련한 내용이라... 양해부탁드립니다 ㅠ.ㅠ

다름이 아니라 직접 Bean 주입을 3번과 같이 하는게 아니라 @Component로 한다면, 자동으로 해당 구현객체를 쓴다는 점이 궁금합니다! 가령, 위에 AuthenticationProvider 의 구현체를 CustomAuthentication 으로 사용한다고 직접적으로 Bean 주입을 해줬기 때문에 해당 구현체를 사용하는 것이 이해가 가지만, CustomUserDetailsService 클래스 같은 경우 @Service annotation 으로 빈 등록을 하게 해줬습니다.

이럴 때는 기존에 사용하고 있던 UserDetailsService 구현체는 어떻게 사용하지 않는다는걸 스프링이 알게 될까요? @Service 를 사용하는 것은 자동주입이고, 기존에 Security 가 사용하는 구현체는 수동 주입했을 것 같은데, 이럴 경우 수동 주입이 우선권을 가지게 되지 않나 싶어서 문의드립니다!

 

감사합니다.

답변 1

1

정수원님의 프로필 이미지
정수원
지식공유자

1. MySecurityConfig.class에 붙은 @Configuration은 어떤 일을 하나요?

네 맞습니다.
이 클래스는 설정 클래스로 시큐리티에 필요한 빈을 정의하는 역할을 하게 됩니다.
현재 챕터에서는 빈을 정의하는 것이 없을 수 있는데 일반적으로 여러 빈들을 정의하는 경우가 많습니다.

2.UserDetailService, CustomAuthenticationProvider 등을 왜 직접 Custom화 하는건가요?
반드시 커스텀 할 필요는 없습니다. 다만 커스텀이 필요한 상황이 발생할 경우 예제를 보여드린 것입니다. 그리고 실무에서도 시스템마다 인증하는 방법들이 다를 수 있기 때문에 커스텀 클래스를 만들어 사용하고 있습니다.

3.CustomAuthenticationProvider 만 따로 Bean 주입 메소드를 작성해준 이유가 있을까요?
이건 특별한 이유가 있는 것은 아닙니다. 스프링에서 빈을 정의하는 방법이 몇가지가 있는데 그 중 하나를 선택한 것 뿐입니다. 어떤 방식을 사용하든 상관없이 원하시는 대로 정의하시면 됩니다.

4. CustomAuthenticationProvider.class 내 authenticate()에 대해서?
일반적으로 인증을 완료하게 되면 인증받은 사용자의 정보를 담은 User 객체, 그리고 권한 정보 등을 Authentication 객체에 저장하게 됩니다. 이 때 새로운 Authentication 객체를 생성해서 저장하고 있는데 실제 스프링 시큐리티에서도 새로운 Authentication 객체에 저장하고 있습니다.
Authentication 에 사용자 정보를 저장할 때는 생성자로 값을 전달하고 있는데 인증 받은 이전과 인증 받은 이후의 생성자가 다릅니다.
그래서 인증 받은 이후에는 새로운 생성자로 Authentication 객체를 만들어서 사용한다 보시면 됩니다.
UsernamePasswordAuthenticationToken 의 생성자는 다음과 같습니다.

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
   super(null);
   this.principal = principal;
   this.credentials = credentials;
   setAuthenticated(false);
}


public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
      Collection<? extends GrantedAuthority> authorities) {
   super(authorities);
   this.principal = principal;
   this.credentials = credentials;
   super.setAuthenticated(true); // must use super, as we override
}

위가 인증받기 전이고 아래가 인증받은 이후의 생성자입니다.
그래서 인증 받은 다음에는 두번째 생성자를 사용해서 새로운 Authentication 객체를 생성한다고 보시면 됩니다.

5. Bean 주입 관련
네 일단 시큐리티에서 기본적으로 제공하는 UserDetailsService 구현체는 빈으로 만들지 않습니다.
일반 객체로 생성을 해서 사용하고 있다고 보시면 됩니다.
만약 시큐리티에서 빈으로 먼저 생성이 되어 있는 경우 우리가 직접 커스텀하게 빈으로 또 생성한다면 동일한 빈이 두개이기 때문에 타입 중복 오류가 발생하게 될 것입니다.
만약 그렇다면 빈을 참조할 때 인터페이스 타입이 아닌 구체적인 빈 이름으로 참조해야 할 것입니다.
특별한 경우를 제외하고는 시큐리티에서 객체를 생성할 때 빈으로 생성하기 보다는 일반 객체로 생성하는 경우도 많으니 커스텀하게 빈으로 정의했는데 실행시 특별한 문제가 생기지 않으면 시큐리티에서 빈으로 생성한 동일한 타입이 없다고 보시면 됩니다.

kwj3591님의 프로필 이미지
kwj3591

작성한 질문수

질문하기