이 블로그는 개인의 공부 목적으로 작성된 블로그입니다. 왜곡된 정보가 포함되어 있을 수 있습니다.
인증
지금까지 UserDetail을 활용하여 사용자를 정의하고 UserDetailsService와 UserDetailsManager를 이용해서 사용자에 관리, 접근할 수 있음을 알아보았다. 이번에는 AuthenticationProvider를 통해 어떻게 인증이 이루어지는 살퍼보고 SecurityContext에 어떻게 진입하게 되는지 알아보자
Authentication 인터페이스
Authentication 인터페이스는 인증 요청 이벤트로서 인증 요청및 엔티티의 세부 정보를 가지고 있다. 실제로 이전의 AuthenticationProvider에서 Authentication 구현체를 통해 인증 요청을 처리하는 것을 확인 할 수 있었다.
https://docs.spring.io/spring-security/reference/features/authentication/index.html
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
Authentication은 Principal 인터페이스를 상속했다는 점에서 기존 Principle 인터페이스가 제공하는 getName 이외에도 getCredentials와 같이 다른 요구사항을 제공하고 있다.
AuthenticationProvider
authenticationProvider 는 인증 논리를 처리한다. 만약 원하는 인증 논리를 등록하고 싶은경우 authenticationProvider를 구현해야한다.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
AuthenticationProvider 인터페이스 내부에는 인증결과를 Authentication으로 반환하는 authenticate 메소드와 supports 메소드가 있다. 이때 supports 메소드의 경우, authentication이 인증형식이 허락되어 있으면 true를 반환한다. 이게 무슨 말이냐면 예를들어 다양한 인증 방식이 존재한다고 가정하자.일반적은 아이디 비번(A), 구글 Oauth(B), 네이버 Oauth(C) 이렇게 3가지 방법이 존재하고 모두 authenticate에 구현이 되어있다. 그러나 만약에 문제가 발생하여 A방법 로그인 방법을 제한해야한다고 하는 경우, support에서 이를 처리하면 허락되어 있지 않은 인증 방식이 아니라고 false를 반환하게 된다.(실제 개발에서 생각한다면 확장을 위해 support로 막아두는 방법도 사용할 수 있을 것 같다)
실습
AuthenticationProvider를 구현하여 인증 기능을 구현해보자
public class User implements UserDetails{
private final String username;
private final String password;
private final String authority;
public User(String username, String password, String authority) {
this.username = username;
this.password = password;
this.authority = authority;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(()->authority);
}
//생략
}
UserDetails의 구현체 User클래스로 사용자를 정의한다.
@Component
public class InMemoryDetailsService implements UserDetailsService {
private final List<UserDetails> users;
public InMemoryDetailsService(List<UserDetails>users){
this.users=users;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return users.stream()
.filter(
u->u.getUserName().equals(username)
)
.findFirst()
.orElseThrow(
()->new UsernameNotFoundException("User not found")
);
}
}
UserDetailsService 구현체인 InMemoryDetailsService 클래스로 loadUserByUsername을 재정의하여 user에 알맞는 userDetail를 반환하도록 한다.
@Component
public class PlainTextPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.equals(encodedPassword);
}
}
PasswordEncoder의 구현체인 PlainTextPasswordEncoder로서 원래는 encode,matches에 암호화를 추가해야하지만 raw 문자열 비교로 구현했다.
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Autowired
public CustomAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
this.userDetailsService = userDetailsService;
this.passwordEncoder = passwordEncoder;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username=authentication.getName();
String password=authentication.getCredentials().toString();
UserDetails u=userDetailsService.loadUserByUsername(username);
if(passwordEncoder.matches(password,u.getPassword())){
return new UsernamePasswordAuthenticationToken(
username,
password,
u.getAuthorities()
);
}
else{
throw new BadCredentialsException("Something wrong");
}
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication);
}
}
AuthenticationProvider 구현체 CustomAuthenticationProvider클래스로 authenticate메소드를 재정의 하였다.
@Configuration
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(){
UserDetails u= new User("jiwon","12345","read");
List<UserDetails> users=List.of(u);
return new InMemoryDetailsService(users);
}
@Bean
public PasswordEncoder passwordEncoder(){
return new PlainTextPasswordEncoder();
}
@Bean
public CustomAuthenticationProvider customAuthenticationProvider(){
return new CustomAuthenticationProvider(userDetailsService(),passwordEncoder());
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.authenticationProvider(customAuthenticationProvider())
.httpBasic(withDefaults());
return http.build();
}
}
SecurityConfig 클래스로 이때까지 정의한 구현체의 의존관계를 주입하고 filterChain을 통한 http 처리를 구현했다.(UserDetail 인스턴스를 생성하여 기본 사용자 정보를 넣었다. 이후에 반응성으로 사용자 정보에 접근할 수 있는 방법을 알아볼 예정이다.
통신 성공!
'보안' 카테고리의 다른 글
[Spring Security in action] 권한부여 (0) | 2024.01.20 |
---|---|
[Spring Security in action] SecurityContext (0) | 2024.01.18 |
[Spring Security in action] 사용자 관리 (0) | 2024.01.15 |
[Spring Security in action] 스프링 시큐리티 시작하기 (0) | 2024.01.11 |
[보안] 전자 지불 시스템, 전자 화페 (0) | 2023.12.08 |