diff --git a/src/main/java/com/example/reddiserver/ReddiServerApplication.java b/src/main/java/com/example/reddiserver/ReddiServerApplication.java index a843f42..73e3ded 100644 --- a/src/main/java/com/example/reddiserver/ReddiServerApplication.java +++ b/src/main/java/com/example/reddiserver/ReddiServerApplication.java @@ -7,7 +7,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.scheduling.annotation.EnableScheduling; -@OpenAPIDefinition(servers = {@Server(url = "https://api.reddi.kr", description = "배포서버 URL"), @Server(url = "http://localhost:8080", description = "로컬서버 URL")}) +//@OpenAPIDefinition(servers = {@Server(url = "https://api.reddi.kr", description = "배포서버 URL"), @Server(url = "http://localhost:8080", description = "로컬서버 URL")}) @EnableJpaAuditing @EnableScheduling @SpringBootApplication @@ -16,5 +16,4 @@ public class ReddiServerApplication { public static void main(String[] args) { SpringApplication.run(ReddiServerApplication.class, args); } - } diff --git a/src/main/java/com/example/reddiserver/security/config/CorsConfig.java b/src/main/java/com/example/reddiserver/config/CorsConfig.java similarity index 94% rename from src/main/java/com/example/reddiserver/security/config/CorsConfig.java rename to src/main/java/com/example/reddiserver/config/CorsConfig.java index 7083be4..4022189 100644 --- a/src/main/java/com/example/reddiserver/security/config/CorsConfig.java +++ b/src/main/java/com/example/reddiserver/config/CorsConfig.java @@ -1,4 +1,4 @@ -package com.example.reddiserver.security.config; +package com.example.reddiserver.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/example/reddiserver/config/SwaggerConfig.java b/src/main/java/com/example/reddiserver/config/SwaggerConfig.java index 8cb84fe..7632eb1 100644 --- a/src/main/java/com/example/reddiserver/config/SwaggerConfig.java +++ b/src/main/java/com/example/reddiserver/config/SwaggerConfig.java @@ -1,31 +1,49 @@ package com.example.reddiserver.config; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.security.SecurityScheme; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import java.util.List; + @Configuration +@SecurityScheme( + name = "Authorization", + type = SecuritySchemeType.HTTP, + bearerFormat = "JWT", + scheme = "bearer" +) public class SwaggerConfig { private static final String SECURITY_SCHEME_NAME = "authorization"; // 추가 + @Bean + @Profile("local") + public OpenAPI localOpenAPIBuilder() { + return new OpenAPI().addServersItem(new Server().url("http://localhost:8080")); + } + + @Bean + @Profile("dev") + public OpenAPI devOpenAPIBuilder() { + return new OpenAPI().addServersItem(new Server().url("https://api.reddi.kr")); + } @Bean public OpenAPI openAPI() { Info info = new Info() - .title("API Document") - .version("v0.0.1") - .description("REDDI API 명세서"); + .title("API Document") + .version("v0.0.1") + .description("REDDI API 명세서"); return new OpenAPI() - .components(new Components() - .addSecuritySchemes(SECURITY_SCHEME_NAME, new SecurityScheme() - .name(SECURITY_SCHEME_NAME) - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT"))) - .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME)) + .components(new Components()) .info(info); } + } diff --git a/src/main/java/com/example/reddiserver/security/config/WebSecurityConfig.java b/src/main/java/com/example/reddiserver/config/WebSecurityConfig.java similarity index 98% rename from src/main/java/com/example/reddiserver/security/config/WebSecurityConfig.java rename to src/main/java/com/example/reddiserver/config/WebSecurityConfig.java index 9cb71ce..02397a8 100644 --- a/src/main/java/com/example/reddiserver/security/config/WebSecurityConfig.java +++ b/src/main/java/com/example/reddiserver/config/WebSecurityConfig.java @@ -1,4 +1,4 @@ -package com.example.reddiserver.security.config; +package com.example.reddiserver.config; import com.example.reddiserver.security.jwt.JwtAuthenticationFilter; import com.example.reddiserver.security.jwt.JwtExceptionHandlerFilter; diff --git a/src/main/java/com/example/reddiserver/controller/AuthController.java b/src/main/java/com/example/reddiserver/controller/AuthController.java index ba48905..b542e9b 100644 --- a/src/main/java/com/example/reddiserver/controller/AuthController.java +++ b/src/main/java/com/example/reddiserver/controller/AuthController.java @@ -2,22 +2,56 @@ import com.example.reddiserver.common.ApiResponse; import com.example.reddiserver.dto.auth.request.ReissueRequestDto; +import com.example.reddiserver.dto.auth.response.UserInfoResponseDto; import com.example.reddiserver.dto.security.response.TokenDto; +import com.example.reddiserver.entity.Member; +import com.example.reddiserver.repository.MemberRepository; +import com.example.reddiserver.security.jwt.TokenProvider; import com.example.reddiserver.service.AuthService; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; @RestController @RequiredArgsConstructor @RequestMapping("/api/auth") public class AuthController { private final AuthService authService; + private final TokenProvider tokenProvider; + @SecurityRequirement(name = "Authorization") // 인증 필요한 엔드포인트에 설정 @PostMapping("/reissue") public ApiResponse reissue(@RequestBody ReissueRequestDto reissueRequestDto) { return ApiResponse.successResponse(authService.reissue(reissueRequestDto), "Access Token 재발급 성공"); } + + @SecurityRequirement(name = "Authorization") // 인증 필요한 엔드포인트에 설정 + @GetMapping("/info") + public ApiResponse getUserInfo(HttpServletRequest request) { + // Extract Access Token from the request + String accessToken = tokenProvider.resolveToken(request); + + Map userInfo = authService.getUserInfo(accessToken); + + if (userInfo != null) { + UserInfoResponseDto userInfoResponseDto = UserInfoResponseDto.from(Long.valueOf(userInfo.get("userId").toString()), + userInfo.get("username").toString(), + userInfo.get("email").toString(), + userInfo.get("profileImageUrl").toString()); + + return ApiResponse.successResponse(userInfoResponseDto); + } + return ApiResponse.errorResponse("유효하지 않은 Access Token 입니다."); + } } + diff --git a/src/main/java/com/example/reddiserver/dto/auth/response/UserInfoResponseDto.java b/src/main/java/com/example/reddiserver/dto/auth/response/UserInfoResponseDto.java new file mode 100644 index 0000000..fffd80a --- /dev/null +++ b/src/main/java/com/example/reddiserver/dto/auth/response/UserInfoResponseDto.java @@ -0,0 +1,22 @@ +package com.example.reddiserver.dto.auth.response; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UserInfoResponseDto { + private Long userId; + private String name; + private String email; + private String profileImageUrl; + + public static UserInfoResponseDto from(Long userId, String name, String email, String profileImageUrl) { + UserInfoResponseDto userInfoResponseDto = new UserInfoResponseDto(); + userInfoResponseDto.setUserId(userId); + userInfoResponseDto.setName(name); + userInfoResponseDto.setEmail(email); + userInfoResponseDto.setProfileImageUrl(profileImageUrl); + return userInfoResponseDto; + } +} diff --git a/src/main/java/com/example/reddiserver/entity/Member.java b/src/main/java/com/example/reddiserver/entity/Member.java index 146dec9..8f1a940 100644 --- a/src/main/java/com/example/reddiserver/entity/Member.java +++ b/src/main/java/com/example/reddiserver/entity/Member.java @@ -34,8 +34,11 @@ public class Member extends BaseTimeEntity { @Column(nullable = false) private String name; + @Column(nullable = false) + private String email; + @Column - private String imageUrl; + private String profileImageUrl; @Column(nullable = false) @Enumerated(EnumType.STRING) @@ -46,10 +49,11 @@ public class Member extends BaseTimeEntity { private Authority authority; @Builder - public Member(String providerId, String name, String imageUrl, ProviderType providerType, Authority authority) { + public Member(String providerId, String name, String email, String profileImageUrl, ProviderType providerType, Authority authority) { this.providerId = providerId; this.name = name; - this.imageUrl = imageUrl; + this.email = email; + this.profileImageUrl = profileImageUrl; this.providerType = providerType; this.authority = authority; } diff --git a/src/main/java/com/example/reddiserver/security/jwt/JwtAuthenticationFilter.java b/src/main/java/com/example/reddiserver/security/jwt/JwtAuthenticationFilter.java index f9d6faf..64920c9 100644 --- a/src/main/java/com/example/reddiserver/security/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/reddiserver/security/jwt/JwtAuthenticationFilter.java @@ -16,23 +16,13 @@ @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String BEARER_PREFIX = "Bearer"; - private final TokenProvider tokenProvider; - - private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader(AUTHORIZATION_HEADER); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) { - return bearerToken.substring(7); - } + private final TokenProvider tokenProvider; - return null; - } @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String jwt = resolveToken(request); + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, RuntimeException { + String jwt = tokenProvider.resolveToken(request); if (StringUtils.hasText(jwt) && tokenProvider.validateAccessToken(jwt)) { Authentication authentication = tokenProvider.getAuthentication(jwt); diff --git a/src/main/java/com/example/reddiserver/security/jwt/TokenProvider.java b/src/main/java/com/example/reddiserver/security/jwt/TokenProvider.java index cee4762..3d9861a 100644 --- a/src/main/java/com/example/reddiserver/security/jwt/TokenProvider.java +++ b/src/main/java/com/example/reddiserver/security/jwt/TokenProvider.java @@ -5,6 +5,7 @@ import com.example.reddiserver.repository.RefreshTokenRepository; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; @@ -16,6 +17,7 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import java.security.Key; import java.util.*; @@ -25,6 +27,9 @@ @Component @RequiredArgsConstructor public class TokenProvider implements InitializingBean { + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer"; private static final String AUTHORITIES_KEY = "auth"; private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30L; private static final long REFRESH_TOKEN_EXPIRE_TIME = 60 * 60 * 24 * 3L; @@ -110,18 +115,32 @@ public Authentication getAuthentication(String accessToken) { return new UsernamePasswordAuthenticationToken(userDetails, "", authorities); } - public boolean validateAccessToken(String accessToken) { + public String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(AUTHORIZATION_HEADER); + + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) { + return bearerToken.substring(7); + } + + return null; + } + + public boolean validateAccessToken(String accessToken){ try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken); return true; } catch (io.jsonwebtoken.security.SignatureException | MalformedJwtException e) { - log.error("잘못된 JWT 서명입니다"); + String msg = "잘못된 JWT 서명입니다"; + log.error(msg); } catch (ExpiredJwtException e) { - log.error("만료된 JWT 토큰입니다"); + String msg = "만료된 JWT 토큰입니다"; + log.error(msg); } catch (UnsupportedJwtException e) { - log.error("지원되지 않는 JWT 토큰입니다"); + String msg = "지원되지 않는 JWT 토큰입니다"; + log.error(msg); } catch (IllegalArgumentException e) { - log.error("Jwt 토큰이 잘못되었습니다"); + String msg = "JWT 토큰이 잘못되었습니다"; + log.error(msg); } return false; } diff --git a/src/main/java/com/example/reddiserver/security/oauth/PrincipalOAuth2Details.java b/src/main/java/com/example/reddiserver/security/oauth/PrincipalOAuth2Details.java index a781132..231aed2 100644 --- a/src/main/java/com/example/reddiserver/security/oauth/PrincipalOAuth2Details.java +++ b/src/main/java/com/example/reddiserver/security/oauth/PrincipalOAuth2Details.java @@ -43,7 +43,7 @@ public String getAuthority() { } @Override - public String getName() { + public String getName() { // principal.getName() 에서 getName() 은 providerId 를 의미 return member.getProviderId(); } } diff --git a/src/main/java/com/example/reddiserver/security/oauth/PrincipalOAuth2DetailsService.java b/src/main/java/com/example/reddiserver/security/oauth/PrincipalOAuth2DetailsService.java index abf9f76..e1a4e7b 100644 --- a/src/main/java/com/example/reddiserver/security/oauth/PrincipalOAuth2DetailsService.java +++ b/src/main/java/com/example/reddiserver/security/oauth/PrincipalOAuth2DetailsService.java @@ -27,6 +27,8 @@ protected OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User String registrationId = userRequest.getClientRegistration().getRegistrationId().toUpperCase(); OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(registrationId, oAuth2User.getAttributes()); + System.out.println("oAuth2User.getAttributes() = " + oAuth2User.getAttributes()); + Member member = memberRepository.findByProviderId(oAuth2UserInfo.getProviderId()).orElse(null); if (member == null) { member = signup(oAuth2UserInfo); @@ -39,6 +41,8 @@ private Member signup(OAuth2UserInfo oAuth2UserInfo) { Member member = Member.builder() .providerId(oAuth2UserInfo.getProviderId()) .name(oAuth2UserInfo.getName()) + .email(oAuth2UserInfo.getEmail()) + .profileImageUrl(oAuth2UserInfo.getProfileImage()) .providerType(oAuth2UserInfo.getProviderType()) .authority(Authority.ROLE_USER) .build(); diff --git a/src/main/java/com/example/reddiserver/security/oauth/provider/GoogleOAuth2User.java b/src/main/java/com/example/reddiserver/security/oauth/provider/GoogleOAuth2User.java index 7caee51..0b7423a 100644 --- a/src/main/java/com/example/reddiserver/security/oauth/provider/GoogleOAuth2User.java +++ b/src/main/java/com/example/reddiserver/security/oauth/provider/GoogleOAuth2User.java @@ -25,4 +25,8 @@ public ProviderType getProviderType() { public String getEmail() { return (String) attributes.get("email"); } + + public String getProfileImage() { + return (String) attributes.get("picture"); + } } diff --git a/src/main/java/com/example/reddiserver/security/oauth/provider/OAuth2UserInfo.java b/src/main/java/com/example/reddiserver/security/oauth/provider/OAuth2UserInfo.java index e5a3274..92e1089 100644 --- a/src/main/java/com/example/reddiserver/security/oauth/provider/OAuth2UserInfo.java +++ b/src/main/java/com/example/reddiserver/security/oauth/provider/OAuth2UserInfo.java @@ -15,4 +15,5 @@ public abstract class OAuth2UserInfo { public abstract String getName(); public abstract ProviderType getProviderType(); public abstract String getEmail(); + public abstract String getProfileImage(); } diff --git a/src/main/java/com/example/reddiserver/service/AuthService.java b/src/main/java/com/example/reddiserver/service/AuthService.java index c6e213f..e8f1ddf 100644 --- a/src/main/java/com/example/reddiserver/service/AuthService.java +++ b/src/main/java/com/example/reddiserver/service/AuthService.java @@ -1,18 +1,27 @@ package com.example.reddiserver.service; +import com.example.reddiserver.common.ApiResponse; import com.example.reddiserver.dto.auth.request.ReissueRequestDto; import com.example.reddiserver.dto.security.response.TokenDto; +import com.example.reddiserver.entity.Member; +import com.example.reddiserver.repository.MemberRepository; import com.example.reddiserver.security.jwt.TokenProvider; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class AuthService { private final TokenProvider tokenProvider; + private final MemberRepository memberRepository; @Transactional public TokenDto reissue(ReissueRequestDto reissueRequestDto) { @@ -24,4 +33,29 @@ public TokenDto reissue(ReissueRequestDto reissueRequestDto) { return tokenProvider.createAccessToken(authentication); } + + // user info 조회 서비스 + public Map getUserInfo(String accessToken) { + + // Validate the Access Token + if (StringUtils.hasText(accessToken) && tokenProvider.validateAccessToken(accessToken)) { + // Retrieve user information from the token + Authentication authentication = tokenProvider.getAuthentication(accessToken); + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + + String providerId = userDetails.getUsername(); + + Member member = memberRepository.findByProviderId(providerId) + .orElseThrow(() -> new RuntimeException("User not found")); + + Map userInfo = new HashMap<>(); + userInfo.put("userId", member.getId()); + userInfo.put("username", member.getName()); + userInfo.put("email", member.getEmail()); + userInfo.put("profileImageUrl", member.getProfileImageUrl()); + return userInfo; + } + return null; + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6f10066..4b64967 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -16,6 +16,7 @@ spring: client-secret: ${CLIENT_SECRET} # redirectUri: http://localhost:8080/login/oauth2/code/google scope: profile, email +front-redirection-url: ${FRONT_LOCAL_REDIRECTION_URL} --- spring: @@ -33,8 +34,9 @@ spring: google: client-id: ${CLIENT_ID} client-secret: ${CLIENT_SECRET} -# redirectUri: http://localhost:8081/login/oauth2/code/google +# redirectUri: http://localhost:8081/login/oauth2/code/google (서버 포트) scope: profile, email +front-redirection-url: ${FRONT_PROD_REDIRECTION_URL} --- # default 공통 설정 @@ -83,6 +85,4 @@ notion: marketingDB: id: ${NOTION_MARKETING_DB_ID} homeDB: - id: ${NOTION_HOME_DB_ID} - -front-redirection-url : ${FRONT_REDIRECTION_URL} \ No newline at end of file + id: ${NOTION_HOME_DB_ID} \ No newline at end of file