Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] spring security 관련 로직 수정 #137

Merged
merged 13 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.whoz_in.main_api.config.security.oauth2;
package com.whoz_in.main_api.config.security;

import com.whoz_in.domain.member.model.MemberId;
import java.util.Collection;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

//SecurityContextHolder에 저장할 인증 객체로, jwt(AccessToken)로 만들어진다.
@RequiredArgsConstructor
public class JwtAuthentication implements Authentication {
private final MemberId memberId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.whoz_in.main_api.config.security.oauth2;
package com.whoz_in.main_api.config.security;


import static com.whoz_in.main_api.config.security.consts.JwtConst.AUTHORIZATION;
import static com.whoz_in.main_api.shared.jwt.JwtConst.AUTHORIZATION;

import com.whoz_in.main_api.shared.jwt.tokens.AccessToken;
import com.whoz_in.main_api.shared.jwt.tokens.AccessTokenSerializer;
Expand All @@ -14,8 +14,11 @@
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

//요청에서 jwt를 꺼내어 인증정보를 만들고 SecurityContextHolder에 넣는 필터
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final AccessTokenSerializer accessTokenSerializer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
package com.whoz_in.main_api.config.security;

import com.whoz_in.main_api.config.security.oauth2.ClientRegistrationRepositoryFactory;
import com.whoz_in.main_api.config.security.oauth2.CustomOAuth2UserService;
import com.whoz_in.main_api.config.security.oauth2.LoginFailureHandler;
import com.whoz_in.main_api.config.security.oauth2.LoginSuccessHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

private final CustomOAuth2UserService customOAuth2UserService;
private final ClientRegistrationRepositoryFactory clientRegistrationRepositoryFactory;
private final ClientRegistrationRepository clientRegistrationRepository;
private final LoginSuccessHandler loginSuccessHandler;
private final LoginFailureHandler loginFailureHandler;
private final JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
@Order(0)
@Order(1)
public SecurityFilterChain oauth2FilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.securityMatcher(
"/login", //TODO: 운용에선 제거
"/oauth2/authorization/*",
"/login/oauth2/code/*"
"/login", //시큐리티 기본 로그인 페이지
"/oauth2/authorization/*", //소셜 로그인 페이지 (OAuth2LoginConfigurer에서 자동 생성)
"/login/oauth2/code/*" //redirect uri
);

commonConfigurations(httpSecurity);
Expand All @@ -38,8 +40,7 @@ public SecurityFilterChain oauth2FilterChain(HttpSecurity httpSecurity) throws E
httpSecurity.logout(AbstractHttpConfigurer::disable);
httpSecurity.oauth2Login(oauth2->
oauth2
//.loginPage(null) //TODO: 운영에선 추가
.clientRegistrationRepository(clientRegistrationRepositoryFactory.create())
.clientRegistrationRepository(clientRegistrationRepository)
.userInfoEndpoint(config -> config.userService(customOAuth2UserService))
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler)
Expand All @@ -48,6 +49,34 @@ public SecurityFilterChain oauth2FilterChain(HttpSecurity httpSecurity) throws E
return httpSecurity.build();
}

//인증이 필요하거나 인증 여부에 따라 다른 동작을 하는 메서드
//로그아웃, 게시글 작성 등
@Bean
@Order(3)
public SecurityFilterChain authenticationFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.securityMatcher(
"/**"
);
httpSecurity.authorizeHttpRequests(auth-> {
//인증 필요
auth.requestMatchers(HttpMethod.POST,
"/api/v1/device"
).authenticated();
//인증 여부에 따라 다른 동작
// auth.requestMatchers(
// ).permitAll();
auth.anyRequest().denyAll();
});

commonConfigurations(httpSecurity);

httpSecurity.csrf(AbstractHttpConfigurer::disable);
httpSecurity.addFilterAt(jwtAuthenticationFilter, LogoutFilter.class);
//TODO: 로그아웃 추가

return httpSecurity.build();
}

