@RestController
@RequestMapping("api/v1/members")
@Slf4j
public class MemberController {
private final MemberService memberService;
@PostMapping("/sign-up")
public ApiResult<MemberDto> signUp(@RequestBody MemberJoinDto joinDto){
log.debug("joinDto : {}", joinDto.toString());
return OK(memberService.createUser(joinDto.getEmail(), joinDto.getPassword()));
}
@GetMapping("/{userId}")
public ApiResult<MemberDto> getMemberInfo(@AuthenticationPrincipal JwtAuthentication authentication,
@PathVariable Long userId){
log.info("/api/v1/members/userId called.");
log.info(authentication.toString());
return OK(this.memberService.getUser(userId));
}
}
Controller 에서 AuthenticationPrincipal 어노테이션 이용해서 인증 정보를 받게 했어.
JwtAuthentication은 JwtAuthenticationFilter 쪽에서
Authorization Header 를 추출해서 Access token 일 경우 verify 를 성공했을 경우
JwtAuthenticationToken 을 생성해서 SecurityContextHolder.getContext().setAuthentication( jwtAuthenticationToken)
이런식으로 지정해줬어.
Member member = Member.builder()
.id(jwt.getClaim("user_id").asLong())
.email(jwt.getClaim("email").asString())
.role(Role.of(jwt.getClaim("role").asString()))
.build();
saveAuthentication(request, member);
}
// log.info("save authentication!.");
JwtAuthenticationToken authenticated = new JwtAuthenticationToken(
new JwtAuthentication(member.getId(), member.getEmail(), member.getAuthorities()),
null,
member.getAuthorities()
);
authenticated.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticated);
}
@ToString
public class JwtAuthentication {
public final Long userId;
public final String email;
public final Collection<? extends GrantedAuthority> authorities;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http.csrf().disable()
.cors().disable()
.formLogin().disable()
.httpBasic().disable()
.headers().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(entryPointUnauthorizedHandler)
.and()
.authorizeHttpRequests()
.requestMatchers(toH2Console()).permitAll()
.requestMatchers(AUTHENTICATION_WHITE_LIST).permitAll()
.requestMatchers("/api/v1/members/**").authenticated()
.anyRequest().permitAll();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
security 설정은 이렇게 해줬고
화이트리스트에 /api/v1/members/sign-up url 만 추가되어있음.
/api/v1/members/{userId} 엔드포인트 쪽에
@AuthenticationPrincipal 어노테이션을 적용해서
JwtAuthentication 을 받아 올 경우에
Postman에서 header 에 Authorization: Bearer 토큰
이렇게 해서 줄때는 잘 동작하는데, 이 Authorization Header를 아예 지우고 리퀘스트를 날리면
엔드포인트 접근을 못할줄 알았는데
엔드포인트 접근한 다음에 authentication.toString() 부분에서 NullPointerException 이 발생하더라고
어노테이션 소스코드 타고 들어가면서 확인해봤는데 문제 파악을 못하겠어서.
// 화면에서 size라는 parameter를 넘기는 case // 혹시 null이 넘어오게 되면 기본형(int)이므로 default 0선언 int size = param.getSize(); // (숫자 / 0)이므로 /0때문에 ArithmeticException 발생 totalPages = (int) (totalCnt / size);
혹시 질문 글에 내용이 부족하면 댓글 달아주면 업데이트해줄게 너무 궁금해
그니까 인증 자체를 통과 못 해서 controller 메소드 실행이 안될 줄 알았다는거지? 맞게 이해한건가? jwtAuthenticationFilter() 가 불러오는 필터는 확실히 거쳐가는 중임?
네 필터는 확실하게 거쳐가고 있습니다.
다만 필터에서 jwt 토큰을 추출했을때 null이면 filterChain.doFilter(request, response) 를 호출한 뒤 return 하고 있습니다.
@AuthenticationPrincipal JwtAuthentication authentication 내가 이 방식으로 인증 구현해본적 없어서 확실하지는 않은데, 이건 인증 정보 없어도 컨트롤러 타지 않음?
@PreAuthorize랑 헷갈린거 아냐?
SecurityFilterChain 쪽에서 /api/v1/members/** 이 부분을 .authencated() 처리해줬다면 filterchain 에서 인증객체를 생성하지 못했을 시 컨트롤러에 도달하지 못해야 하는것 아닌가요? 제가 잘못 이해하고있는것 같기도 하고...
필터 시작지점에서 SecurityContextHolder.clearContext() 해보셈.
토큰이 있건 없건 이걸 거쳐가도록 해보고 다시 테스트 해봐
해당 코드를 JwtAuthenticationFilter의 doFilter 메소드 최상단에 실행 시켜도 NullPointerException이 발생합니다. 컨트롤러에 도달하네요.
올린 코드들에는 별 문제 없어보이는데... Filter 클래스에서 문제있나봄.