인증(Authentication) 로직 구현하기

Nicky·2024년 2월 16일
0
post-thumbnail

인증 절차

본격적인 코드 구현 전에 Spring Security의 인증 절차에 대해 알아보자.

  • 웹 요청(로그인 요청)이 들어오면 AuthenticationFilter에서 로그인 정보(이름, 비밀번호)를 통해 UsernamePasswordAuthenticationToken을 생성
  • AuthenticationManager는 검증 단계를 총괄하는 클래스인 AuthenticationProvider에게 인증 처리를 위임(토큰 전달).
  • AuthenticationProvider는 토큰 정보와 일치하는 사용자를 DB에서 조회(userDetailsServiceloadUserByUsername 메서드 이용)
  • PasswordEncoder의 matches()를 통해 비밀번호 일치 여부 확인. 일치 시에 UserDetails를 포함한 Authentication 객체 반환.
  • 인증 성공 시 SecurityContextAuthentication 객체 저장.
  • UsernamePasswordAuthenticationToken: Authentication 구현체. 사용자 이름, 비밀번호로 구성된 토큰.
  • 인증 완료된 이후의 Authentication: 사용자 정보(UserDetails)로 구성됨
  • SecurityContext: 현재 실행 중인 스레드에 연결된 보안 정보를 저장하고 관리

JWT 로그인은 여기에 인증 성공시 토큰을 발급받는 로직을 별도로 추가해야한다!

JwtAuthenticationFilter

우선 인증 필터 역할을 할 JwtAuthenticationFilter 부터 구현하자.
앞서 다뤘듯이 인증 객체를 반환받을 AuthenticationManager를 주입받아야 한다.

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;
    private final SecurityService securityService;

attemptAuthentication

로그인 요청으로부터 사용자 이름, 비밀번호를 가져와 인증 토큰을 생성하고, 이를 통해 Authentication 객체를 받아오는 메서드.

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            try {
                // 요청에서 로그인 정보 가져오기
                ObjectMapper om = new ObjectMapper();
                MemberLoginRequest loginParam = om.readValue(request.getInputStream(), MemberLoginRequest.class);
                // 인증 토큰 생성
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(loginParam.name(), loginParam.password());
                // 인증
                return authenticationManager.authenticate(authenticationToken);
            } catch (IOException e) {
                return null;
            }
    }

successfulAuthentication

인증 성공시 동작하는 메서드로 인증된 Authentication 객체에서 UserDetatil를 꺼내, JWT 토큰 발급, response에 access 토큰 DTO 반환.

    // 인증 성공 시
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
        // authentication에서 userDetails 추출
        CustomUserDetails userDetails = (CustomUserDetails) authResult.getPrincipal();
        // JWT 토큰 발급
        AccessTokenResponse accessTokenResponse = securityService.getAccessTokenResponse(userDetails);
        // response body에 access 토큰 DTO 담기
        responseWriter.writeAccessTokenResponse(response, accessTokenResponse);
    }

UserDetailService

loadUserByUsername

사용자의 이름을 기반으로 사용자 정보를 불러오는 메서드.
UserDetails는 Member 엔티티를 통해 생성.

    // 이름으로 회원 조회
    @Override
    public CustomUserDetails loadUserByUsername(String username) {
        Member member = memberRepository.findByName(username)
                .orElseThrow(() -> new UsernameNotFoundException(MEMBER_NAME_NOT_FOUND.getMessage()));
        return new CustomUserDetails(member);
    }

UserDetails

사용자의 정보(이름, 비밀번호, 권한 등)들을 갖고 있는 UserDetails.

public record CustomUserDetails(Member member) implements UserDetails {

    // 권환 반환
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<Role> roles = member.getRoles();
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority(role.getRole()))
                .collect(Collectors.toList());
    }

    // 비밀번호 반환
    @Override
    public String getPassword() {
        return member.getPassword();
    }

    // 이름 반환
    @Override
    public String getUsername() {
        return member.getName();
    }

    // 계정 만료 여부
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 계정 잠김 여부
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 자격 증명 만료 여부
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // 계정 활성화 여부
    @Override
    public boolean isEnabled() {
        return true;
    }

}

인증 필터 등록

구현한 인증 필터는 다음과 같이 설정할 수 있다.
인증 필터가 실행될 로그인 요청 경로를 정하고, 로그인 요청 실패시 예외를 처리할 핸들러를 설정할 수 있다.

// 인증 필터 설정
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, securityService, responseWriter);
jwtAuthenticationFilter.setFilterProcessesUrl("/api/security/login");
jwtAuthenticationFilter.setAuthenticationFailureHandler(new JwtAuthenticationFailureHandler(responseWriter));

이제 SecurityConfig의 FilterChain에 등록하도록 하자.

.addFilter(jwtAuthenticationFilter)
profile
코딩 연구소

0개의 댓글

관련 채용 정보