diff --git a/build.gradle b/build.gradle index 77fef91..019efcd 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' diff --git a/src/main/java/com/anywayclear/config/SecurityConfig.java b/src/main/java/com/anywayclear/config/SecurityConfig.java index 645ff4c..e69348f 100644 --- a/src/main/java/com/anywayclear/config/SecurityConfig.java +++ b/src/main/java/com/anywayclear/config/SecurityConfig.java @@ -1,14 +1,17 @@ package com.anywayclear.config; import com.anywayclear.config.oauth.CustumOAuth2UserService; +import com.anywayclear.config.oauth.OAuth2AuthenticationSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.filter.CorsFilter; + +import javax.servlet.Filter; @Configuration @EnableWebSecurity @@ -17,15 +20,26 @@ public class SecurityConfig { @Autowired private CustumOAuth2UserService custumOAuth2UserService; + @Autowired + private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; + + @Autowired + private CorsFilter corsFilter; + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { - httpSecurity.cors(Customizer.withDefaults()) // 기본 CORS 설정 적용 + + httpSecurity + .addFilterBefore(corsFilter, CorsFilter.class) // CorsFilter를 SecurityFilterChain 앞에 추가 .csrf().disable() // CSRF 비활성화 + .formLogin().disable() // spring security에서 제공하는 login form 비활성화 .authorizeHttpRequests(authorize -> authorize .antMatchers(HttpMethod.OPTIONS).permitAll() // OPTIONS 메서드는 모두 허용 .anyRequest().permitAll() // 모든 요청 권한 허용 (추후 권한 설정해야함) ) .oauth2Login(oauth2Login -> oauth2Login +// .loginPage("/frontLoginPage") // OAuth 2.0 로그인을 처리할 때, 인증되지 않은 사용자를 전달할 로그인 페이지를 지정 + .successHandler(oAuth2AuthenticationSuccessHandler) .userInfoEndpoint() .userService(custumOAuth2UserService) ); diff --git a/src/main/java/com/anywayclear/config/jwt/JwtProperties.java b/src/main/java/com/anywayclear/config/jwt/JwtProperties.java new file mode 100644 index 0000000..5b150af --- /dev/null +++ b/src/main/java/com/anywayclear/config/jwt/JwtProperties.java @@ -0,0 +1,7 @@ +package com.anywayclear.config.jwt; + +public interface JwtProperties { + int EXPIRATION_TIME = 864000000; // 10일 (1/1000초) + String TOKEN_PREFIX = "Bearer "; + String HEADER_STRING = "Authorization"; +} diff --git a/src/main/java/com/anywayclear/config/jwt/JwtProvider.java b/src/main/java/com/anywayclear/config/jwt/JwtProvider.java new file mode 100644 index 0000000..3aa0468 --- /dev/null +++ b/src/main/java/com/anywayclear/config/jwt/JwtProvider.java @@ -0,0 +1,43 @@ +package com.anywayclear.config.jwt; + +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; +import io.jsonwebtoken.Jwts; + +import static com.anywayclear.config.jwt.JwtProperties.*; + +@Component +public class JwtProvider { + private static final Logger logger = LoggerFactory.getLogger(JwtProvider.class); + private Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS512); + + // 인증 정보를 기반으로 JWT 토큰 생성하는 메서드 + public String createToken(Authentication authentication) { + System.out.println(">>>>>>>>>>>> "); + + // 사용자 정보 가져오기 + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + System.out.println("Provider ================== "); + System.out.println("oAuth2User.getAttributes() = " + oAuth2User.getAttributes()); + + // 현재 시간과 토큰 만료 시간 설정 + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME); + + // JWT 토큰 생성 + return Jwts.builder() + .setSubject((String) oAuth2User.getAttributes().get("userId")) // userId를 토큰의 subject로 설정 + .setIssuedAt(new Date()) // 토큰 발행 시간 설정 + .setExpiration(expiryDate) // 토큰 만료 시간 설정 + .signWith(SECRET_KEY, SignatureAlgorithm.HS512) // 토큰 서명에 사용할 비밀키 설정 + .compact(); // 최종적으로 JWT 토큰 문자열로 변환 + } +} diff --git a/src/main/java/com/anywayclear/config/oauth/CustumOAuth2UserService.java b/src/main/java/com/anywayclear/config/oauth/CustumOAuth2UserService.java index 0cf3b7e..afc7772 100644 --- a/src/main/java/com/anywayclear/config/oauth/CustumOAuth2UserService.java +++ b/src/main/java/com/anywayclear/config/oauth/CustumOAuth2UserService.java @@ -1,5 +1,8 @@ package com.anywayclear.config.oauth; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + import com.anywayclear.entity.Member; import com.anywayclear.repository.MemberRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -12,10 +15,13 @@ import org.springframework.stereotype.Service; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; +@Slf4j +@RequiredArgsConstructor @Service public class CustumOAuth2UserService extends DefaultOAuth2UserService { @@ -25,39 +31,52 @@ public class CustumOAuth2UserService extends DefaultOAuth2UserService { @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); - Map attributes = oAuth2User.getAttributes(); - Map kakao_account = (Map) attributes.get("kakao_account"); + Map kakao_account = (Map) oAuth2User.getAttributes().get("kakao_account"); Map profile = (Map) kakao_account.get("profile"); String emailAddress = (String) kakao_account.get("email"); String nickname = (String) profile.get("nickname"); String image = (String) profile.get("profile_image_url"); - String id = String.valueOf(UUID.randomUUID()); // UUID Version 4를 생성하여 반환 - String userId = String.valueOf(UUID.randomUUID()); - // email로 회원가입 여부 판단 + Member member = getOrCreateMember(emailAddress, nickname, image); + + String oauth2UserRole = member.getRole(); + String oauth2UserId = member.getUserId(); + + Map userAttributes = new HashMap<>(); + userAttributes.put("role", oauth2UserRole); + userAttributes.put("userId", oauth2UserId); + + // Spring Security의 세션에 OAuth2User객체 저장됨 + return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority(oauth2UserRole)), userAttributes, "userId"); + } + + private Member getOrCreateMember(String emailAddress, String nickname, String image) { Optional memberOptional = memberRepository.findByEmailAddress(emailAddress); - Member member; - - if (memberOptional.isEmpty()) { - // 회원가입 - member = Member.builder() - .id(id) - .userId(userId) - .emailAddress(emailAddress) - .image(image) - .nickname(nickname) - .role("ROLE_CUSTOMER") - .build(); - } else { - member = memberOptional.get(); + if (memberOptional.isPresent()) { + System.out.println("이미 회원입니다"); + Member member = memberOptional.get(); member.setImage(image); + return memberRepository.save(member); + } else { + System.out.println("회원가입합니다"); + return createMember(emailAddress, nickname, image); } + } - memberRepository.save(member); - String oauth2UserRole = member.getRole(); + private Member createMember(String emailAddress, String nickname, String image) { + String id = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + + Member member = Member.builder() + .id(id) + .userId(userId) + .emailAddress(emailAddress) + .image(image) + .nickname(nickname) + .role("ROLE_CUSTOMER") + .build(); - // Handler로 로그인된 유저객체 보내기 - return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority(oauth2UserRole)), attributes, "id"); + return memberRepository.save(member); } } diff --git a/src/main/java/com/anywayclear/config/oauth/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/anywayclear/config/oauth/OAuth2AuthenticationSuccessHandler.java new file mode 100644 index 0000000..9f17798 --- /dev/null +++ b/src/main/java/com/anywayclear/config/oauth/OAuth2AuthenticationSuccessHandler.java @@ -0,0 +1,37 @@ +package com.anywayclear.config.oauth; + +import com.anywayclear.config.jwt.JwtProperties; +import com.anywayclear.config.jwt.JwtProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + @Autowired + private JwtProvider jwtProvider; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + + String jwt = jwtProvider.createToken(authentication); + String redirectUrl = "http://localhost:3000/oauth2/redirect"; + + response.setHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + jwt); + response.setStatus(HttpServletResponse.SC_OK); + + if (response.isCommitted()) { + logger.debug(redirectUrl + "로 리다이렉트 불가"); + return; + } + + getRedirectStrategy().sendRedirect(request, response, redirectUrl); + + } +}