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: 소셜 로그인 관련 500 에러 수정 및 OAuth 로직 일부 개선 #112

Merged
merged 7 commits into from
Jan 18, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,26 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequiredArgsConstructor
public class OAuthLoginController {

private final OAuthService oAuthService;

@GetMapping("/logins/callbacks/{provider}")
@ResponseStatus(HttpStatus.OK)
public TokenResponse oAuthLogin(
@PathVariable String provider,
@RequestParam String code) {
return oAuthService.oAuthLogin(provider, code);
public TokenResponse oAuthLogin(@PathVariable String provider,
@RequestParam String code,
@RequestParam String state) {
return oAuthService.oAuthLogin(provider, code, state);
}

@GetMapping("/favicon.ico")
@ResponseStatus(HttpStatus.OK)
public Void favicon() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public KakaoOAuthUserInfo(Map<String, Object> attributes) {

@Override
public String getOAuthId() {
return (String) attributes.get("id");
return String.valueOf(attributes.get("id"));
}
}
22 changes: 2 additions & 20 deletions src/main/java/net/teumteum/auth/domain/OAuthToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,11 @@
import com.fasterxml.jackson.annotation.JsonProperty;

public record OAuthToken(
@JsonProperty("token_type")
String tokenType,
@JsonProperty("access_token")
String accessToken,
String scope,

@JsonProperty("expires_in")
Integer expiresIn
@JsonProperty("token_type")
String tokenType
) {

public String getTokenType() {
return this.tokenType;
}

public String getAccessToken() {
return this.accessToken;
}

public String getScope() {
return this.scope;
}

public Integer getExpiresIn() {
return this.expiresIn;
}
}
38 changes: 24 additions & 14 deletions src/main/java/net/teumteum/auth/service/OAuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static net.teumteum.core.security.Authenticated.카카오;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;

import java.net.URLEncoder;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -41,11 +42,11 @@ public class OAuthService {
private final UserConnector userConnector;


public TokenResponse oAuthLogin(String registrationId, String code) {
public TokenResponse oAuthLogin(String registrationId, String code, String state) {
ClientRegistration clientRegistration = inMemoryClientRegistrationRepository.findByRegistrationId(
registrationId);
Authenticated authenticated = getAuthenticated(clientRegistration.getRegistrationId());
OAuthUserInfo oAuthUserInfo = getOAuthUserInfo(clientRegistration, authenticated, code);
OAuthUserInfo oAuthUserInfo = getOAuthUserInfo(clientRegistration, authenticated, code, state);
return makeResponse(oAuthUserInfo, authenticated);
}

Expand All @@ -57,8 +58,9 @@ private Authenticated getAuthenticated(String registrationId) {
}

private OAuthUserInfo getOAuthUserInfo(ClientRegistration clientRegistration, Authenticated authenticated,
String code) {
Map<String, Object> oAuthAttribute = getOAuthAttribute(clientRegistration, getToken(clientRegistration, code));
String code, String state) {
Map<String, Object> oAuthAttribute = getOAuthAttribute(clientRegistration,
getToken(clientRegistration, code, state));
if (authenticated == 네이버) {
return new NaverOAuthUserInfo(oAuthAttribute);
}
Expand All @@ -73,31 +75,39 @@ private TokenResponse makeResponse(OAuthUserInfo oAuthUserInfo, Authenticated au
.orElseGet(() -> new TokenResponse(oauthId));
}

private OAuthToken getToken(ClientRegistration clientRegistration, String code, String state) {
return WebClient.create().post()
.uri(clientRegistration.getProviderDetails().getTokenUri())
.headers(header -> {
header.setContentType(APPLICATION_FORM_URLENCODED);
header.setAcceptCharset(Collections.singletonList(UTF_8));
}).bodyValue(tokenRequest(clientRegistration, code, state))
.retrieve()
.bodyToMono(OAuthToken.class).block();
}

private Map<String, Object> getOAuthAttribute(ClientRegistration clientRegistration, OAuthToken oAuthToken) {
return WebClient.create().get().uri(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
.headers(header -> header.setBearerAuth(oAuthToken.getAccessToken())).retrieve()
return WebClient.create().get()
.uri(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
.headers(header -> header.setBearerAuth(oAuthToken.accessToken())).retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
}).block();
}

private OAuthToken getToken(ClientRegistration clientRegistration, String code) {
return WebClient.create().post().uri(clientRegistration.getProviderDetails().getTokenUri()).headers(header -> {
header.setContentType(APPLICATION_FORM_URLENCODED);
header.setAcceptCharset(Collections.singletonList(UTF_8));
}).bodyValue(tokenRequest(clientRegistration, code)).retrieve().bodyToMono(OAuthToken.class).block();
}

private Optional<User> getUser(String oauthId, Authenticated authenticated) {
return this.userConnector.findByAuthenticatedAndOAuthId(authenticated, oauthId);
}

private MultiValueMap<String, String> tokenRequest(ClientRegistration clientRegistration, String code) {
private MultiValueMap<String, String> tokenRequest(ClientRegistration clientRegistration, String code,
String state) {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("code", code);
formData.add("grant_type", "authorization_code");
formData.add("grant_type", clientRegistration.getAuthorizationGrantType().getValue());
formData.add("redirect_uri", clientRegistration.getRedirectUri());
formData.add("client_secret", clientRegistration.getClientSecret());
formData.add("client_id", clientRegistration.getClientId());
formData.add("state", URLEncoder.encode(state, UTF_8));
return formData;
}
}
15 changes: 9 additions & 6 deletions src/main/java/net/teumteum/core/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
@RequiredArgsConstructor
public class SecurityConfig {

private static final String[] PATTERNS = {"/css/**", "/images/**", "/js/**", "/favicon.ico", "/h2-console/**",
"/logins/**"};
private static final String[] PATTERNS = {"/css/**", "/images/**", "/js/**", "/favicon.ico/**", "/h2-console/**",
"/logins/**", "/auth/**"};

private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler accessDeniedHandler;
Expand All @@ -38,14 +38,18 @@ public class SecurityConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring()
.requestMatchers("/css/**", "/js/**", "/img/**", "/favicon.ico", "/error");
.requestMatchers("/css/**", "/js/**",
"/favicon.ico", "/resources/**"

);
}


@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable).cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(request -> request.requestMatchers("/auth/**", "/logins/**").permitAll()
.requestMatchers(HttpMethod.POST, "/users/registers").permitAll().requestMatchers(PATTERNS).permitAll()
.authorizeHttpRequests(request -> request.requestMatchers(PATTERNS).permitAll()
.requestMatchers(HttpMethod.POST, "/users/registers").permitAll()
.anyRequest().authenticated()).httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(STATELESS))
Expand All @@ -61,7 +65,6 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("http://localhost:3000");
config.addAllowedOrigin("https://api.teum.org");
config.addAllowedHeader("*");
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
config.addExposedHeader("Authorization");
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application-auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ spring:

naver:
client-id: ${NAVER_CLIENT_ID}
client-secret: ${NAVER_CLIENT_ID}
client-secret: ${NAVER_CLIENT_SECRET}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 key값 기준으로 강제 바인딩 해주고 있어서 변경해도 배포에 영향은 없을거에요

redirect-uri: ${NAVER_REDIRECT_URI}
authorization-grant-type: authorization_code
scope:
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/application-datasource.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ spring:

datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url:
url: ${DATABASE_URL}
username: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
hikari:
connection-timeout: 3000
maximum-pool-size: 80

flyway:
url:
url: ${DATABASE_URL}
user: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
baseline-on-migrate: true
Empty file.
Loading