From cd7c54c91c45632bf110f25a3305df70d0f452d1 Mon Sep 17 00:00:00 2001 From: jonghne Date: Thu, 23 Nov 2023 20:54:25 +0900 Subject: [PATCH 1/2] =?UTF-8?q?#15=20fix:=20403=20error=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20(=EC=9D=B8=EC=A6=9D=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fineants/spring/config/FilterConfig.java | 41 ----- .../spring/config/SecurityConfig.java | 9 +- .../fineants/spring/config/WebConfig.java | 32 ---- .../fineants/spring/filter/CorsFilter.java | 4 +- .../spring/filter/JwtAuthorizationFilter.java | 141 ++++++++++-------- .../filter/auth/CustomUserDetailService.java | 23 +++ .../spring/filter/auth/CustomUserDetails.java | 52 +++++++ 7 files changed, 161 insertions(+), 141 deletions(-) delete mode 100644 src/main/java/codesquad/fineants/spring/config/FilterConfig.java delete mode 100644 src/main/java/codesquad/fineants/spring/config/WebConfig.java create mode 100644 src/main/java/codesquad/fineants/spring/filter/auth/CustomUserDetailService.java create mode 100644 src/main/java/codesquad/fineants/spring/filter/auth/CustomUserDetails.java diff --git a/src/main/java/codesquad/fineants/spring/config/FilterConfig.java b/src/main/java/codesquad/fineants/spring/config/FilterConfig.java deleted file mode 100644 index 481db03fe..000000000 --- a/src/main/java/codesquad/fineants/spring/config/FilterConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -//package codesquad.fineants.spring.config; -// -//import org.springframework.boot.web.servlet.FilterRegistrationBean; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -// -//import com.fasterxml.jackson.databind.ObjectMapper; -// -//import codesquad.fineants.domain.jwt.JwtProvider; -//import codesquad.fineants.domain.oauth.support.AuthenticationContext; -//import codesquad.fineants.spring.api.member.service.OauthMemberRedisService; -//import codesquad.fineants.spring.filter.JwtAuthorizationFilter; -//import codesquad.fineants.spring.filter.LogoutFilter; -//import lombok.RequiredArgsConstructor; -// -//@RequiredArgsConstructor -//@Configuration -//public class FilterConfig { -// -// private final JwtProvider jwtProvider; -// private final AuthenticationContext authenticationContext; -// private final ObjectMapper objectMapper; -// private final OauthMemberRedisService redisService; -// -// @Bean -// public FilterRegistrationBean jwtAuthorizationFilter() { -// FilterRegistrationBean filterFilterRegistrationBean = new FilterRegistrationBean<>(); -// filterFilterRegistrationBean.setFilter( -// new JwtAuthorizationFilter(jwtProvider, authenticationContext, objectMapper, redisService)); -// filterFilterRegistrationBean.addUrlPatterns("/api/*"); -// return filterFilterRegistrationBean; -// } -// -// @Bean -// public FilterRegistrationBean logoutFiler() { -// FilterRegistrationBean logoutFilerBean = new FilterRegistrationBean<>(); -// logoutFilerBean.setFilter(new LogoutFilter(redisService, objectMapper)); -// logoutFilerBean.addUrlPatterns("/api/auth/logout"); -// return logoutFilerBean; -// } -//} diff --git a/src/main/java/codesquad/fineants/spring/config/SecurityConfig.java b/src/main/java/codesquad/fineants/spring/config/SecurityConfig.java index d15288b2d..14ad69225 100644 --- a/src/main/java/codesquad/fineants/spring/config/SecurityConfig.java +++ b/src/main/java/codesquad/fineants/spring/config/SecurityConfig.java @@ -1,6 +1,7 @@ package codesquad.fineants.spring.config; import codesquad.fineants.domain.jwt.JwtProvider; +import codesquad.fineants.domain.member.MemberRepository; import codesquad.fineants.domain.oauth.support.AuthenticationContext; import codesquad.fineants.spring.api.member.service.OauthMemberRedisService; import codesquad.fineants.spring.filter.CorsFilter; @@ -10,7 +11,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @@ -18,8 +18,6 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import java.util.List; - @Configuration @RequiredArgsConstructor @EnableWebSecurity @@ -28,6 +26,7 @@ public class SecurityConfig { private final AuthenticationContext authenticationContext; private final ObjectMapper objectMapper; private final OauthMemberRedisService redisService; + private final MemberRepository memberRepository; // 인증이 필요하지 않은 주소 private final String[] accessUrl = {"/api/auth/login", @@ -57,8 +56,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti * 필터 추가 */ http.addFilterBefore(new CorsFilter(), UsernamePasswordAuthenticationFilter.class); // cors 필터 - http.addFilterBefore(new JwtAuthorizationFilter(jwtProvider, authenticationContext,objectMapper,redisService), UsernamePasswordAuthenticationFilter.class); // JWT 토큰으로 모든 접근에 대해 인증한다. - http.addFilterBefore(new LogoutFilter(redisService,objectMapper), UsernamePasswordAuthenticationFilter.class); + http.addFilterBefore(new JwtAuthorizationFilter(jwtProvider, authenticationContext, objectMapper, redisService,memberRepository), UsernamePasswordAuthenticationFilter.class); // JWT 토큰으로 모든 접근에 대해 인증한다. + http.addFilterBefore(new LogoutFilter(redisService, objectMapper), UsernamePasswordAuthenticationFilter.class); /** * 요청 허용 / 미허용 diff --git a/src/main/java/codesquad/fineants/spring/config/WebConfig.java b/src/main/java/codesquad/fineants/spring/config/WebConfig.java deleted file mode 100644 index e51b5f96f..000000000 --- a/src/main/java/codesquad/fineants/spring/config/WebConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -//package codesquad.fineants.spring.config; -// -//import java.util.List; -// -//import org.springframework.context.annotation.Configuration; -//import org.springframework.web.method.support.HandlerMethodArgumentResolver; -//import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -// -//import codesquad.fineants.domain.oauth.support.AuthPrincipalArgumentResolver; -//import codesquad.fineants.spring.intercetpor.LogoutInterceptor; -//import lombok.RequiredArgsConstructor; -// -//@RequiredArgsConstructor -//@Configuration -//public class WebConfig implements WebMvcConfigurer { -// -// private final AuthPrincipalArgumentResolver authPrincipalArgumentResolver; -// -// @Override -// public void addInterceptors(InterceptorRegistry registry) { -// registry.addInterceptor(new LogoutInterceptor()) -// .excludePathPatterns("/api/*") -// .addPathPatterns("/api/auth/logout"); -// } -// -// @Override -// public void addArgumentResolvers(List resolvers) { -// resolvers.add(authPrincipalArgumentResolver); -// WebMvcConfigurer.super.addArgumentResolvers(resolvers); -// } -//} diff --git a/src/main/java/codesquad/fineants/spring/filter/CorsFilter.java b/src/main/java/codesquad/fineants/spring/filter/CorsFilter.java index fb4ba7b8b..14b2d3669 100644 --- a/src/main/java/codesquad/fineants/spring/filter/CorsFilter.java +++ b/src/main/java/codesquad/fineants/spring/filter/CorsFilter.java @@ -22,9 +22,7 @@ public class CorsFilter implements Filter { @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws - IOException, - ServletException, ServletException { + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; diff --git a/src/main/java/codesquad/fineants/spring/filter/JwtAuthorizationFilter.java b/src/main/java/codesquad/fineants/spring/filter/JwtAuthorizationFilter.java index 59f1e3846..b051b7f18 100644 --- a/src/main/java/codesquad/fineants/spring/filter/JwtAuthorizationFilter.java +++ b/src/main/java/codesquad/fineants/spring/filter/JwtAuthorizationFilter.java @@ -9,6 +9,13 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import codesquad.fineants.spring.filter.auth.CustomUserDetails; +import codesquad.fineants.domain.member.Member; +import codesquad.fineants.domain.member.MemberRepository; +import io.jsonwebtoken.Claims; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.AntPathMatcher; import org.springframework.util.StringUtils; import org.springframework.web.cors.CorsUtils; @@ -31,64 +38,78 @@ @RequiredArgsConstructor public class JwtAuthorizationFilter extends OncePerRequestFilter { - public static final String AUTHORIZATION = "Authorization"; - public static final String BEARER = "Bearer"; - private static final AntPathMatcher pathMatcher = new AntPathMatcher(); - private static final List excludeUrlPatterns = List.of( - "/api/auth/**/authUrl", - "/api/auth/**/login", - "/api/auth/signup", - "/api/auth/login", - "/api/auth/refresh/token", - "/api/auth/logout", - "/api/stocks/**"); - private final JwtProvider jwtProvider; - private final AuthenticationContext authenticationContext; - private final ObjectMapper objectMapper; - private final OauthMemberRedisService redisService; - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - return excludeUrlPatterns.stream() - .anyMatch(pattern -> pathMatcher.match(pattern, request.getRequestURI())); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - if (CorsUtils.isPreFlightRequest(request)) { - filterChain.doFilter(request, response); - return; - } - try { - String token = extractJwt(request).orElseThrow( - () -> new UnAuthorizationException(JwtErrorCode.EMPTY_TOKEN)); - jwtProvider.validateToken(token); - redisService.validateAlreadyLogout(token); - authenticationContext.setAuthMember(jwtProvider.extractAuthMember(token)); - } catch (FineAntsException e) { - setErrorResponse(response, e.getErrorCode()); - return; - } - - filterChain.doFilter(request, response); - } - - private Optional extractJwt(HttpServletRequest request) { - String header = request.getHeader(AUTHORIZATION); - - if (!StringUtils.hasText(header) || !header.startsWith(BEARER)) { - return Optional.empty(); - } - - return Optional.of(header.split(" ")[1]); - } - - private void setErrorResponse(HttpServletResponse httpServletResponse, ErrorCode errorCode) throws IOException { - httpServletResponse.setStatus(errorCode.getHttpStatus().value()); - httpServletResponse.setContentType("application/json"); - httpServletResponse.setCharacterEncoding("UTF-8"); - ApiResponse body = ApiResponse.error(errorCode); - httpServletResponse.getWriter().write(objectMapper.writeValueAsString(body)); - } + public static final String AUTHORIZATION = "Authorization"; + public static final String BEARER = "Bearer"; + private static final AntPathMatcher pathMatcher = new AntPathMatcher(); + private static final List excludeUrlPatterns = List.of( + "/api/auth/**/authUrl", + "/api/auth/**/login", + "/api/auth/signup", + "/api/auth/login", + "/api/auth/refresh/token", + "/api/auth/logout", + "/api/stocks/**"); + private final JwtProvider jwtProvider; + private final AuthenticationContext authenticationContext; + private final ObjectMapper objectMapper; + private final OauthMemberRedisService redisService; + private final MemberRepository memberRepository; + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + return excludeUrlPatterns.stream() + .anyMatch(pattern -> pathMatcher.match(pattern, request.getRequestURI())); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + if (CorsUtils.isPreFlightRequest(request)) { + filterChain.doFilter(request, response); + return; + } + try { + String token = extractJwt(request).orElseThrow( + () -> new UnAuthorizationException(JwtErrorCode.EMPTY_TOKEN)); + jwtProvider.validateToken(token); + redisService.validateAlreadyLogout(token); + authenticationContext.setAuthMember(jwtProvider.extractAuthMember(token)); + + // Authentication 객체 생성 + Claims claims = jwtProvider.getClaims(token); + long memberId = Long.parseLong(String.valueOf(claims.get("memberId"))); + + Member member = memberRepository.findById(memberId).orElseThrow(()-> new RuntimeException("there is no member")); + + CustomUserDetails userDetail = new CustomUserDetails(member); + + Authentication authentication = new UsernamePasswordAuthenticationToken(userDetail, null, userDetail.getAuthorities()); + + // security 세션에 authentication 객체 넣기 (spring security가 인증 / 인가를 처리 하기 위함) + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (FineAntsException e) { + setErrorResponse(response, e.getErrorCode()); + return; + } + + filterChain.doFilter(request, response); + } + + private Optional extractJwt(HttpServletRequest request) { + String header = request.getHeader(AUTHORIZATION); + + if (!StringUtils.hasText(header) || !header.startsWith(BEARER)) { + return Optional.empty(); + } + + return Optional.of(header.split(" ")[1]); + } + + private void setErrorResponse(HttpServletResponse httpServletResponse, ErrorCode errorCode) throws IOException { + httpServletResponse.setStatus(errorCode.getHttpStatus().value()); + httpServletResponse.setContentType("application/json"); + httpServletResponse.setCharacterEncoding("UTF-8"); + ApiResponse body = ApiResponse.error(errorCode); + httpServletResponse.getWriter().write(objectMapper.writeValueAsString(body)); + } } diff --git a/src/main/java/codesquad/fineants/spring/filter/auth/CustomUserDetailService.java b/src/main/java/codesquad/fineants/spring/filter/auth/CustomUserDetailService.java new file mode 100644 index 000000000..bd66f4735 --- /dev/null +++ b/src/main/java/codesquad/fineants/spring/filter/auth/CustomUserDetailService.java @@ -0,0 +1,23 @@ +package codesquad.fineants.spring.filter.auth; + +import codesquad.fineants.domain.member.Member; +import codesquad.fineants.domain.member.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailService implements UserDetailsService { + private final MemberRepository memberRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + System.out.println("PrincipalDetailService.loadUserByUsername"); + Member member = memberRepository.findMemberByEmail(email) + .orElseThrow(()-> new RuntimeException("사용자를 찾을 수 없습니다.")); + return new CustomUserDetails(member); + } +} diff --git a/src/main/java/codesquad/fineants/spring/filter/auth/CustomUserDetails.java b/src/main/java/codesquad/fineants/spring/filter/auth/CustomUserDetails.java new file mode 100644 index 000000000..f64e71c82 --- /dev/null +++ b/src/main/java/codesquad/fineants/spring/filter/auth/CustomUserDetails.java @@ -0,0 +1,52 @@ +package codesquad.fineants.spring.filter.auth; + +import codesquad.fineants.domain.member.Member; +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +@Data +public class CustomUserDetails implements UserDetails { + private Member member; + + public CustomUserDetails(Member member) { + this.member = member; + } + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getPassword() { + return member.getPassword(); + } + + @Override + public String getUsername() { + return member.getEmail(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} From f27ff9c03bf974d3722e07a4939a1cd283865659 Mon Sep 17 00:00:00 2001 From: jonghne Date: Thu, 23 Nov 2023 21:20:12 +0900 Subject: [PATCH 2/2] =?UTF-8?q?#15=20fix=20:=20merge=20=EC=A4=91=20spring?= =?UTF-8?q?=20security=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=EB=90=9C=20=EB=B6=80=EB=B6=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ .../java/codesquad/fineants/spring/filter/CorsFilter.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 2973eacd4..643f83bbb 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-devtools' implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'org.springframework.boot:spring-boot-starter-mail' + // spring security 라이브러리 + implementation 'org.springframework.boot:spring-boot-starter-security' // websocket implementation 'org.springframework.boot:spring-boot-starter-websocket' diff --git a/src/main/java/codesquad/fineants/spring/filter/CorsFilter.java b/src/main/java/codesquad/fineants/spring/filter/CorsFilter.java index 26b48ce98..14b2d3669 100644 --- a/src/main/java/codesquad/fineants/spring/filter/CorsFilter.java +++ b/src/main/java/codesquad/fineants/spring/filter/CorsFilter.java @@ -17,8 +17,8 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) +//@Component +//@Order(Ordered.HIGHEST_PRECEDENCE) public class CorsFilter implements Filter { @Override