728x90
JWT 강의를 보며 기본 개념을 익히고 우리팀에 적용해보기로 했다.
JWT 로그인 적용인증 처리 (Filter)
- Filter 는 Client 의 API 요청이 Controller 에 전달되기 전, 사전처리를 하는 영역
으로 Controller 에 도달하기 전에 인증 처리를 하기 위해 사용
- FormLoginFilter : 회원 폼 로그인 요청 시 username / password 인증
- POST "/user/login" API 에 대해서만 동작 필요
- Client 로부터 username, password 를 전달받아 인증 수행
- 인증 성공 시
- FormLoginSuccessHandler 통해 JWT 생성
- 이후 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);
}
}
- JWTAuthFilter : API 요청 Header 에 전달되는 JWT 유효성 인증
인증 처리(Provider)
- Filter 가 인증에 필요한 정보를 적합한 클래스 형태로 만들어 Spring Security 에 인증 요청을 함
- Spring Security 는 Filter 가 요청한 인증 처리를 할 수 있는 Provider 를 찾고, 실제 인증처리는 Provider 에 의해 진행됨
- 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);
}
}
2. JWTAuthProvider
- Client 의 Header 로 전달된 JWT 가 유효한지 검증
```java
@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());
}
'Spring' 카테고리의 다른 글
[TIL] 항해99 Day 40 (0) | 2024.04.15 |
---|---|
[TIL] 항해99 Day 38 (0) | 2024.04.15 |
JWT 방식의 정보 인증 (0) | 2024.04.12 |
[TIL] CORS에러! 무엇인가? (1) | 2024.04.12 |
MVC 테스트 (1) | 2024.04.12 |