포스트

04.로그인성공 & JWT발급

로그인 성공 시 JWT 발급

  • 이전에 만들어 놓은 로그인필터의 로그인 성공 메소드에 토큰 생성 코드 작성
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import com.example.userservice.dto.MyUserDetails;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;

@RequiredArgsConstructor
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
// UsernamePasswordAuthenticationFilter는 원래 HttpSecurity#formLogin에서 진행을 했는데
// jwt를 위해 formLogin을 disable 시켰기 때문에 개발자가 구현해줘야 한다

  private final AuthenticationManager authenticationManager;

  // JWTUtil 클래스를 통해서 로그인이 성공했을 때 토큰 발급
  // SecurityConfig에 가서 LoginFilter 생성자에 JWTUtil 파라미터도 추가해주어야 한다
  private final JWTUtil jwtUtil;

  @Override
  // 미승인 Authentication 생성하는 메소드
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException{

    // 요청을 가로채서 username과 password를 추출
    String username = obtainUsername(request);
    String password = obtainPassword(request);

    System.out.println("username : " + username);

    // 스프링 시큐리티에서 username과 password를 검증하기 위해서는 token에 담아야 한다
    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null);
                                                                                        // username, password, role
    // token에 담은 검증을 AuthenticationManager로 전달
    return authenticationManager.authenticate(authToken);
  }

  @Override
  protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
    MyUserDetails myUserDetails = (MyUserDetails) authentication.getPrincipal();
    String username = myUserDetails.getUsername();

    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
    GrantedAuthority auth = iterator.next();

    String role = auth.getAuthority();

    String token = jwtUtil.createJwt(username, role, 60*60*10L);

    response.addHeader("Authorization", "Bearer " + token);
    // 위의 HTTP인증방식은 RFC 7235 정의에 의해서 공식적으로 형식을 지정해줌
    // Authorization: 타입 인증토큰
    // Authorization: Bearer 인증토큰string
    // "Bearer "의 경우 공백을 끝에 붙여줘야 함
  }

  @Override
  protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
    response.setStatus(401);
  }
}
  • JWTUtil 클래스의 토큰 생성 메소드를 이용하여 토큰을 생성하여야 하기 때문에 JWTUtil를 의존성 주입 해준다.
  • SpringSecurity의 로그인필터 등록 시 생성자를 만들며 등록하였기 때문에, 생성자를 만드는 곳에도 파라미터를 추가해준다.
1
2
3
4
LoginFilter loginFilter = new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil);
        loginFilter.setFilterProcessesUrl("/api/users/loginProc");
        http
                .addFilterAt(loginFilter, UsernamePasswordAuthenticationFilter.class);
  • Authentication에서 유저 정보를 MyUserDetails 형태로 가져와서 username과 role의 값을 추출하고
  • 토큰 생성 시 넣어준다.
  • 토큰 생성 시 username, role, 유효기간을 전달하여 생성한다.
  • 생성된 토큰을 response의 header에 추가하여 준다.
  • 추가되는 양식은 response.addHeader(“Authorization”, “Bearer “ + token); 하여야만 한다.
  • Bearer의 뒤에는 공백이 하나 있다.
  • 로그인 실패 시 header의 상태값을 401로 보낸다.

  • 생각 정리
  • formLogin을 통한 세션 저장 시
    1. UsernamePasswordAuthenticationFilter(간략히 UPAF)는 미인증된 UsernamePasswordAuthenticationToken을(UPAT)을 만들고,
    2. AuthenticationManager를 구현한 ProviderManager에게 UPAT을 전달하고, ProviderManager는 AuthenticationProvider에게 전달
    3. AuthenticationProvider는 UserDetailService에게 username을 보내고 UserDetailService는 Repository를 통해 DB에서 유저 정보를 찾는다
    4. 찾은 유저 정보를 UserDetail 객체로 만들어 ProviderManager에게 전달하고 최종적으로 UsernamePasswordAuthenticationFilter는 DB에서 찾은 유저정보 UserDetail의 정보를 비교해 실제 인증 처리를 진행
    5. 인증이 완료되면 SecurityContextHolder - SecurityContext - Authentication에 저장

  • JWT
  • LoginFilter는 formLogin 시에 UsernamePasswordAuthenticationToken을 만드는 UsernamePasswordAuthenticationFilter 의 역할을 대신한다.
  • 따라서 UPAF가 하던 로그인 성공 시 세션에 넣는 것 대신에 토큰을 생성하여 등록
  • JWTUtil은 UPAF가 세션에 등록하기 위해 진행하던 일들을 하는 애, 그래서 username을 확인하고, 역할을 확인하고, 토큰 만료시간을 확인할 수 있는 것 같다.
  • 또한 세션등록 정보 대신에 토큰 생성 구현 로직을 가지고 있는 것 같다
참고 사이트

개발자 유미