private void commonConfigurations(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf(AbstractHttpConfigurer::disable);
httpSecurity.httpBasic(AbstractHttpConfigurer::disable);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.whoz_in.main_api.config.security;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SecurityFilterConfig {

// Filter를 구현한 클래스가 빈으로 등록되면 전역 필터로 동작하기 때문에 모든 요청에 대해 jwt 검증을 수행하게 됩니다.
// 따라서 필요한 경우에만 jwt 필터를 사용할 수 있도록 FilterRegistrationBean을 사용하여 전역 필터에서 제외합니다.
@Bean
public FilterRegistrationBean<JwtAuthenticationFilter> jwtAuthenticationFilterRegistration(
JwtAuthenticationFilter jwtAuthenticationFilter) {
FilterRegistrationBean<JwtAuthenticationFilter> registrationBean = new FilterRegistrationBean<>(jwtAuthenticationFilter);
registrationBean.setEnabled(false);
return registrationBean;
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package com.whoz_in.main_api.config.security.oauth2;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.stereotype.Component;

@Component
@Configuration
@RequiredArgsConstructor
public class ClientRegistrationRepositoryFactory {

public class ClientRegistrationRepositoryConfig {
private final ClientRegistrations clientRegistrations;

public ClientRegistrationRepository create(){
@Bean
public ClientRegistrationRepository clientRegistrationRepository(){
return new InMemoryClientRegistrationRepository(
clientRegistrations.kakaoClientRegistration()
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
public class ClientRegistrations {

@Value("${oauth.kakao.secret}")
private String KAKAO_SECRET;
private String kakaoSecret;

@Value("${oauth.kakao.clientId}")
private String KAKAO_CLIENT_ID;
private String kakaoClientId;

@Value("${oauth.redirectUri}")
private String redirectUri;
Expand All @@ -23,11 +23,11 @@ public ClientRegistration kakaoClientRegistration() {
String providerName = SocialProvider.KAKAO.getProviderName();

return ClientRegistration.withRegistrationId(providerName)
.clientId(KAKAO_CLIENT_ID)
.clientSecret(KAKAO_SECRET)
.clientId(kakaoClientId)
.clientSecret(kakaoSecret)
.redirectUri(redirectUri)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.scope("account_email", "profile_nickname", "profile_image")
.scope("profile_image")
.authorizationUri("https://kauth.kakao.com/oauth/authorize")
.tokenUri("https://kauth.kakao.com/oauth/token")
.userInfoUri("https://kapi.kakao.com/v2/user/me")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.whoz_in.domain.member.MemberRepository;
import com.whoz_in.domain.member.model.SocialProvider;
import com.whoz_in.domain.shared.event.EventBus;
import com.whoz_in.main_api.config.security.oauth2.response.ProviderResponse;
import com.whoz_in.main_api.config.security.oauth2.response.ProviderResponseFactory;
import lombok.RequiredArgsConstructor;
Expand All @@ -11,30 +12,26 @@
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

// TODO: QueryHandler 나 다른 객체로 변경
private final MemberRepository memberRepository;
private final ApplicationEventPublisher eventPublisher;

@Transactional(readOnly = true)
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
//소셜 프로바이더로부터 받은 액세스 토큰을 이용하여 사용자 정보를 가져오도록 구현되어있다.
OAuth2User oAuth2User = super.loadUser(userRequest);

//우리 프로젝트에서 필요한 정보로 재구성하여 반환하도록 한다.
String providerName = userRequest.getClientRegistration().getRegistrationId();
SocialProvider socialProvider = SocialProvider.findSocialProvider(providerName);
ProviderResponse providerResponse = ProviderResponseFactory.create(socialProvider, oAuth2User.getAttributes());
String socialId = providerResponse.getSocialId();
String email = providerResponse.getEmail(); // TODO: email 받아오지 않기
String name = providerResponse.getName();
boolean registered = memberRepository.existsBySocialProviderAndSocialId(socialProvider, socialId);
// TODO: 일반 회원가입으로 등록이 되었을 경우에, 카카오 로그인을 시도하면 socialProvider 정보와 socialId 값을 저장해야 함
// 카카오톡으로부터 사용자의 실명을 가져오면?

return new OAuth2UserInfo(registered, socialProvider, socialId, name);
return new OAuth2UserInfo(registered, socialProvider, socialId);
}

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package com.whoz_in.main_api.config.security.oauth2;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

//사용자가 소셜 로그인에 실패했을 경우 처리하는 핸들러
@Component
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
AuthenticationException exception) throws IOException {

exception.printStackTrace();
response.sendRedirect("/login"); //TODO: Static
Expand Down
Loading
Loading