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] oauth2를 이용한 로그인 구현 #10

Merged
merged 19 commits into from
Oct 15, 2024
Merged
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
6 changes: 6 additions & 0 deletions .github/workflows/github-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ jobs:
port: 22
key: ${{ secrets.PRIVATE_KEY }}
script: |
# 기존 컨테이너 중지 및 제거
if [ "$(sudo docker ps -q -f name=xcellent-be)" ]; then
sudo docker stop xcellent-be
sudo docker rm xcellent-be
fi

sudo docker ps
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/xcellent-be
sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/xcellent-be
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ application.yaml
application-dev.yaml
application-local.yaml
application-jwt.yaml
application-oauth.yaml
application-mail.yaml

### macOS ###
.DS_Store
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar

# 운영 및 개발에서 사용되는 환경 설정을 분리
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=dev,jwt", "/app.jar"]
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=dev,jwt,oauth,mail", "/app.jar"]
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.6'
implementation 'com.auth0:java-jwt:4.4.0'

// OAuth2
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

// SMTP, Redis
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'

Expand Down
20 changes: 18 additions & 2 deletions src/main/java/com/leets/xcellentbe/domain/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import com.leets.xcellentbe.domain.shared.BaseTimeEntity;
import com.leets.xcellentbe.domain.shared.UserStatus;

