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

[FEAT] Github Oauth2 로그인 / 인증,인가 기능 구현 #4

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "src/main/resources/HACKER-CONFIG"]
path = src/main/resources/HACKER-CONFIG
url = https://github.com/zaranaramorimori/HACKER-CONFIG.git
7 changes: 5 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'

// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}

tasks.named('test') {
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/com/teamzzong/hacker/application/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.teamzzong.hacker.application;

import java.util.Optional;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.teamzzong.hacker.application.oauth.OauthClients;
import com.teamzzong.hacker.domain.AuthTokenPayload;
import com.teamzzong.hacker.domain.AuthTokenType;
import com.teamzzong.hacker.domain.AuthTokens;
import com.teamzzong.hacker.domain.Member;
import com.teamzzong.hacker.domain.SocialType;
import com.teamzzong.hacker.dto.LoginResponse;
import com.teamzzong.hacker.dto.SignUpRequest;
import com.teamzzong.hacker.dto.SignUpResponse;
import com.teamzzong.hacker.dto.UserInfo;
import com.teamzzong.hacker.infrastructure.MemberRepository;

import lombok.RequiredArgsConstructor;

@Service
@Transactional
@RequiredArgsConstructor
public class AuthService {

private final OauthClients oauthClients;
private final MemberRepository memberRepository;
private final AuthTokenProvider tokenProvider;

public LoginResponse login(SocialType socialType, String code) {
UserInfo userInfo = oauthClients.requestUserInfo(socialType, code);
Optional<Member> optionalMember = memberRepository.findBySocialTypeAndSocialId(socialType, userInfo.socialId());
if (optionalMember.isEmpty()) {
return LoginResponse.signUp(userInfo);
}
Member member = optionalMember.get();
return LoginResponse.login(createAuthToken(member));
}

private AuthTokens createAuthToken(Member member) {
AuthTokenPayload payload = new AuthTokenPayload(member.getId());
String accessToken = tokenProvider.generate(AuthTokenType.ACCESS, payload);
String refreshToken = tokenProvider.generate(AuthTokenType.REFRESH, payload);
return new AuthTokens(accessToken, refreshToken);
}

public SignUpResponse signUp(SignUpRequest request) {
validateExistMember(request.socialType(), request.socialId());
validateNickname(request.nickname());
Member member = memberRepository.save(new Member(request.socialType(), request.socialId(), request.nickname()));
return SignUpResponse.from(createAuthToken(member));
}

private void validateExistMember(SocialType socialType, String socialId) {
memberRepository.findBySocialTypeAndSocialId(socialType, socialId)
.ifPresent(member -> {
throw new IllegalArgumentException("이미 존재하는 회원입니다.");
});
}

private void validateNickname(String nickname) {
memberRepository.findByNickname(nickname)
.ifPresent(member -> {
throw new IllegalArgumentException("이미 존재하는 닉네임입니다.");
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.teamzzong.hacker.application;

import com.teamzzong.hacker.domain.AuthTokenPayload;
import com.teamzzong.hacker.domain.AuthTokenType;

public interface AuthTokenProvider {

String generate(AuthTokenType type, AuthTokenPayload payload);

AuthTokenPayload extract(AuthTokenType type, String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.teamzzong.hacker.application.oauth;

import com.teamzzong.hacker.domain.SocialType;
import com.teamzzong.hacker.dto.UserInfo;

public interface OauthClient {

SocialType socialType();

String requestAccessToken(String code);

UserInfo requestUserInfo(String accessToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.teamzzong.hacker.application.oauth;

import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import com.teamzzong.hacker.domain.SocialType;
import com.teamzzong.hacker.dto.UserInfo;

public class OauthClients {

private final Map<SocialType, OauthClient> clients;

public OauthClients(Set<OauthClient> clients) {
this.clients = clients.stream()
.collect(Collectors.toMap(OauthClient::socialType, oauthClient -> oauthClient));
}

public UserInfo requestUserInfo(SocialType socialType, String code) {
OauthClient oauthClient = clients.get(socialType);
if (oauthClient == null) {
throw new IllegalArgumentException("올바르지 않은 SocialType 입니다.");
}
String accessToken = oauthClient.requestAccessToken(code);
return oauthClient.requestUserInfo(accessToken);
}

}
23 changes: 23 additions & 0 deletions src/main/java/com/teamzzong/hacker/config/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.teamzzong.hacker.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.teamzzong.hacker.application.AuthTokenProvider;
import com.teamzzong.hacker.infrastructure.jwt.JwtAuthTokenProvider;

import lombok.AllArgsConstructor;

@Configuration
@EnableConfigurationProperties(JwtAuthProperty.class)
@AllArgsConstructor
public class AuthConfig {

private final JwtAuthProperty property;

@Bean
public AuthTokenProvider authTokenProvider() {
return JwtAuthTokenProvider.from(property);
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/teamzzong/hacker/config/GithubOauthProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.teamzzong.hacker.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "oauth2.github")
public record GithubOauthProperty(
String clientId,
String clientSecret,
String redirectUri,
String tokenUri,
String userInfoUri
) {

}
19 changes: 19 additions & 0 deletions src/main/java/com/teamzzong/hacker/config/JwtAuthProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.teamzzong.hacker.config;

import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

import com.teamzzong.hacker.domain.AuthTokenType;

import lombok.AllArgsConstructor;
import lombok.Getter;

@ConfigurationProperties(prefix = "auth")
@AllArgsConstructor
@Getter
public class JwtAuthProperty {

private final Map<AuthTokenType, Integer> expirationMinute;
private final Map<AuthTokenType, String> secretKey;
}
24 changes: 24 additions & 0 deletions src/main/java/com/teamzzong/hacker/config/LoginConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.teamzzong.hacker.config;

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.teamzzong.hacker.presentation.LoginMemberResolver;

@Configuration
public class LoginConfig implements WebMvcConfigurer {

private final LoginMemberResolver loginMemberResolver;

public LoginConfig(LoginMemberResolver loginMemberResolver) {
this.loginMemberResolver = loginMemberResolver;
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginMemberResolver);
}
}
20 changes: 20 additions & 0 deletions src/main/java/com/teamzzong/hacker/config/OauthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.teamzzong.hacker.config;

import java.util.Set;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.teamzzong.hacker.application.oauth.OauthClient;
import com.teamzzong.hacker.application.oauth.OauthClients;

@Configuration
@EnableConfigurationProperties({GithubOauthProperty.class})
public class OauthConfig {

@Bean
public OauthClients oAuth2Clients(Set<OauthClient> clients) {
return new OauthClients(clients);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.teamzzong.hacker.domain;

public record AuthTokenPayload(
Long memberId
) {
}
7 changes: 7 additions & 0 deletions src/main/java/com/teamzzong/hacker/domain/AuthTokenType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.teamzzong.hacker.domain;

public enum AuthTokenType {
ACCESS,
REFRESH,
;
}
12 changes: 12 additions & 0 deletions src/main/java/com/teamzzong/hacker/domain/AuthTokens.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.teamzzong.hacker.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public class AuthTokens {

private final String accessToken;
private final String refreshToken;
}
20 changes: 20 additions & 0 deletions src/main/java/com/teamzzong/hacker/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
package com.teamzzong.hacker.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Enumerated(value = EnumType.STRING)
private SocialType socialType;

private String socialId;

private String nickname;

public Member(SocialType socialType, String socialId, String nickname) {
this(null, socialType, socialId, nickname);
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/teamzzong/hacker/domain/SocialType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.teamzzong.hacker.domain;

public enum SocialType {
GITHUB,
;
}
6 changes: 6 additions & 0 deletions src/main/java/com/teamzzong/hacker/dto/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.teamzzong.hacker.dto;

public record LoginMember(
Long memberId
) {
}
26 changes: 26 additions & 0 deletions src/main/java/com/teamzzong/hacker/dto/LoginResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.teamzzong.hacker.dto;

import com.teamzzong.hacker.domain.AuthTokens;

public record LoginResponse(
Boolean isNew,
LoginTokenResponse tokens,
LoginUserInfoResponse userInfo
) {

public static LoginResponse login(AuthTokens authTokens) {
return new LoginResponse(
false,
new LoginTokenResponse(authTokens.getAccessToken(), authTokens.getRefreshToken()),
null
);
}

public static LoginResponse signUp(UserInfo userInfo) {
return new LoginResponse(
true,
null,
LoginUserInfoResponse.from(userInfo)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.teamzzong.hacker.dto;

public record LoginTokenResponse(
String accessToken,
String refreshToken
) {
}
20 changes: 20 additions & 0 deletions src/main/java/com/teamzzong/hacker/dto/LoginUserInfoResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.teamzzong.hacker.dto;

import com.teamzzong.hacker.domain.SocialType;

public record LoginUserInfoResponse(
SocialType socialType,
String socialId,
String username,
String profileImage
) {

public static LoginUserInfoResponse from(UserInfo userInfo) {
return new LoginUserInfoResponse(
userInfo.socialType(),
userInfo.socialId(),
userInfo.nickname(),
userInfo.profileImage()
);
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/teamzzong/hacker/dto/SignUpRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.teamzzong.hacker.dto;

import com.teamzzong.hacker.domain.SocialType;

public record SignUpRequest(
SocialType socialType,
String socialId,
String username,
String nickname
) {
}
Loading
Loading