-
Notifications
You must be signed in to change notification settings - Fork 1
JWT 인증
MoonDooo edited this page Mar 11, 2024
·
2 revisions
이 필터에서는 authentication_server로부터 발급한 JWT를 검증하는 것으로 서명을 확인하기 위해 restTemplate
를 이용하여 해당 JWT를 보낸다. 올바른 JWT라면 해당 서버의 user의 데이터베이스 상의 id를 받아 데이터베이스에 저장한다. 이를 통해 해당 인증 서버와 user를 동기화 한다.
@RequiredArgsConstructor
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final static String AUTHENTICATION_PROPERTY = "Authorization";
private final RestTemplate restTemplate;
private final UserRepository userRepository;
@Value("${security.verify-server.uri}")
private String authenticationServerUri;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = request.getHeader(AUTHENTICATION_PROPERTY);
if (jwt != null && jwt.startsWith("bearer ")) {
jwt = jwt.substring(7);
}else{
filterChain.doFilter(request, response);
return;
}
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(AUTHENTICATION_PROPERTY, jwt);
HttpEntity<?> httpEntity = new HttpEntity<>(httpHeaders);
try {
UserIdAndAuthorities userIdAndAuthorities = restTemplate.exchange(authenticationServerUri, HttpMethod.GET, httpEntity, UserIdAndAuthorities.class).getBody();
Check.notNull(userIdAndAuthorities, StatusCode.Internal_Server_Error);
Check.notNull(userIdAndAuthorities.getAuthorities(), StatusCode.Internal_Server_Error);
Check.notNull(userIdAndAuthorities.getUserId(), StatusCode.Internal_Server_Error);
List<GrantedAuthority> grantedAuthorities = userIdAndAuthorities.getAuthorities().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
String userId = userIdAndAuthorities.getUserId();
createUserIfNotFound(userId);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userIdAndAuthorities.getUserId(), null, grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain. doFilter(request, response);
}catch (HttpClientErrorException e){
StatusCodeMessageDto dto = e.getResponseBodyAs(StatusCodeMessageDto.class);
Check.notNull(dto, StatusCode.Internal_Server_Error);
StatusCode stateCode = StatusCode.getStateCode(dto);
Check.notNull(stateCode, StatusCode.Internal_Server_Error);
throw new CustomErrorException(stateCode);
}
}
private void createUserIfNotFound(String userId) {
if (!userRepository.existsById(Long.valueOf(userId))){
userRepository.save(new User(Long.valueOf(userId)));
}
}
}
설정 값로 부터 지정한 헤더 이름으로 JWT를 가져온다. 만약 존재하지 않다면 다음 필터로 보낸다. 만약 JWT가 없다면 해당 사용자는 ANONYMOUS
권한을 얻는다
authentication_serve
와 동일하게 가져온 userId와 권한들은 authentication
에 저장된다. 이후 HandlerMethodArgumentResolver
을 이용하여 @AuthenticationUser
를 통해 파라미터에 주입된다. 코드는 다음과 같다.
@Component
public class AuthenticationUserResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(AuthenticationUser.class)!=null&& parameter.getParameterType().equals(Long.class);
}
@Override
public Long resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return Long.parseLong(authentication.getName());
}
}
@Component
public class AuthenticationOptionalUserResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(AuthenticationUser.class)!=null&& parameter.getParameterType().equals(Optional.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!authentication.isAuthenticated() || "anonymousUser".equals(authentication.getName())) {
return Optional.empty();
}
try {
return Optional.of(Long.parseLong(authentication.getName()));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
}
optional 버전은 익명이어도 접근 가능한 요청에 대해 허용하기 위해서 사용된다.
@DeleteMapping
public ResponseEntity<Result<Boolean>> deleteComment(@AuthenticationUser Long userId, @RequestParam Long commentId){
return Result.isSuccess(commentService.deleteComment(userId, commentId));
}
사용 예는 다음과 같다.
모든 설정
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.formLogin(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.rememberMe(AbstractHttpConfigurer::disable)
.exceptionHandling(ex->ex.authenticationEntryPoint(entryPoint))
.authorizeHttpRequests(auth-> {
auth
.requestMatchers(HttpMethod.GET, "/comment").hasAnyRole(Roles.ANONYMOUS.role(), Roles.OAUTH2_USER.role())
.requestMatchers(HttpMethod.DELETE, "/comment").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers(HttpMethod.POST, "/comment").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers(HttpMethod.PUT, "/comment").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers(HttpMethod.POST, "/comment/like").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/comment/nestedComment").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/comment/nestedComment/like").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/destination/detail").hasAnyRole(Roles.OAUTH2_USER.role(),Roles.ANONYMOUS.role())
.requestMatchers("/destination/search").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/destination/recommend").hasAnyRole(Roles.OAUTH2_USER.role(), Roles.ANONYMOUS.role())
.requestMatchers("/destination/scrap/**").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/destination/route").hasAnyRole(Roles.OAUTH2_USER.role())
.requestMatchers("/post/user").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/post/likedCommented").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/post").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/post/detail").hasAnyRole(Roles.OAUTH2_USER.role(), Roles.ANONYMOUS.role())
.requestMatchers("/post/like/**").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/posts").hasAnyRole(Roles.OAUTH2_USER.role(), Roles.ANONYMOUS.role())
.requestMatchers("/post/scrap/**").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/post/search").hasAnyRole(Roles.OAUTH2_USER.role(), Roles.ANONYMOUS.role())
.requestMatchers("/scrap/delete").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/travelPlan/**").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/blockUser/**").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/nickname/**").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/profile/upload").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/profile").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/profile/original").hasAnyRole(Roles.OAUTH2_USER.role(), Roles.ANONYMOUS.role())
.requestMatchers("/scrap").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/searchHistory").hasRole(Roles.OAUTH2_USER.role())
.requestMatchers("/swagger-ui/**").permitAll()
.requestMatchers("/v3/api-docs/**").permitAll()
.anyRequest().hasAnyRole("OAUTH2_USER", "ANONYMOUS");
})
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(customExceptionHandlerFilter, JwtAuthenticationFilter.class)
.sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
Spring 임경완 |
---|
@ MoonDooo |