Hello, Freakin world!

[스프링 시큐리티] Username/Password 인증 구현하기 본문

Spring boot/Security

[스프링 시큐리티] Username/Password 인증 구현하기

johnna_endure 2021. 4. 17. 19:04

시나리오

 

- 인증 기능을 구현한다.

- 로그인 화면에서 인증 요청을 받는다.

- 인증 요청 정보를 Member 데이터베이스에 저장된 정보와 대조해 인증한다.

 


1. 로그인 화면

 

출처 : https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-authentication-form

스프링 시큐리티에 form 로그인 방식 설정을 해두면 위와 같이 동작합니다.

보호된 자원에 접근하는 경우, AccessDeniedException이 발생합니다. ( 폼 로그인 설정이 없을 경우 단순하게 403 Forbidden 에러 페이지를 보여줍니다.)

SecurityFilterChain에 등록되어 있는 예외처리 필터에서 AccessDeniedException을 캐치해 로그인 화면으로 클라이언트를 리다이렉트 시킵니다.

 

form 로그인을 설정하는 코드는 아래와 같습니다.

...

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            	.antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin(form -> form
                        .loginPage("/login")
                        .defaultSuccessUrl("/success")
                );
    }
}

커스텀 로그인 페이지를 구현하는 경우 GET /login 에 해당하는 엔드포인트도 컨트롤러를 이용해 작성해줘야 됩니다.


2. UsernamePasswordAuthenticationFilter

 

Username/Password 방식의 인증은 이 필터에서 걸러져? 시작됩니다.

정해진 흐름이 있으니 한 번 살펴보겠습니다.

 

사용자가 로그인 폼에서 정보를 입력하고 POST /login 으로 요청을 날립니다. (POST /login 의 핸들러는 스프링 시큐리티가 제공합니다.)

요청 정보는 UsernamePasswordAuthenticationToken 이라는 객체에 담김니다. UsernamePasswordAuthenticationToken는 Authentication 인터페이스의 구현 객체입니다. 인증 방식에 따라 여러 구현 객체들이 있습니다.

UsernamePasswordAuthenticationToken는 Authentication 인터페이스의 구현 객체입니다. 인증 정보를 담는 DTO 역할의 객체라고 생각하시면 됩니다.

AuthenticationManager 는 UsernamePasswordAuthenticationToken에 담김 정보를 이용해 인증을 수행할 수행할 객체들을 담는 컨테이너입니다. 이 인증을 수행할 객체들을 AuthenticationProvider 라고 합니다.

AuthenticationProvider 에서 인증에 성공하면 4번으로, 실패하면 3번으로 분기됩니다.

 

실패할 경우, SecurityContextHolder가 클리어되고, RememberMeService가 등록됐다면 loginFailure 콜백 메서드가 실행됩니다.

성공할 경우에도 SecurityContextHolder에 인증 정보가 저장되는 등의 작업이 수행됩니다.


3. DaoAuthenticationProvider

 

처음의 요구 사항에서 우리는 이미 저정된 데이터베이스를 기반으로 인증을 수행하고 싶습니다.

이 경우에 DaoAuthenticationProvider를 사용할 수 있습니다.

 

3,4번을 제외하고 위 그림과 동일합니다.

UserDetailService는 인증을 수행하기 위해 프로바이더에게 인증 정보를 제공할 객체입니다.

우리는 DB의 정보를 이용해 인증을 수행하기로 했으므로 여기세 MemberRepository 의존성이 추가되겠네요.

PasswordEncoder는 말그대로 패스워드를 암호화하는 객체입니다.

스프링 시큐리티는 인증을 수행한 후 Authentication 객체에서 암호를 바로 지웁니다.

사실, 인증이 끝난 시점에서 패스워드를 계속 들고 있을 이유도 없죠. 하지만 특정 용도로 패스워드를 AuthenticationManager에 간단한 설정 eraseCredentials(true)로 가능합니다. 패스워드 인코더도 필요하겠고요.

 

 

큰 그림을 어느 정도 그려봤으니 구현을 시작하겠습니다.


UserDetailService 구현

 

...

@RequiredArgsConstructor
@Service
public class CustomUserDetailService implements UserDetailsService {

    private final MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Member member = memberRepository.findByName(username)
                .orElseThrow(() -> new UsernameNotFoundException("not found " + username));
        UserDetails userDetails = User.builder()
                .username(member.getName())
                .roles(member.getRole())
                .password(member.getPassword())
                .passwordEncoder(p -> createDelegatingPasswordEncoder().encode(p))
                .build();
        return userDetails;
    }
}

DB에 저장된 데이터를 기반으로 프로바이더에게 전달합니다. 반횐된 UserDetail은 Authentication의 Object principle 필드 변수에 할당됩니다. 보안 분야에서 principle은 클라이언트를 나타내는 주체라는 의미로 꽤 다양하게 사용됩니다.

인증 요청에서 username을 나타내기도 하고, UserDetail 처럼 클라이언트의 정보 더미들을 말하기도 합니다.

 

 

DaoAuthenticationProvider 등록

WebSecurityConfig.class

...

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailService userDetailService;

	...
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
//        auth.eraseCredentials(false);
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(createDelegatingPasswordEncoder());
        daoAuthenticationProvider.setUserDetailsService(userDetailService);
        return daoAuthenticationProvider;
    }

}

프로바이더를 생성하고 인증 매니저에 등록해줍니다.

 

이걸로 모든 설정이 끝났습니다.

 

전체 코드를 첨부하면서 마무리하겠습니다.

 

 

 

johnna-endure/spring-security-authentication-exam

Contribute to johnna-endure/spring-security-authentication-exam development by creating an account on GitHub.

github.com

 

 

Comments