Skip to content

Commit

Permalink
feat: google oauth2
Browse files Browse the repository at this point in the history
  • Loading branch information
alstn113 committed Dec 11, 2024
1 parent 736d1b4 commit 381104d
Show file tree
Hide file tree
Showing 18 changed files with 201 additions and 14 deletions.
3 changes: 3 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
OAUTH_GITHUB_CLIENT_ID=
OAUTH_GITHUB_CLIENT_SECRET=

OAUTH_GOOGLE_CLIENT_ID=
OAUTH_GOOGLE_CLIENT_SECRET=

DB_URL=
DB_USERNAME=
DB_PASSWORD=
Expand Down
6 changes: 3 additions & 3 deletions server/src/main/java/com/fluffy/DataInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,23 @@ private void init() {
Member member1 = memberRepository.save(new Member(
"[email protected]",
OAuth2Provider.GITHUB,
111111L,
"111111",
"Alice",
"https://cdn-icons-png.flaticon.com/128/4472/4472552.png"
));

Member member2 = memberRepository.save(new Member(
"[email protected]",
OAuth2Provider.GITHUB,
222222L,
"222222",
"Bob",
"https://cdn-icons-png.flaticon.com/128/4472/4472525.png"
));

Member member3 = memberRepository.save(new Member(
"[email protected]",
OAuth2Provider.GITHUB,
333333L,
"333333",
"Charlie",
"https://cdn-icons-png.flaticon.com/128/4472/4472516.png"
));
Expand Down
6 changes: 3 additions & 3 deletions server/src/main/java/com/fluffy/auth/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ public class Member extends AuditableEntity {
private OAuth2Provider provider;

@Column(nullable = false)
private Long socialId;
private String socialId;

@Column(nullable = false)
private String name;

@Column(nullable = false)
private String avatarUrl;

public Member(String email, OAuth2Provider provider, Long socialId, String name, String avatarUrl) {
public Member(String email, OAuth2Provider provider, String socialId, String name, String avatarUrl) {
this(null, email, provider, socialId, name, avatarUrl);
}

public Member(Long id, String email, OAuth2Provider provider, Long socialId, String name, String avatarUrl) {
public Member(Long id, String email, OAuth2Provider provider, String socialId, String name, String avatarUrl) {
this.id = id;
this.email = email;
this.provider = provider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public interface MemberRepository extends Repository<Member, Long> {

Optional<Member> findById(Long id);

Optional<Member> findBySocialIdAndProvider(Long socialId, OAuth2Provider provider);
Optional<Member> findBySocialIdAndProvider(String socialId, OAuth2Provider provider);

default Member getById(Long id) {
return findById(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
public enum OAuth2Provider {

GITHUB,
GOOGLE,
;

public static OAuth2Provider from(String provider) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import static com.fluffy.auth.domain.QMember.member;
import static com.fluffy.exam.domain.QExam.exam;
import static com.fluffy.exam.domain.QQuestion.question;
import static com.querydsl.core.types.ExpressionUtils.count;

import com.fluffy.exam.domain.ExamRepositoryCustom;
import com.fluffy.exam.domain.ExamStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import jakarta.annotation.Nullable;

public record OAuth2UserInfo(
Long socialId,
String socialId,
String name,
@Nullable String email,
String avatarUrl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@JsonNaming(SnakeCaseStrategy.class)
public record GithubUserInfoResponse(
Long id,
String id,
String login,
String avatarUrl,
@Nullable String email
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.fluffy.oauth2.infra.google;

import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.MediaType.APPLICATION_JSON;

import com.fluffy.oauth2.infra.google.dto.GoogleAccessTokenRequest;
import com.fluffy.oauth2.infra.google.dto.GoogleAccessTokenResponse;
import com.fluffy.oauth2.infra.google.dto.GoogleUserInfoResponse;
import java.time.Duration;
import org.springframework.boot.web.client.ClientHttpRequestFactories;
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

@Component
public class GoogleOAuth2Client {

private final GoogleOAuth2Properties properties;
private final RestClient restClient;

public GoogleOAuth2Client(GoogleOAuth2Properties properties, RestClient.Builder restClientBuilder) {
this.properties = properties;
this.restClient = createRestClient(restClientBuilder);
}

private RestClient createRestClient(RestClient.Builder restClientBuilder) {
return restClientBuilder.requestFactory(clientHttpRequestFactory()).build();
}

private ClientHttpRequestFactory clientHttpRequestFactory() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
.withConnectTimeout(Duration.ofSeconds(3))
.withReadTimeout(Duration.ofSeconds(5));

return ClientHttpRequestFactories.get(settings);
}

public GoogleAccessTokenResponse fetchAccessToken(String code) {
GoogleAccessTokenRequest request = new GoogleAccessTokenRequest(
code,
properties.clientId(),
properties.clientSecret(),
"authorization_code",
properties.redirectUri()
);

return restClient.post()
.uri("https://oauth2.googleapis.com/token")
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON)
.body(request)
.retrieve()
.body(GoogleAccessTokenResponse.class);
}

public GoogleUserInfoResponse fetchUserInfo(String accessToken) {
return restClient.get()
.uri("https://www.googleapis.com/oauth2/v2/userinfo")
.header(AUTHORIZATION, String.format("Bearer %s", accessToken))
.accept(APPLICATION_JSON)
.retrieve()
.body(GoogleUserInfoResponse.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.fluffy.oauth2.infra.google;

import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("auth.oauth2.google")
public record GoogleOAuth2Properties(
@NotBlank String clientId,
@NotBlank String clientSecret,
@NotBlank String redirectUri,
@NotBlank String clientUri
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.fluffy.oauth2.infra.google;

import com.fluffy.auth.domain.OAuth2Provider;
import com.fluffy.oauth2.application.OAuth2Strategy;
import com.fluffy.oauth2.domain.OAuth2UserInfo;
import com.fluffy.oauth2.infra.google.dto.GoogleAccessTokenResponse;
import com.fluffy.oauth2.infra.google.dto.GoogleUserInfoResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;

@Component
@RequiredArgsConstructor
public class GoogleOAuth2Strategy implements OAuth2Strategy {

private final GoogleOAuth2Client googleOAuth2Client;
private final GoogleOAuth2Properties properties;

@Override
public String buildOAuth2LoginUrl(String next) {
return UriComponentsBuilder.fromHttpUrl("https://accounts.google.com/o/oauth2/v2/auth")
.queryParam("client_id", properties.clientId())
.queryParam("redirect_uri", properties.redirectUri())
.queryParam("response_type", "code")
.queryParam("scope", "email profile")
.build()
.toUriString();
}

@Override
public OAuth2UserInfo fetchOAuth2UserInfo(String code) {
GoogleAccessTokenResponse accessTokenResponse = googleOAuth2Client.fetchAccessToken(code);
String accessToken = accessTokenResponse.accessToken();

GoogleUserInfoResponse userInfoResponse = googleOAuth2Client.fetchUserInfo(accessToken);

return userInfoResponse.toOAuth2UserInfo();
}

@Override
public String buildClientRedirectUrl(String next) {
return UriComponentsBuilder.fromHttpUrl(properties.clientUri())
.path(next)
.build()
.toUriString();
}

@Override
public OAuth2Provider getProvider() {
return OAuth2Provider.GOOGLE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.fluffy.oauth2.infra.google.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(SnakeCaseStrategy.class)
public record GoogleAccessTokenRequest(
String code,
String clientId,
String clientSecret,
String grantType,
String redirectUri
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.fluffy.oauth2.infra.google.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(SnakeCaseStrategy.class)
public record GoogleAccessTokenResponse(String accessToken) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.fluffy.oauth2.infra.google.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fluffy.oauth2.domain.OAuth2UserInfo;

@JsonNaming(SnakeCaseStrategy.class)
public record GoogleUserInfoResponse(
String id,
String email,
String name,
String picture
) {

public OAuth2UserInfo toOAuth2UserInfo() {
return new OAuth2UserInfo(
id,
name,
email,
picture
);
}
}
5 changes: 5 additions & 0 deletions server/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ auth:
client-secret: ${OAUTH_GITHUB_CLIENT_SECRET}
redirect-uri: ${api-host}/api/v1/auth/oauth2/callback/github
client-uri: ${client-host}
google:
client-id: ${OAUTH_GOOGLE_CLIENT_ID}
client-secret: ${OAUTH_GOOGLE_CLIENT_SECRET}
redirect-uri: ${api-host}/api/v1/auth/oauth2/callback/google
client-uri: ${client-host}

logging:
level:
Expand Down
5 changes: 5 additions & 0 deletions server/src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ auth:
client-secret: ${OAUTH_GITHUB_CLIENT_SECRET}
redirect-uri: ${api-host}/api/v1/auth/oauth2/callback/github
client-uri: ${client-host}
google:
client-id: ${OAUTH_GOOGLE_CLIENT_ID}
client-secret: ${OAUTH_GOOGLE_CLIENT_SECRET}
redirect-uri: ${api-host}/api/v1/auth/oauth2/callback/google
client-uri: ${client-host}

logging:
level:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static MemberBuilder defaultMember() {
.withId(null)
.withEmail("[email protected]")
.withProvider(OAuth2Provider.GITHUB)
.withSocialId(1234567890L)
.withSocialId("1234567890")
.withName("example")
.withAvatarUrl("https://example.com/image.jpg");
}
Expand All @@ -20,7 +20,7 @@ public static class MemberBuilder {
private Long id;
private String email;
private OAuth2Provider provider;
private Long socialId;
private String socialId;
private String name;
private String avatarUrl;

Expand All @@ -39,7 +39,7 @@ public MemberBuilder withProvider(OAuth2Provider provider) {
return this;
}

public MemberBuilder withSocialId(Long socialId) {
public MemberBuilder withSocialId(String socialId) {
this.socialId = socialId;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class ExamPeriodTest {
void startAtAndEndAtShouldBeTruncatedToMinutes() {
// given
LocalDateTime startAt = LocalDateTime.now();
System.out.println(startAt);
LocalDateTime endAt = startAt.plusMinutes(1);

// when
Expand Down

0 comments on commit 381104d

Please sign in to comment.