본문으로 바로가기

AuthenticationProvider 인터페이스는 화면에서 입력한 로그인 정보와 DB에서 가져온 사용자의 정보를 비교해주는 인터페이스이다. 해당 인터페이스에 오버라이드되는 authenticate() 메서드는 화면에서 사용자가 입력한 로그인 정보를 담고 있는 Authentication 객체를 가지고 있다. 그리고 DB에서 사용자의 정보를 가져오는 건 UserDetailsService 인터페이스에서 loadUserByUsername() 메서드로 구현했다. 따라서 authenticate() 메서드에서 loadUserByUsernmae() 메서드를 이용해 DB에서 사용자 정보를 가져와서 Authentication 객체에서 화면에서 가져온 로그인 정보와 비교하면 된다. AuthenticationProvider 인터페이스는 인증에 성공하면 인증된 Authentication 객체를 생성하여 리턴하기 때문에 비밀번호, 계정 활성화, 잠금 모든 부분에서 확인이 되었다면 리턴해주도록 하자.



1. AuthenticationProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class CustomAuthenticationProvider implements AuthenticationProvider {
    
    @Autowired
    private UserDetailsService userDeSer;
 
    @SuppressWarnings("unchecked")
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        
        String username = (String) authentication.getPrincipal();
        String password = (String) authentication.getCredentials();
        
        CustomUserDetails user = (CustomUserDetails) userDeSer.loadUserByUsername(username);
        
        if(!matchPassword(password, user.getPassword())) {
            throw new BadCredentialsException(username);
        }
 
        if(!user.isEnabled()) {
            throw new BadCredentialsException(username);
        }
        
        return new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
    }
 
    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
    
    private boolean matchPassword(String loginPwd, String password) {
        return loginPwd.equals(password);
    }
 
}
cs

10행: 화면에서 입력한 아이디를 username에 담는다.

11행: 화면에서 입력한 비밀번호를 password에 담는다.

13행: 화면에서 입력한 아이디(username)로 DB에 있는 사용자의 정보를 UserDetails 형으로 가져와 user에 담는다.

15행: 화면에서 입력한 비밀번호와 DB에서 가져온 비밀번호를 비교하는 로직이다. 비밀번호가 맞지 않다면 예외를 던진다.

19행: 계정 활성화 여부를 확인하는 로직이다. AuthenticationProvider 인터페이스를 구현하게 되면 계정 잠금 여부나 활성화 여부등은 여기에서 확인해야 한다.

23행: 계정이 인증됐다면 UsernamePasswordAuthenticationToken 객체에 화면에서 입력한 정보와 DB에서 가져온 권한을 담아서 리턴한다.

27행: AuthenticationProvider 인터페이스가 지정된 Authentication 객체를 지원하는 경우에 true를 리턴한다.

31행: 비밀번호를 비교하는 메서드이다. 맞으면 true를 리턴한다.


계정 관련 체크 여부(19행)에 관해서 언급할 게 있다. 이 부분은 온전히 내 생각일 뿐이기 떄문에 정확한 건 아니다. 좀 더 찾아봐야 할 부분이다. 그리고 난 왜 UserDetailsService 인터페이스를 구현할 때 이걸 궁금해하지 않았는 지 의문이다(ㅋㅋ) UserDetailsService 인터페이스를 구현할 때 비활성화된 계정을 체크하는 로직이 없었다. 근데 비활성화된 계정은 에러로 예외가 던져졌다(!!!) 왜 이걸 궁금해하지 않고 그냥 넘어갔을 까 ㅋㅋㅋ 정확히는 모르지만 DB에서 가져온 사용자의 정보를 UserDetails 인터페이스에 담으면서 계정관련 체크 여부를 알아서 확인하고 예외를 던지는 것 같다.


근데 AuthenticationProvider 인터페이스를 구현하게 됐을 때 문제가 발생했었다. 비활성화된 계정이 예외를 던지지않고 그냥 로그인에 성공해버리는 문제가 생겼었다ㅠㅠ.. AuthenticationProvider 인터페이스를 구현하니까 UserDetailsService 인터페이스에서 계정 관련 체크를 하지 않았다ㅠㅠ AuthenticationProvider 인터페이스를 구현하게 되면서 UserDetailsService 인터페이스는 DB에서 사용자의 정보를 가져오는 역할만 하게 되버린 것 같다. 그래서 UserDetailsService 인터페이스에서 계정 활성화여부를 체크해보려고 했는데 적절한 예외가 없어서 에러 메시지가 제대로 나오지 않았다. 근데 AuthenticationProvider 인터페이스에는 적절한 에러 메시지를 던지는 예외가 있는 걸 확인했다! 유레카! 그래서 내 생각에 AuthenticationProvider 인터페이스와 UserDetailsService 인터페이스를 구현하게 되면 UserDetailsServcie 인터페이스는 DB에서 사용자의 정보를 가져오는 역할을 하고, AuthenticationProvider 인터페이스에서 비밀번호 체크 및 계정과 관련된 각종 체크여부를 확인해야 되는 것 같다. 대부분 계정 체크 부분을 true로 하고 만들어서 그런지 자료가 없었다ㅠㅠ 그냥 내 생각일 뿐이니.. 얼른 정확한 자료를 찾아봐야 겠다ㅠㅠ



2. Security 설정

context-security

1
2
3
4
5
6
7
8
<security:authentication-manager>
    <security:authentication-provider ref="userAuthProvider"/>
    <security:authentication-provider user-service-ref="userService">
    </security:authentication-provider>
</security:authentication-manager>
        
<bean id="userService" class="tody.common.service.CustomUserDetailsService"/>
<bean id="userAuthProvider" class="tody.common.service.CustomAuthenticationProvider"/>
cs

02행과 08행이 추가되었다. AuthenticationProvider 인터페이스를 구현한 CustomAuthenticationProvider 클래스를 등록하는 부분이다.


<authentication-provider> 태그는 여러 요소를 사용할 수 있는데, 여러 요소를 사용할 경우 순서대로 진행된다. 사실 어떤걸 먼저 선언하든 로그인 결과에는 영향을 주지는 않는다. 하지만 UserDetailsService 인터페이스가 먼저 선언되서 사용될 경우 에러 메시지가 제대로 나오지 않는다. 정확히는 에러 메시지 프로퍼티를 사용할 수 없다. 이 부분에 대해선 정확히 잘 몰라서 설명하지 못하지만(ㅠㅠ) UserDetailsService 인터페이스가 먼저 실행되면 AuthenticationProvider 인터페이스를 구현할 이유가 없지 않을까? 싶다..ㅋㅋㅋㅋ



3. 실행

비밀번호가 틀릴 경우, 비밀번호가 맞지 않다는 에러 문구가 뜨고 로그인에 실패할 것이다. 물론 비밀번호를 맞게 한다면 당연히 로그인에 성공해야 한다. 이렇게 비밀번호 비교 확인이 완벽하게 이루어진다면 AuthenticationProvider 인터페이스도 완벽하게 구현된 것이다! 휴! 뭔가 어려워보였지만 인터페이스에 맞는 기능들을 생각하고 구현한다면 그리 어렵지만은 않다.


GitHub 

그리고 지금까지 개인적인 생각으로는 AuthenticationProvider 인터페이스까진 구현하지 않아도 충분히 사용할 수 있을 거 같다 ㅋㅋ