Skip to content

Commit

Permalink
Refactor: 네이버 로그인 필터 추가 (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
nohy6630 authored Apr 7, 2024
2 parents 487cffd + 902058a commit 2fb141c
Show file tree
Hide file tree
Showing 16 changed files with 193 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.numberone.backend.config;

import com.numberone.backend.filter.JwtAccessFilter;
import com.numberone.backend.filter.JwtExceptionFilter;
import com.numberone.backend.filter.JwtRefreshFilter;
import com.numberone.backend.filter.SocialAuthenticationFilter;
import com.numberone.backend.filter.*;
import com.numberone.backend.handler.CustomAccessDeniedHandler;
import com.numberone.backend.handler.CustomAuthenticationEntryPoint;
import lombok.RequiredArgsConstructor;
Expand All @@ -13,12 +10,12 @@
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final SocialAuthenticationFilter socialAuthenticationFilter;
private final KakaoAuthenticationFilter kakaoAuthenticationFilter;
private final NaverAuthenticationFilter naverAuthenticationFilter;
private final JwtAccessFilter jwtAccessFilter;
private final JwtRefreshFilter jwtRefreshFilter;
private final JwtExceptionFilter jwtExceptionFilter;
Expand All @@ -40,8 +37,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
"/notification/send-fcm",
"/hello").permitAll()
.anyRequest().authenticated())
.addFilterBefore(socialAuthenticationFilter, ExceptionTranslationFilter.class)
.addFilterBefore(jwtAccessFilter, SocialAuthenticationFilter.class)
.addFilterBefore(naverAuthenticationFilter, ExceptionTranslationFilter.class)
.addFilterBefore(kakaoAuthenticationFilter, NaverAuthenticationFilter.class)
.addFilterBefore(jwtAccessFilter, KakaoAuthenticationFilter.class)
.addFilterBefore(jwtRefreshFilter, JwtAccessFilter.class)
.addFilterBefore(jwtExceptionFilter, JwtRefreshFilter.class)
.exceptionHandling(c -> c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ public class JwtExceptionFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (ExpiredJwtException e){
httpResponseProvider.setErrorResponse(response,HttpServletResponse.SC_FORBIDDEN, EXPIRED_TOKEN);//to do: 프론트와 협의 후 403에서 401로 수정
}catch (JwtException e){
httpResponseProvider.setErrorResponse(response,HttpServletResponse.SC_UNAUTHORIZED, INVALID_TOKEN);
} catch (ExpiredJwtException e) {
httpResponseProvider.setErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, EXPIRED_TOKEN);
} catch (JwtException e) {
httpResponseProvider.setErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, INVALID_TOKEN);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.numberone.backend.handler.SocialAuthenticationFailureHandler;
import com.numberone.backend.handler.SocialAuthenticationSuccessHandler;
import com.numberone.backend.provider.SocialAuthenticationProvider;
import com.numberone.backend.provider.KakaoAuthenticationProvider;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -17,24 +17,23 @@
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.net.http.HttpHeaders;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@Component
public class SocialAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public class KakaoAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final ObjectMapper objectMapper;
private static final String JSON_PARAM = "token";
private static final String REQUEST_URL = "/token/kakao";
private static final String REQUEST_METHOD = "POST";


