[TIL] JWT 방식의 정보 인증_항해99 Day 37

woonie·2022년 2월 15일
1

TIL

목록 보기
32/64

JWT 강의를 보며 기본 개념을 익히고 우리팀에 적용해보기로 했다.

JWT 로그인 적용인증 처리 (Filter)

  • Filter 는 Client 의 API 요청이 Controller 에 전달되기 전, 사전처리를 하는 영역
    으로 Controller 에 도달하기 전에 인증 처리를 하기 위해 사용
  1. FormLoginFilter : 회원 폼 로그인 요청 시 username / password 인증
    1. POST "/user/login" API 에 대해서만 동작 필요
    2. Client 로부터 username, password 를 전달받아 인증 수행
    3. 인증 성공 시
      1. FormLoginSuccessHandler 통해 JWT 생성
      2. 이후 Client 에서는 모든 API 응답 Header 에 JWT 를 포함하여 인증
    public class FormLoginFilter extends UsernamePasswordAuthenticationFilter {
    final private ObjectMapper objectMapper;

    public FormLoginFilter(final AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
        objectMapper = new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        UsernamePasswordAuthenticationToken authRequest;
        try {
            JsonNode requestBody = objectMapper.readTree(request.getInputStream());
            String username = requestBody.get("username").asText();
            String password = requestBody.get("password").asText();
            authRequest = new UsernamePasswordAuthenticationToken(username, password);
        } catch (Exception e) {
            throw new IllegalArgumentException("username, password 입력이 필요합니다. (JSON)");
        }

        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}
  1. JWTAuthFilter : API 요청 Header 에 전달되는 JWT 유효성 인증

인증 처리(Provider)

  • Filter 가 인증에 필요한 정보를 적합한 클래스 형태로 만들어 Spring Security 에 인증 요청을 함
  • Spring Security 는 Filter 가 요청한 인증 처리를 할 수 있는 Provider 를 찾고, 실제 인증처리는 Provider 에 의해 진행됨
  1. FormLoginAuthProvider
    • Client 에서 전달한 ID/PW 가 DB 의 ID/PW 와 일치하는지 인증
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
    // FormLoginFilter 에서 생성된 토큰으로부터 아이디와 비밀번호 조회
    String username = token.getName();
    String password = (String) token.getCredentials();

    // UserDetailsService 를 통해 DB에서 username 으로 사용자 조회
    UserDetailsImpl userDetails = (UserDetailsImpl) userDetailsService.loadUserByUsername(username);
    if (!passwordEncoder.matches(password, userDetails.getPassword())) {
        throw new BadCredentialsException(userDetails.getUsername() + "Invalid password");
    }

    return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }
  • 인증 성공 시 → FormLoginSuccessHandler 에서 응답 Header 에 JWT 포함

public class FormLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    public static final String AUTH_HEADER = "Authorization";
    public static final String TOKEN_TYPE = "BEARER";

    @Override
    public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response,
                                        final Authentication authentication) {
        final UserDetailsImpl userDetails = ((UserDetailsImpl) authentication.getPrincipal());
        // Token 생성
        final String token = JwtTokenUtils.generateJwtToken(userDetails);
        response.addHeader(AUTH_HEADER, TOKEN_TYPE + " " + token);
    }

}
  1. JWTAuthProvider
    • Client 의 Header 로 전달된 JWT 가 유효한지 검증
@Override
public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {
    String token = (String) authentication.getPrincipal();
    String username = jwtDecoder.decodeUsername(token);

    // TODO: API 사용시마다 매번 User DB 조회 필요
    //  -> 해결을 위해서는 UserDetailsImpl 에 User 객체를 저장하지 않도록 수정
    //  ex) UserDetailsImpl 에 userId, username, role 만 저장
    //    -> JWT 에 userId, username, role 정보를 암호화/복호화하여 사용
    User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("Can't find " + username));;
    UserDetailsImpl userDetails = new UserDetailsImpl(user);
    return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
profile
동료들과 함께하는 개발의 중요성에 관심이 많습니다. 언제나 호기심을 갖고 꾸준히 노력하는 개발자로서 성장하고 있습니다.

0개의 댓글