import com.leets.xcellentbe.domain.user.Role;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -75,6 +75,12 @@ public class User extends BaseTimeEntity {
@Enumerated(EnumType.STRING)
private UserStatus userStatus;

@NotNull
@Column
@Enumerated(EnumType.STRING)
private Role userRole;


@NotNull
@Column(length=4)
private int userBirthYear;
Expand All @@ -88,14 +94,15 @@ public class User extends BaseTimeEntity {
private int userBirthDay;

@Builder
private User(String customId, String email, String userName, String password, String phoneNumber, String description, int userBirthDay, int userBirthMonth, int userBirthYear) {
private User(String customId, String email, String userName, String password, String phoneNumber, String description, int userBirthDay, int userBirthMonth, int userBirthYear, String socialEmail, Role userRole) {
this.customId = customId;
this.email = email;
this.userName = userName;
this.password = password;
this.phoneNumber= phoneNumber;
this.description = description;
this.userStatus = UserStatus.ACTIVE;
this.userRole = userRole;
this.userBirthMonth = userBirthMonth;
this.userBirthYear = userBirthYear;
this.userBirthDay = userBirthDay;
Expand All @@ -115,6 +122,15 @@ public static User create(String customId, String email, String userName, String
.userBirthDay(userBirthDay)
.userBirthYear(userBirthYear)
.userBirthMonth(userBirthMonth)
.userRole(Role.USER)
.build();
}

public static User socialCreate(String email, String socialEmail) {
return User.builder()
.email(email)
.userRole(Role.GUEST)
.socialEmail(socialEmail)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByCustomId(String customId);;
Optional<User> findByRefreshToken(String refreshToken);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.leets.xcellentbe.global.auth.email;

import com.leets.xcellentbe.global.error.ErrorCode;
import com.leets.xcellentbe.global.error.exception.CommonException;

public class AuthCodeAlreadySentException extends CommonException {
public AuthCodeAlreadySentException() {
super(ErrorCode.AUTH_CODE_ALREADY_SENT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.leets.xcellentbe.global.auth.email;

import com.leets.xcellentbe.global.error.ErrorCode;
import com.leets.xcellentbe.global.error.exception.CommonException;

public class EmailCannotBeSent extends CommonException
{
public EmailCannotBeSent()
{
super(ErrorCode.EMAIL_CANNOT_BE_SENT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.leets.xcellentbe.global.auth.email;

import jakarta.validation.constraints.Email;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class EmailCheckDto {
@Email
private String email;
private String authNum;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.leets.xcellentbe.global.auth.email;

import lombok.Getter;

@Getter
public class EmailRequestDto {
private String email;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.leets.xcellentbe.global.auth.email;

import org.springframework.stereotype.Service;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import java.util.Random;

import com.leets.xcellentbe.global.error.exception.custom.InvalidInputValueException;

@Transactional
@RequiredArgsConstructor
@Service
@Slf4j
public class EmailService {
private final RedisService redisService;
private final JavaMailSender mailSender;
private int authNumber;

//임의의 6자리 양수를 반환합니다.
public void makeRandomNumber() {
Random r = new Random();
this.authNumber = 100000 + r.nextInt(900000);
}


//mail을 어디서 보내는지, 어디로 보내는지 , 인증 번호를 html 형식으로 어떻게 보내는지 작성합니다.
public String joinEmail(String email) throws MessagingException {
makeRandomNumber();
String toMail = email;
String title = "회원 가입 인증 이메일 입니다."; // 이메일 제목
String content =
"인증 번호는 " + authNumber + "입니다." +
"<br>" +
"인증번호를 제대로 입력해주세요"; //이메일 내용 삽입
mailSend(toMail, title, content);
return Integer.toString(authNumber);
}

//이메일을 전송합니다.
public void mailSend(String toMail, String title, String content) throws MessagingException {

if(redisService.getData(toMail)!=null){
throw new AuthCodeAlreadySentException();
}

try {
MimeMessage message = mailSender.createMimeMessage();//JavaMailSender 객체를 사용하여 MimeMessage 객체를 생성
MimeMessageHelper helper = new MimeMessageHelper(message, true,
"utf-8");// true를 전달하여 multipart 형식의 메시지를 지원하고, "utf-8"을 전달하여 문자 인코딩을 설정
helper.setTo("h");//이메일의 수신자 주소 설정
helper.setSubject(title);//이메일의 제목을 설정
helper.setText(content, true);//이메일의 내용 설정 두 번째 매개 변수에 true를 설정하여 html 설정으로한다.
mailSender.send(message);
}
catch(Exception e) {
throw new EmailCannotBeSent();
}
redisService.setDataExpire(toMail,Integer.toString(authNumber));
}

public String checkAuthNum(String email, String authNum) {
String storedAuthNum = redisService.getData(email);

if (storedAuthNum == null) {
return "인증번호가 만료되었습니다.";
}
else if(!storedAuthNum.equals(authNum)){
throw new InvalidInputValueException();
}
else {
return "인증에 성공하였습니다.";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.leets.xcellentbe.global.auth.email;

import java.time.Duration;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
@Service
@RequiredArgsConstructor
public class RedisService {
private static final long AUTH_CODE_EXPIRE_SECONDS = 300;

private final StringRedisTemplate redisTemplate;//Redis에 접근하기 위한 Spring의 Redis 템플릿 클래스

public String getData(String key){//지정된 키(key)에 해당하는 데이터를 Redis에서 가져오는 메서드
ValueOperations<String,String> valueOperations=redisTemplate.opsForValue();
return valueOperations.get(key);
}
public void setData(String key,String value){//지정된 키(key)에 값을 저장하는 메서드
ValueOperations<String,String> valueOperations=redisTemplate.opsForValue();
valueOperations.set(key,value);
}
public void setDataExpire(String key,String value){//지정된 키(key)에 값을 저장하고, 지정된 시간(duration) 후에 데이터가 만료되도록 설정하는 메서드
ValueOperations<String,String> valueOperations=redisTemplate.opsForValue();
Duration expireDuration=Duration.ofSeconds(AUTH_CODE_EXPIRE_SECONDS);
valueOperations.set(key,value,expireDuration);
}
public void deleteData(String key){//지정된 키(key)에 해당하는 데이터를 Redis에서 삭제하는 메서드
redisTemplate.delete(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpSe
public void saveAuthentication(User myUser) {
String password = myUser.getPassword();
// if (password == null) { // OAuth2 사용시 소셜로그인 비밀번호 생성 코드
// password = PasswordUtil.generateRandomPassword();
// password = PasswordUtils.generateRandomPassword();
// }
UserDetails userDetailsUser = org.springframework.security.core.userdetails.User.builder()
.username(myUser.getEmail())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,22 @@
import com.leets.xcellentbe.domain.user.dto.UserLoginRequestDto;
import com.leets.xcellentbe.domain.user.dto.UserSignUpRequestDto;
import com.leets.xcellentbe.domain.user.service.UserService;
import com.leets.xcellentbe.global.auth.email.EmailCheckDto;
import com.leets.xcellentbe.global.auth.email.EmailRequestDto;
import com.leets.xcellentbe.global.auth.email.EmailService;
import com.leets.xcellentbe.global.error.exception.custom.InvalidInputValueException;
import com.leets.xcellentbe.global.response.GlobalResponseDto;

import io.swagger.v3.oas.annotations.Operation;
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final UserService userService;
private final EmailService emailService;

@PostMapping("/register")
@Operation(summary = "회원가입", description = "회원가입을 합니다.")
Expand All @@ -34,4 +40,17 @@ public String login(@RequestBody UserLoginRequestDto userLoginRequestDto) {
// 로그인 로직 처리
return "로그인 성공";
}

@PostMapping("/email/send")
public ResponseEntity<GlobalResponseDto<String>> mailSend(@RequestBody EmailRequestDto emailRequestDto) throws
MessagingException {
emailService.joinEmail(emailRequestDto.getEmail());
return ResponseEntity.status(HttpStatus.OK).body(GlobalResponseDto.success());
}

@PostMapping("/email/check")
public ResponseEntity<GlobalResponseDto<String>> AuthCheck(@RequestBody EmailCheckDto emailCheckDto) {
return ResponseEntity.status(HttpStatus.OK).body(GlobalResponseDto.success(emailService.checkAuthNum(emailCheckDto.getEmail(), emailCheckDto.getAuthNum())));
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.leets.xcellentbe.global.auth.login.oauth;

import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
import java.util.Map;

import com.leets.xcellentbe.domain.user.Role;

import lombok.Getter;

/**
* DefaultOAuth2User를 상속하고, email과 role 필드를 추가로 가진다.
*/
@Getter
public class CustomOAuthUser extends DefaultOAuth2User {

private String email;
private Role role;

public CustomOAuthUser(Collection<? extends GrantedAuthority> authorities,
Map<String, Object> attributes, String nameAttributeKey,
String email, Role role) {
super(authorities, attributes, nameAttributeKey);
this.email = email;
this.role = role;
}
}
Loading