diff --git a/src/main/java/com/sirius/spurt/common/auth/PrincipalDetails.java b/src/main/java/com/sirius/spurt/common/auth/PrincipalDetails.java index 5bd1adc..844fd38 100644 --- a/src/main/java/com/sirius/spurt/common/auth/PrincipalDetails.java +++ b/src/main/java/com/sirius/spurt/common/auth/PrincipalDetails.java @@ -36,7 +36,7 @@ public String getPassword() { @Override public String getUsername() { - return null; + return userEntity.getUserId(); } @Override diff --git a/src/main/java/com/sirius/spurt/common/config/SecurityConfig.java b/src/main/java/com/sirius/spurt/common/config/SecurityConfig.java index af04dd8..a0c85ab 100644 --- a/src/main/java/com/sirius/spurt/common/config/SecurityConfig.java +++ b/src/main/java/com/sirius/spurt/common/config/SecurityConfig.java @@ -6,10 +6,14 @@ import com.sirius.spurt.common.filter.ExceptionHandlerFilter; import com.sirius.spurt.common.jwt.AuthorizationFilter; import com.sirius.spurt.common.jwt.JwtUtils; +import com.sirius.spurt.common.oauth.handler.OAuth2AuthenticationFailHandler; +import com.sirius.spurt.common.oauth.handler.OAuth2AuthenticationSuccessHandler; +import com.sirius.spurt.common.oauth.service.OAuth2Service; import com.sirius.spurt.store.repository.database.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -24,6 +28,7 @@ public class SecurityConfig { private final UserRepository userRepository; private final JwtUtils jwtUtils; private final ObjectMapper objectMapper; + private final OAuth2Service oAuth2Service; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -36,14 +41,25 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .authorizeHttpRequests( (requests) -> requests + .requestMatchers("/oauth2/**") + .permitAll() .requestMatchers("/v1/question/random") .permitAll() + .requestMatchers("/v1/jobgroup", HttpMethod.POST.name()) + .permitAll() .requestMatchers("/error") .permitAll() .anyRequest() .authenticated()) .addFilterBefore(authorizationFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(exceptionHandlerFilter(), LogoutFilter.class); + .addFilterBefore(exceptionHandlerFilter(), LogoutFilter.class) + .oauth2Login( + oauth2LoginConfigurer -> + oauth2LoginConfigurer + .successHandler(oAuth2AuthenticationSuccessHandler()) + .failureHandler(oAuth2AuthenticationFailHandler()) + .userInfoEndpoint( + userInfoEndpoint -> userInfoEndpoint.userService(oAuth2Service))); return http.build(); } @@ -57,4 +73,14 @@ public AuthorizationFilter authorizationFilter() { public ExceptionHandlerFilter exceptionHandlerFilter() { return new ExceptionHandlerFilter(objectMapper); } + + @Bean + public OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler() { + return new OAuth2AuthenticationSuccessHandler(jwtUtils); + } + + @Bean + public OAuth2AuthenticationFailHandler oAuth2AuthenticationFailHandler() { + return new OAuth2AuthenticationFailHandler(objectMapper); + } } diff --git a/src/main/java/com/sirius/spurt/common/config/WebConfig.java b/src/main/java/com/sirius/spurt/common/config/WebConfig.java index aaa1433..18247c3 100644 --- a/src/main/java/com/sirius/spurt/common/config/WebConfig.java +++ b/src/main/java/com/sirius/spurt/common/config/WebConfig.java @@ -1,6 +1,7 @@ package com.sirius.spurt.common.config; import com.sirius.spurt.common.jwt.JwtUtils; +import com.sirius.spurt.common.resolver.LoginUserResolver; import com.sirius.spurt.common.resolver.NonLoginUserResolver; import java.util.List; import lombok.RequiredArgsConstructor; @@ -15,6 +16,7 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List resolvers) { + resolvers.add(new LoginUserResolver(jwtUtils)); resolvers.add(new NonLoginUserResolver(jwtUtils)); } } diff --git a/src/main/java/com/sirius/spurt/common/jwt/AuthorizationFilter.java b/src/main/java/com/sirius/spurt/common/jwt/AuthorizationFilter.java index a2804e9..4b28c21 100644 --- a/src/main/java/com/sirius/spurt/common/jwt/AuthorizationFilter.java +++ b/src/main/java/com/sirius/spurt/common/jwt/AuthorizationFilter.java @@ -4,8 +4,10 @@ import static com.sirius.spurt.common.jwt.JwtUtils.REFRESH_TOKEN_NAME; import static com.sirius.spurt.common.jwt.JwtUtils.TOKEN_TYPE; import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; import com.sirius.spurt.common.auth.PrincipalDetails; +import com.sirius.spurt.common.oauth.user.OAuthUser; import com.sirius.spurt.common.validator.TokenValidator; import com.sirius.spurt.common.validator.UserValidator; import com.sirius.spurt.store.repository.database.entity.UserEntity; @@ -36,7 +38,10 @@ public class AuthorizationFilter extends OncePerRequestFilter { private final JwtUtils jwtUtils; private static final List whiteList = - List.of(new AntPathRequestMatcher("/v1/question/random", GET.name())); + List.of( + new AntPathRequestMatcher("/oauth2/**", GET.name()), + new AntPathRequestMatcher("/v1/question/random", GET.name()), + new AntPathRequestMatcher("/v1/jobgroup", POST.name())); @Override protected void doFilterInternal( @@ -56,16 +61,16 @@ protected void doFilterInternal( TokenValidator.validateCookie(accessCookie); String accessToken = accessCookie.replace(TOKEN_TYPE, ""); - String userId = jwtUtils.getUserId(accessToken); - if (userId == null) { + OAuthUser oAuthUser = jwtUtils.getOAuthUser(accessToken); + if (oAuthUser == null) { TokenValidator.validateCookie(refreshCookie); String refreshToken = refreshCookie.replace(TOKEN_TYPE, ""); - userId = jwtUtils.getUserId(refreshToken); - TokenValidator.validateUserId(userId); - jwtUtils.updateTokens(response, userId); + oAuthUser = jwtUtils.getOAuthUser(refreshToken); + TokenValidator.validateOAuthUser(oAuthUser); + jwtUtils.updateTokens(response, oAuthUser.getUserId(), oAuthUser.getEmail()); } - setAuthentication(userId); + setAuthentication(oAuthUser.getUserId()); filterChain.doFilter(request, response); } diff --git a/src/main/java/com/sirius/spurt/common/jwt/JwtUtils.java b/src/main/java/com/sirius/spurt/common/jwt/JwtUtils.java index 82ecab0..a143829 100644 --- a/src/main/java/com/sirius/spurt/common/jwt/JwtUtils.java +++ b/src/main/java/com/sirius/spurt/common/jwt/JwtUtils.java @@ -5,7 +5,9 @@ import com.sirius.spurt.common.exception.GlobalException; import com.sirius.spurt.common.jwt.token.AccessToken; import com.sirius.spurt.common.jwt.token.RefreshToken; +import com.sirius.spurt.common.oauth.user.OAuthUser; import com.sirius.spurt.store.provider.auth.AuthProvider; +import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.io.Decoders; @@ -43,11 +45,12 @@ public void init() { this.key = Keys.hmacShaKeyFor(keyBytes); } - private AccessToken getAccessToken(String userId) { + private AccessToken getAccessToken(String userId, String email) { String accessToken = Jwts.builder() .setSubject("accessToken") .claim("userId", userId) + .claim("email", email) .setExpiration(new Date(getCurrentTimestamp() + ACCESS_TOKEN_EXPIRE_TIME)) .signWith(key, SignatureAlgorithm.HS256) .compact(); @@ -55,11 +58,12 @@ private AccessToken getAccessToken(String userId) { return AccessToken.builder().token(accessToken).expireTime(ACCESS_TOKEN_EXPIRE_TIME).build(); } - private RefreshToken getRefreshToken(String userId) { + private RefreshToken getRefreshToken(String userId, String email) { String refreshToken = Jwts.builder() .setSubject("refreshToken") .claim("userId", userId) + .claim("email", email) .setExpiration(new Date(getCurrentTimestamp() + REFRESH_TOKEN_EXPIRE_TIME)) .signWith(key, SignatureAlgorithm.HS256) .compact(); @@ -67,16 +71,24 @@ private RefreshToken getRefreshToken(String userId) { return RefreshToken.builder().token(refreshToken).expireTime(REFRESH_TOKEN_EXPIRE_TIME).build(); } - public void setAccessToken(HttpServletResponse response, String userId) { - AccessToken accessToken = getAccessToken(userId); - setCookie(response, ACCESS_TOKEN_NAME, accessToken.getToken(), accessToken.getExpireTime()); + public void setAccessToken(HttpServletResponse response, String userId, String email) { + AccessToken accessToken = getAccessToken(userId, email); + setCookie( + response, + ACCESS_TOKEN_NAME, + TOKEN_TYPE + accessToken.getToken(), + accessToken.getExpireTime()); } - public void setRefreshToken(HttpServletResponse response, String userId) { - RefreshToken refreshToken = getRefreshToken(userId); + public void setRefreshToken(HttpServletResponse response, String userId, String email) { + RefreshToken refreshToken = getRefreshToken(userId, email); authProvider.setRefreshToken( KEY_PREFIX + userId, refreshToken.getToken(), refreshToken.getExpireTime()); - setCookie(response, REFRESH_TOKEN_NAME, refreshToken.getToken(), refreshToken.getExpireTime()); + setCookie( + response, + REFRESH_TOKEN_NAME, + TOKEN_TYPE + refreshToken.getToken(), + refreshToken.getExpireTime()); } private void setCookie(HttpServletResponse response, String key, String token, Long expireTime) { @@ -95,27 +107,26 @@ private long getCurrentTimestamp() { return System.currentTimeMillis(); } - public String getUserId(String token) { + public OAuthUser getOAuthUser(String token) { try { - return Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody() - .get("userId") - .toString(); + Claims claims = + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); + return OAuthUser.builder() + .userId(claims.get("userId").toString()) + .email(claims.get("email").toString()) + .build(); } catch (Exception e) { log.error(e.getMessage()); return null; } } - public void updateTokens(HttpServletResponse response, String userId) { + public void updateTokens(HttpServletResponse response, String userId, String email) { if (!authProvider.hasRefreshToken(KEY_PREFIX + userId)) { throw new GlobalException(AUTHENTICATION_FAILED); } - setAccessToken(response, userId); - setRefreshToken(response, userId); + setAccessToken(response, userId, email); + setRefreshToken(response, userId, email); } } diff --git a/src/main/java/com/sirius/spurt/common/meta/Social.java b/src/main/java/com/sirius/spurt/common/meta/Social.java new file mode 100644 index 0000000..3fc847f --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/meta/Social.java @@ -0,0 +1,5 @@ +package com.sirius.spurt.common.meta; + +public enum Social { + GOOGLE +} diff --git a/src/main/java/com/sirius/spurt/common/oauth/handler/OAuth2AuthenticationFailHandler.java b/src/main/java/com/sirius/spurt/common/oauth/handler/OAuth2AuthenticationFailHandler.java new file mode 100644 index 0000000..a5d42c0 --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/oauth/handler/OAuth2AuthenticationFailHandler.java @@ -0,0 +1,47 @@ +package com.sirius.spurt.common.oauth.handler; + +import static com.sirius.spurt.common.meta.ResultCode.AUTHENTICATION_FAILED; +import static jakarta.servlet.http.HttpServletResponse.SC_OK; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sirius.spurt.service.controller.RestResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class OAuth2AuthenticationFailHandler implements AuthenticationFailureHandler { + private final ObjectMapper objectMapper; + + @Override + public void onAuthenticationFailure( + HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) + throws IOException, ServletException { + setErrorResponse(response, RestResponse.error(AUTHENTICATION_FAILED)); + } + + private void setErrorResponse(HttpServletResponse response, RestResponse res) + throws IOException { + response.setCharacterEncoding(UTF_8.name()); + response.setContentType(APPLICATION_JSON_VALUE); + response.setStatus(SC_OK); + + try { + response.getWriter().write(objectMapper.writeValueAsString(res)); + } catch (Exception e) { + log.error(e.getMessage()); + } finally { + response.getWriter().close(); + } + } +} diff --git a/src/main/java/com/sirius/spurt/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/sirius/spurt/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java new file mode 100644 index 0000000..689adc1 --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java @@ -0,0 +1,54 @@ +package com.sirius.spurt.common.oauth.handler; + +import com.sirius.spurt.common.auth.PrincipalDetails; +import com.sirius.spurt.common.jwt.JwtUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +@Slf4j +@Component +@RequiredArgsConstructor +public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + private final JwtUtils jwtUtils; + + private final String IS_COMMITTED = "Response has already committed."; + + @Value("${redirect-uri.prod}") + private String redirectUri; + + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException { + String targetUrl = determineTargetUrl(request, response, authentication); + if (response.isCommitted()) { + log.debug(IS_COMMITTED + targetUrl); + return; + } + + getRedirectStrategy().sendRedirect(request, response, targetUrl); + } + + @Override + protected String determineTargetUrl( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); + jwtUtils.setAccessToken( + response, + principalDetails.getUserEntity().getUserId(), + principalDetails.getUserEntity().getEmail()); + jwtUtils.setRefreshToken( + response, + principalDetails.getUserEntity().getUserId(), + principalDetails.getUserEntity().getEmail()); + return UriComponentsBuilder.fromUriString(redirectUri).build().toString(); + } +} diff --git a/src/main/java/com/sirius/spurt/common/oauth/info/OAuth2UserInfo.java b/src/main/java/com/sirius/spurt/common/oauth/info/OAuth2UserInfo.java new file mode 100644 index 0000000..56c2743 --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/oauth/info/OAuth2UserInfo.java @@ -0,0 +1,18 @@ +package com.sirius.spurt.common.oauth.info; + +import java.util.Map; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public abstract class OAuth2UserInfo { + protected Map attributes; + + public abstract String getUserId(); + + public abstract String getEmail(); +} diff --git a/src/main/java/com/sirius/spurt/common/oauth/info/OAuth2UserInfoFactory.java b/src/main/java/com/sirius/spurt/common/oauth/info/OAuth2UserInfoFactory.java new file mode 100644 index 0000000..f24ab4a --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/oauth/info/OAuth2UserInfoFactory.java @@ -0,0 +1,19 @@ +package com.sirius.spurt.common.oauth.info; + +import static com.sirius.spurt.common.meta.ResultCode.UNKNOWN_SOCIAL; +import static com.sirius.spurt.common.meta.Social.GOOGLE; + +import com.sirius.spurt.common.exception.GlobalException; +import com.sirius.spurt.common.meta.Social; +import com.sirius.spurt.common.oauth.info.impl.GoogleOAuth2UserInfo; +import java.util.Map; + +public class OAuth2UserInfoFactory { + public static OAuth2UserInfo getOAuth2UserInfo(Social social, Map attributes) { + if (GOOGLE.equals(social)) { + return new GoogleOAuth2UserInfo(attributes); + } + + throw new GlobalException(UNKNOWN_SOCIAL); + } +} diff --git a/src/main/java/com/sirius/spurt/common/oauth/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/com/sirius/spurt/common/oauth/info/impl/GoogleOAuth2UserInfo.java new file mode 100644 index 0000000..4d94509 --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/oauth/info/impl/GoogleOAuth2UserInfo.java @@ -0,0 +1,20 @@ +package com.sirius.spurt.common.oauth.info.impl; + +import com.sirius.spurt.common.oauth.info.OAuth2UserInfo; +import java.util.Map; + +public class GoogleOAuth2UserInfo extends OAuth2UserInfo { + public GoogleOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getUserId() { + return attributes.get("sub").toString(); + } + + @Override + public String getEmail() { + return attributes.get("email").toString(); + } +} diff --git a/src/main/java/com/sirius/spurt/common/oauth/service/OAuth2Service.java b/src/main/java/com/sirius/spurt/common/oauth/service/OAuth2Service.java new file mode 100644 index 0000000..44557c6 --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/oauth/service/OAuth2Service.java @@ -0,0 +1,50 @@ +package com.sirius.spurt.common.oauth.service; + +import static com.sirius.spurt.common.meta.ResultCode.SYSTEM_ERROR; + +import com.sirius.spurt.common.auth.PrincipalDetails; +import com.sirius.spurt.common.exception.GlobalException; +import com.sirius.spurt.common.meta.Social; +import com.sirius.spurt.common.oauth.info.OAuth2UserInfo; +import com.sirius.spurt.common.oauth.info.OAuth2UserInfoFactory; +import com.sirius.spurt.store.repository.database.entity.UserEntity; +import com.sirius.spurt.store.repository.database.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class OAuth2Service extends DefaultOAuth2UserService { + private final UserRepository userRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) { + OAuth2User oAuth2User = super.loadUser(oAuth2UserRequest); + + try { + return process(oAuth2UserRequest, oAuth2User); + } catch (Exception e) { + throw new GlobalException(SYSTEM_ERROR); + } + } + + private OAuth2User process(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) { + Social social = + Social.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId().toUpperCase()); + OAuth2UserInfo oAuth2UserInfo = + OAuth2UserInfoFactory.getOAuth2UserInfo(social, oAuth2User.getAttributes()); + UserEntity userEntity = getUserByUserId(oAuth2UserInfo.getUserId(), oAuth2UserInfo.getEmail()); + return PrincipalDetails.builder().userEntity(userEntity).build(); + } + + private UserEntity getUserByUserId(String userId, String email) { + UserEntity userEntity = userRepository.findByUserId(userId); + if (userEntity == null) { + return UserEntity.builder().userId(userId).email(email).build(); + } + return userEntity; + } +} diff --git a/src/main/java/com/sirius/spurt/common/oauth/user/OAuthUser.java b/src/main/java/com/sirius/spurt/common/oauth/user/OAuthUser.java new file mode 100644 index 0000000..9f10ab7 --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/oauth/user/OAuthUser.java @@ -0,0 +1,15 @@ +package com.sirius.spurt.common.oauth.user; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OAuthUser { + private String userId; + private String email; +} diff --git a/src/main/java/com/sirius/spurt/common/resolver/LoginUserResolver.java b/src/main/java/com/sirius/spurt/common/resolver/LoginUserResolver.java new file mode 100644 index 0000000..1963a87 --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/resolver/LoginUserResolver.java @@ -0,0 +1,106 @@ +package com.sirius.spurt.common.resolver; + +import static com.sirius.spurt.common.jwt.JwtUtils.ACCESS_TOKEN_NAME; +import static com.sirius.spurt.common.jwt.JwtUtils.REFRESH_TOKEN_NAME; +import static com.sirius.spurt.common.jwt.JwtUtils.TOKEN_TYPE; +import static com.sirius.spurt.common.meta.ResultCode.AUTHENTICATION_FAILED; + +import com.sirius.spurt.common.exception.GlobalException; +import com.sirius.spurt.common.jwt.JwtUtils; +import com.sirius.spurt.common.oauth.user.OAuthUser; +import com.sirius.spurt.common.resolver.user.LoginUser; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Arrays; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.core.MethodParameter; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Slf4j +@RequiredArgsConstructor +public class LoginUserResolver implements HandlerMethodArgumentResolver { + private final JwtUtils jwtUtils; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(LoginUser.class); + } + + @Override + public Object resolveArgument( + MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) + throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse(); + + if (isTestHeader(request)) { + return new LoginUser("admin", "email"); + } + + String accessCookie = getCookie(request, ACCESS_TOKEN_NAME); + String refreshCookie = getCookie(request, REFRESH_TOKEN_NAME); + log.info("accessCookie: " + accessCookie); + log.info("refreshCookie: " + refreshCookie); + if (isInValidCookie(accessCookie)) { + throw new GlobalException(AUTHENTICATION_FAILED); + } + + OAuthUser oAuthUser = getOAuthUser(response, accessCookie, refreshCookie); + return new LoginUser(oAuthUser.getUserId(), oAuthUser.getEmail()); + } + + private boolean isTestHeader(HttpServletRequest request) { + return StringUtils.hasText(request.getHeader("test")); + } + + private String getCookie(HttpServletRequest request, String key) { + if (ArrayUtils.isEmpty(request.getCookies())) { + return null; + } + + Cookie cookie = + Arrays.stream(request.getCookies()) + .filter(c -> key.equals(c.getName())) + .findFirst() + .orElse(null); + if (cookie == null) { + return null; + } + + return cookie.getValue(); + } + + private OAuthUser getOAuthUser( + HttpServletResponse response, String accessCookie, String refreshCookie) { + String accessToken = accessCookie.replace(TOKEN_TYPE, ""); + OAuthUser oAuthUser = jwtUtils.getOAuthUser(accessToken); + if (oAuthUser == null) { + if (isInValidCookie(refreshCookie)) { + throw new GlobalException(AUTHENTICATION_FAILED); + } + + String refreshToken = refreshCookie.replace(TOKEN_TYPE, ""); + oAuthUser = jwtUtils.getOAuthUser(refreshToken); + if (oAuthUser == null) { + throw new GlobalException(AUTHENTICATION_FAILED); + } + jwtUtils.updateTokens(response, oAuthUser.getUserId(), oAuthUser.getEmail()); + } + + return oAuthUser; + } + + private boolean isInValidCookie(String cookie) { + return !StringUtils.hasText(cookie) || !cookie.startsWith(TOKEN_TYPE); + } +} diff --git a/src/main/java/com/sirius/spurt/common/resolver/NonLoginUserResolver.java b/src/main/java/com/sirius/spurt/common/resolver/NonLoginUserResolver.java index cba1eec..b2446ac 100644 --- a/src/main/java/com/sirius/spurt/common/resolver/NonLoginUserResolver.java +++ b/src/main/java/com/sirius/spurt/common/resolver/NonLoginUserResolver.java @@ -2,8 +2,10 @@ import static com.sirius.spurt.common.jwt.JwtUtils.ACCESS_TOKEN_NAME; import static com.sirius.spurt.common.jwt.JwtUtils.REFRESH_TOKEN_NAME; +import static com.sirius.spurt.common.jwt.JwtUtils.TOKEN_TYPE; import com.sirius.spurt.common.jwt.JwtUtils; +import com.sirius.spurt.common.oauth.user.OAuthUser; import com.sirius.spurt.common.resolver.user.NonLoginUser; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; @@ -23,7 +25,6 @@ @RequiredArgsConstructor public class NonLoginUserResolver implements HandlerMethodArgumentResolver { private final JwtUtils jwtUtils; - private final String TOKEN_TYPE = "Bearer "; @Override public boolean supportsParameter(MethodParameter parameter) { @@ -46,27 +47,14 @@ public Object resolveArgument( String accessCookie = getCookie(request, ACCESS_TOKEN_NAME); String refreshCookie = getCookie(request, REFRESH_TOKEN_NAME); + log.info("accessCookie: " + accessCookie); + log.info("refreshCookie: " + refreshCookie); if (isInValidCookie(accessCookie)) { return new NonLoginUser(null); } - String accessToken = accessCookie.replace(TOKEN_TYPE, ""); - String userId = jwtUtils.getUserId(accessToken); - if (userId == null) { - if (isInValidCookie(refreshCookie)) { - return new NonLoginUser(null); - } - - String refreshToken = refreshCookie.replace(TOKEN_TYPE, ""); - userId = jwtUtils.getUserId(refreshToken); - if (userId == null) { - return new NonLoginUser(null); - } - - jwtUtils.updateTokens(response, userId); - } - - return new NonLoginUser(userId); + OAuthUser oAuthUser = getOAuthUser(response, accessCookie, refreshCookie); + return new NonLoginUser(oAuthUser.getUserId()); } private boolean isTestHeader(HttpServletRequest request) { @@ -90,6 +78,26 @@ private String getCookie(HttpServletRequest request, String key) { return cookie.getValue(); } + private OAuthUser getOAuthUser( + HttpServletResponse response, String accessCookie, String refreshCookie) { + String accessToken = accessCookie.replace(TOKEN_TYPE, ""); + OAuthUser oAuthUser = jwtUtils.getOAuthUser(accessToken); + if (oAuthUser == null) { + if (isInValidCookie(refreshCookie)) { + return OAuthUser.builder().build(); + } + + String refreshToken = refreshCookie.replace(TOKEN_TYPE, ""); + oAuthUser = jwtUtils.getOAuthUser(refreshToken); + if (oAuthUser == null) { + return OAuthUser.builder().build(); + } + jwtUtils.updateTokens(response, oAuthUser.getUserId(), oAuthUser.getEmail()); + } + + return oAuthUser; + } + private boolean isInValidCookie(String cookie) { return !StringUtils.hasText(cookie) || !cookie.startsWith(TOKEN_TYPE); } diff --git a/src/main/java/com/sirius/spurt/common/resolver/user/LoginUser.java b/src/main/java/com/sirius/spurt/common/resolver/user/LoginUser.java new file mode 100644 index 0000000..40d8cc1 --- /dev/null +++ b/src/main/java/com/sirius/spurt/common/resolver/user/LoginUser.java @@ -0,0 +1,15 @@ +package com.sirius.spurt.common.resolver.user; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LoginUser { + private String userId; + private String email; +} diff --git a/src/main/java/com/sirius/spurt/common/validator/TokenValidator.java b/src/main/java/com/sirius/spurt/common/validator/TokenValidator.java index ef7dd9f..43fd851 100644 --- a/src/main/java/com/sirius/spurt/common/validator/TokenValidator.java +++ b/src/main/java/com/sirius/spurt/common/validator/TokenValidator.java @@ -3,6 +3,7 @@ import static com.sirius.spurt.common.meta.ResultCode.AUTHENTICATION_FAILED; import com.sirius.spurt.common.exception.GlobalException; +import com.sirius.spurt.common.oauth.user.OAuthUser; public class TokenValidator { public static void validateCookie(String cookie) { @@ -11,8 +12,8 @@ public static void validateCookie(String cookie) { } } - public static void validateUserId(String userId) { - if (!isExistUserId(userId)) { + public static void validateOAuthUser(OAuthUser oAuthUser) { + if (!isExistOAuthUser(oAuthUser)) { throw new GlobalException(AUTHENTICATION_FAILED); } } @@ -21,7 +22,7 @@ private static boolean isExistCookie(String cookie) { return cookie != null; } - private static boolean isExistUserId(String userId) { - return userId != null; + private static boolean isExistOAuthUser(OAuthUser oAuthUser) { + return oAuthUser != null; } } diff --git a/src/main/java/com/sirius/spurt/service/controller/jobgroup/JobGroupController.java b/src/main/java/com/sirius/spurt/service/controller/jobgroup/JobGroupController.java index c4e27a3..ed16d72 100644 --- a/src/main/java/com/sirius/spurt/service/controller/jobgroup/JobGroupController.java +++ b/src/main/java/com/sirius/spurt/service/controller/jobgroup/JobGroupController.java @@ -1,6 +1,7 @@ package com.sirius.spurt.service.controller.jobgroup; import com.sirius.spurt.common.auth.PrincipalDetails; +import com.sirius.spurt.common.resolver.user.LoginUser; import com.sirius.spurt.service.business.jobgroup.SaveJobGroupBusiness; import com.sirius.spurt.service.business.jobgroup.UpdateJobGroupBusiness; import com.sirius.spurt.service.controller.RestResponse; @@ -28,10 +29,9 @@ public class JobGroupController { */ @PostMapping public RestResponse saveJobGroup( - @AuthenticationPrincipal PrincipalDetails principalDetails, - @RequestBody SaveJobGroupBusiness.Dto dto) { - dto.setUserId(principalDetails.getUserEntity().getUserId()); - dto.setEmail(principalDetails.getUserEntity().getEmail()); + LoginUser loginUser, @RequestBody SaveJobGroupBusiness.Dto dto) { + dto.setUserId(loginUser.getUserId()); + dto.setEmail(loginUser.getEmail()); return RestResponse.success(saveJobGroupBusiness.execute(dto)); }