public SocialAuthenticationFilter(SocialAuthenticationProvider socialAuthenticationProvider,
SocialAuthenticationSuccessHandler socialAuthenticationSuccessHandler,
SocialAuthenticationFailureHandler socialAuthenticationFailureHandler,
ObjectMapper objectMapper) {
public KakaoAuthenticationFilter(KakaoAuthenticationProvider kakaoAuthenticationProvider,
SocialAuthenticationSuccessHandler socialAuthenticationSuccessHandler,
SocialAuthenticationFailureHandler socialAuthenticationFailureHandler,
ObjectMapper objectMapper) {
super(new AntPathRequestMatcher(REQUEST_URL, REQUEST_METHOD));
setAuthenticationManager(new ProviderManager(socialAuthenticationProvider));
setAuthenticationManager(new ProviderManager(kakaoAuthenticationProvider));
setAuthenticationSuccessHandler(socialAuthenticationSuccessHandler);
setAuthenticationFailureHandler(socialAuthenticationFailureHandler);
this.objectMapper = objectMapper;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.numberone.backend.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.numberone.backend.handler.SocialAuthenticationFailureHandler;
import com.numberone.backend.handler.SocialAuthenticationSuccessHandler;
import com.numberone.backend.provider.NaverAuthenticationProvider;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@Component
public class NaverAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final ObjectMapper objectMapper;
private static final String JSON_PARAM = "token";
private static final String REQUEST_URL = "/token/naver";
private static final String REQUEST_METHOD = "POST";

public NaverAuthenticationFilter(NaverAuthenticationProvider naverAuthenticationProvider,
SocialAuthenticationSuccessHandler socialAuthenticationSuccessHandler,
SocialAuthenticationFailureHandler socialAuthenticationFailureHandler,
ObjectMapper objectMapper){
super(new AntPathRequestMatcher(REQUEST_URL, REQUEST_METHOD));
setAuthenticationManager(new ProviderManager(naverAuthenticationProvider));
setAuthenticationSuccessHandler(socialAuthenticationSuccessHandler);
setAuthenticationFailureHandler(socialAuthenticationFailureHandler);
this.objectMapper = objectMapper;
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
Map<String, String> requestBodyMap = objectMapper.readValue(requestBody, Map.class);
String token = requestBodyMap.get(JSON_PARAM);
Authentication authentication = UsernamePasswordAuthenticationToken.unauthenticated(token, null);
return getAuthenticationManager().authenticate(authentication);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.numberone.backend.provider;

import com.numberone.backend.config.AspectConfig;
import com.numberone.backend.exception.notfound.NotFoundMemberException;
import com.numberone.backend.domain.member.entity.Member;
import com.numberone.backend.domain.member.repository.MemberRepository;
import com.numberone.backend.feign.KakaoFeign;
Expand All @@ -11,28 +9,25 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class SocialAuthenticationProvider implements AuthenticationProvider {
public class KakaoAuthenticationProvider implements AuthenticationProvider {
private final KakaoFeign kakaoFeign;
private final MemberRepository memberRepository;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (String) authentication.getPrincipal();
Long socialId;
Long kakaoId;
try {
socialId = kakaoFeign.getUserData(JwtProvider.PREFIX_BEARER + token).getId();
kakaoId = kakaoFeign.getUserData(JwtProvider.PREFIX_BEARER + token).getId();
} catch (Exception e) {
throw new BadCredentialsException("유효하지 않은 OAuth 토큰입니다.");
}
Member member = memberRepository.findBySocialId(socialId)
.orElse(null);
if (member == null)
member = memberRepository.save(Member.of(socialId));
Member member = memberRepository.findByKakaoId(kakaoId)
.orElseGet(() -> memberRepository.save(Member.ofKakao(kakaoId)));
return UsernamePasswordAuthenticationToken.authenticated(member.getId(), null, null);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.numberone.backend.provider;

import com.numberone.backend.domain.member.entity.Member;
import com.numberone.backend.domain.member.repository.MemberRepository;
import com.numberone.backend.feign.NaverFeign;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class NaverAuthenticationProvider implements AuthenticationProvider {
private final NaverFeign naverFeign;
private final MemberRepository memberRepository;


@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (String) authentication.getPrincipal();
String naverId;
try {
naverId = naverFeign.getUserData(JwtProvider.PREFIX_BEARER + token).getResponse().getId();
} catch (Exception e) {
throw new BadCredentialsException("유효하지 않은 OAuth 토큰입니다.");
}
Member member = memberRepository.findByNaverId(naverId)
.orElseGet(() -> memberRepository.save(Member.ofNaver(naverId)));
return UsernamePasswordAuthenticationToken.authenticated(member.getId(), null, null);
}

@Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.numberone.backend.swagger;


import com.numberone.backend.swagger.dto.request.JwtRefreshRequest;
import com.numberone.backend.swagger.dto.request.LoginRequest;
import com.numberone.backend.swagger.dto.response.JwtRefreshResponse;
import com.numberone.backend.swagger.dto.response.LoginResponse;
import com.numberone.backend.swagger.dto.request.RefreshRequestDummy;
import com.numberone.backend.swagger.dto.request.LoginRequestDummy;
import com.numberone.backend.swagger.dto.response.RefreshResponseDummy;
import com.numberone.backend.swagger.dto.response.LoginResponseDummy;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -26,22 +26,22 @@ public class SwaggerDocumentController {// 필터에서 처리하는 api의 경
이후 서버에 API 요청시 이 JWT 토큰을 같이 담아서 요청해야 정상적으로 API가 호출 됩니다.
""")
@PostMapping("/kakao")
public LoginResponse loginKakao(@RequestBody LoginRequest loginRequest) {
public LoginResponseDummy loginKakao(@RequestBody LoginRequestDummy loginRequestDummy) {
return null;
}

// @Operation(summary = "네이버 토큰을 이용하여 서버 JWT 토큰 발급받기", description =
// """
// 네이버 토큰을 body 에 담아서 post 요청 해주세요.
//
// 앞으로 서버 요청 시에 사용할 수 있는 JWT 토큰이 발급됩니다.
//
// 이후 서버에 API 요청시 이 JWT 토큰을 같이 담아서 요청해야 정상적으로 API가 호출 됩니다.
// """)
// @PostMapping("/naver")
// public GetTokenResponse loginNaver(@RequestBody @Valid GetTokenRequest tokenRequest) {
// return tokenService.loginNaver(tokenRequest);
// }
@Operation(summary = "네이버 토큰을 이용하여 서버 JWT 토큰 발급받기", description =
"""
네이버 토큰을 body 에 담아서 post 요청 해주세요.
앞으로 서버 요청 시에 사용할 수 있는 JWT 토큰이 발급됩니다.
이후 서버에 API 요청시 이 JWT 토큰을 같이 담아서 요청해야 정상적으로 API가 호출 됩니다.
""")
@PostMapping("/naver")
public LoginResponseDummy loginNaver(@RequestBody LoginRequestDummy loginRequestDummy) {
return null;
}

@Operation(summary = "만료된 JWT 토큰 갱신하기", description =
"""
Expand All @@ -50,7 +50,7 @@ public LoginResponse loginKakao(@RequestBody LoginRequest loginRequest) {
새로 사용할 수 있는 JWT 토큰이 발급됩니다.
""")
@PostMapping("/refresh")
public JwtRefreshResponse refresh(@RequestBody JwtRefreshRequest jwtRefreshRequest) {
public RefreshResponseDummy refresh(@RequestBody RefreshRequestDummy refreshRequestDummy) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class JwtRefreshRequest {
public class LoginRequestDummy {
private String token;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LoginRequest {
public class RefreshRequestDummy {
private String token;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import lombok.Getter;

@Getter
public class LoginResponse {
public class LoginResponseDummy {
private String accessToken;
private String refreshToken;
private Boolean isOnboarding;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import lombok.Getter;

@Getter
public class JwtRefreshResponse {
public class RefreshResponseDummy {
private String accessToken;
private String refreshToken;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.numberone.backend.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;

@FeignClient(name = "naverFeign", url = "${spring.naver.api-url}")
public interface NaverFeign {
@GetMapping(value = "${spring.naver.token-info-url}")
NaverIdDto getUserData(@RequestHeader(name = "Authorization") String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.numberone.backend.feign;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class NaverIdDto {
private Response response;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class Response {
private String id;
}
}
7 changes: 7 additions & 0 deletions daepiro-common/src/main/resources/application-common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ spring:
map:
client-id: ${KAKAO_MAP_CLIENT_ID}
map-api-url: ${KAKAO_MAP_API_URL}
naver:
api-url: ${NAVER_API_URL}
token-info-url: ${NAVER_TOKEN_INFO_URL}

cloud:
aws:
Expand Down Expand Up @@ -44,6 +47,10 @@ spring:
client-id: ${KAKAO_MAP_CLIENT_ID}
map-api-url: ${KAKAO_MAP_API_URL}

naver:
api-url: ${NAVER_API_URL}
token-info-url: ${NAVER_TOKEN_INFO_URL}

cloud:
aws:
credentials:
Expand Down
Loading

0 comments on commit 2fb141c

Please sign in to comment.