Skip to content

Commit

Permalink
Merge pull request #105 from JNU-econovation/feature/BE-40
Browse files Browse the repository at this point in the history
[BE-40] IDP 로그인, 회원가입
  • Loading branch information
BlackBean99 authored Sep 1, 2023
2 parents fefe4d4 + 93541c2 commit 0d38190
Show file tree
Hide file tree
Showing 33 changed files with 343 additions and 78 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/CI_Check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,11 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle

- name: Start containers # test 돌릴때 레디스 필요
run: docker-compose up -d

- name: spotless check
run: ./gradlew spotlessCheck --no-daemon

- name: test and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew test sonar --info --stacktrace --no-daemon
run: ./gradlew test sonar --info --stacktrace --no-daemon
Binary file modified server/.gradle/7.6.1/executionHistory/executionHistory.bin
Binary file not shown.
Binary file modified server/.gradle/7.6.1/executionHistory/executionHistory.lock
Binary file not shown.
Binary file modified server/.gradle/7.6.1/fileHashes/fileHashes.bin
Binary file not shown.
Binary file modified server/.gradle/7.6.1/fileHashes/fileHashes.lock
Binary file not shown.
Binary file modified server/.gradle/7.6.1/fileHashes/resourceHashesCache.bin
Binary file not shown.
Binary file modified server/.gradle/buildOutputCleanup/buildOutputCleanup.lock
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.econovation.recruit.api.applicant.usecase.ApplicantRegisterUseCase;
import com.econovation.recruitcommon.utils.Result;
import com.econovation.recruitdomain.common.aop.domainEvent.Events;
import com.econovation.recruitdomain.domain.applicant.Applicant;
import com.econovation.recruitdomain.domains.applicant.adaptor.AnswerAdaptor;
import com.econovation.recruitdomain.domains.applicant.adaptor.QuestionAdaptor;
import com.econovation.recruitdomain.domains.applicant.domain.Answer;
Expand All @@ -13,7 +12,6 @@
import com.econovation.recruitdomain.domains.applicant.exception.ApplicantDuplicateSubmitException;
import com.econovation.recruitdomain.domains.applicant.exception.QuestionNotFoundException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -120,23 +118,4 @@ private String convertToSubmitApplicantEventTitle(List<Answer> results) {
titleBuilder.append("[").append(hopeField).append("] ").append(name);
return titleBuilder.toString();
}

private Applicant createApplicantFromUserInput(Map<String, String> userInput) {
return Applicant.builder()
.id(UUID.randomUUID())
.hopeField(userInput.get("hopeField"))
.firstPriority(userInput.get("firstPriority"))
.secondPriority(userInput.get("secondPriority"))
.name(userInput.get("name"))
.phoneNumber(userInput.get("phoneNumber"))
.studentId(Integer.parseInt(userInput.get("studentId")))
.grade(Integer.parseInt(userInput.get("grade")))
.semester(Integer.parseInt(userInput.get("semester")))
.major(userInput.get("major"))
.doubleMajor(userInput.get("doubleMajor"))
.minor(userInput.get("minor"))
.supportPath(userInput.get("supportPath"))
.email(userInput.get("email"))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
@AllArgsConstructor
@Getter
public class AuthDetails implements UserDetails {

private String userId;
private String idpId;

private String role;

Expand All @@ -28,7 +27,7 @@ public String getPassword() {

@Override
public String getUsername() {
return userId;
return idpId;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
Expand Down Expand Up @@ -71,18 +72,21 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// .permitAll()
// .mvcMatchers("/v1/auth/token/refresh")
// .permitAll()
// .mvcMatchers(HttpMethod.GET, "/api/v1/token}")
// .permitAll()
// .mvcMatchers(HttpMethod.GET,
// "/v1/events/{eventId:[0-9]*$}/ticketItems")
// .permitAll()
// .mvcMatchers(HttpMethod.GET,
// "/v1/events/{eventId:[0-9]*$}/comments/**")
// .permitAll()
// .mvcMatchers(HttpMethod.GET, "/v1/events/search")
// .permitAll()
// .mvcMatchers(HttpMethod.GET, "/v1/examples/health")
// TODO 임시로 모든 요청 permit
.mvcMatchers(HttpMethod.POST, "/api/v1/questions")
.permitAll()
.mvcMatchers(HttpMethod.POST, "/api/v1/applicants")
.permitAll()
.mvcMatchers(HttpMethod.POST, "/api/v1/signup")
.permitAll()
.mvcMatchers(HttpMethod.POST, "/api/v1/login")
.permitAll()
// TODO 임시로 모든 요청 permit -> 채승이가 로그인 로직 완성하면 수정
.mvcMatchers("/**")
.permitAll()
// 스웨거용 인메모리 유저의 권한은 SWAGGER 이다
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.econovation.recruit.api.user.controller;

import static com.econovation.recruitcommon.consts.RecruitStatic.INTERVIEWER_SUCCESS_SIGNUP_MESSAGE;

import com.econovation.recruit.api.interviewer.docs.InterviewerExceptionDocs;
import com.econovation.recruit.api.user.usecase.UserLoginUseCase;
import com.econovation.recruit.api.user.usecase.UserRegisterUseCase;
import com.econovation.recruitcommon.annotation.ApiErrorExceptionsExample;
import com.econovation.recruitcommon.annotation.DevelopOnlyApi;
import com.econovation.recruitcommon.dto.TokenResponse;
import com.econovation.recruitcommon.jwt.JwtTokenProvider;
import com.econovation.recruitdomain.domains.dto.LoginRequestDto;
import com.econovation.recruitdomain.domains.dto.SignUpRequestDto;
import com.econovation.recruitdomain.domains.interviewer.domain.Role;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
@Slf4j
@Tag(name = "[0.0]. 유저 관련 API", description = "유저 API")
public class UserController {
private final JwtTokenProvider jwtTokenProvider;
private final UserRegisterUseCase userRegisterUseCase;
private final UserLoginUseCase userLoginUseCase;
private final Long tempId = 0L;

@DevelopOnlyApi
@Operation(summary = "임시 토큰을 발급합니다.")
@GetMapping("/token")
public ResponseEntity<TokenResponse> issueToken() {
log.info("tempId: {}", tempId);
String accessToken = jwtTokenProvider.generateAccessToken(tempId, Role.ROLE_TF.getRole());
String refreshToken = jwtTokenProvider.generateRefreshToken(tempId);
log.info("accessToken: {}", accessToken);
log.info("refreshToken: {}", refreshToken);
TokenResponse tokenResponse = new TokenResponse(accessToken, refreshToken);
return new ResponseEntity<>(tokenResponse, HttpStatus.OK);
}

@Operation(summary = "로그인합니다.", description = "accessToken, refreshToken을 발급합니다.")
@PostMapping("/login")
public ResponseEntity<TokenResponse> login(LoginRequestDto loginRequestDto) {
TokenResponse tokenResponse = userLoginUseCase.execute(loginRequestDto);
return new ResponseEntity<>(tokenResponse, HttpStatus.OK);
}

@Operation(summary = "회원가입합니다.", description = "회원가입합니다.")
@ApiErrorExceptionsExample(InterviewerExceptionDocs.class)
@PostMapping("/signup")
public ResponseEntity<String> signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) {
userRegisterUseCase.signUp(signUpRequestDto);
return new ResponseEntity<>(INTERVIEWER_SUCCESS_SIGNUP_MESSAGE, HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.econovation.recruit.api.user.docs;

import com.econovation.recruitcommon.annotation.ExceptionDoc;
import com.econovation.recruitcommon.annotation.ExplainError;
import com.econovation.recruitcommon.exception.RecruitCodeException;
import com.econovation.recruitcommon.interfaces.SwaggerExampleExceptions;
import com.econovation.recruitdomain.domains.interviewer.exception.InterviewerNotMatchException;
import com.econovation.recruitdomain.domains.interviewer.exception.InvalidPasswordException;

@ExceptionDoc
public class LoginExceptionDocs implements SwaggerExampleExceptions {
@ExplainError("유효하지 않은 비밀번호를 입력한 경우")
public RecruitCodeException 비밀번호_부적절 = InvalidPasswordException.EXCEPTION;

@ExplainError("등록되지 않은 이메일과 비밀번호로 로그인을 시도한 경우")
public RecruitCodeException 이메일_비밀번호_불일치 = InterviewerNotMatchException.EXCEPTION;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.econovation.recruit.api.user.docs;

import com.econovation.recruitcommon.annotation.ExceptionDoc;
import com.econovation.recruitcommon.annotation.ExplainError;
import com.econovation.recruitcommon.exception.RecruitCodeException;
import com.econovation.recruitcommon.interfaces.SwaggerExampleExceptions;
import com.econovation.recruitdomain.domains.interviewer.exception.InvalidPasswordException;

@ExceptionDoc
public class SignUpExceptionDocs implements SwaggerExampleExceptions {
@ExplainError("유효하지 않은 비밀번호를 입력한 경우")
public RecruitCodeException 비밀번호_부적절 = InvalidPasswordException.EXCEPTION;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.econovation.recruit.api.user.service;

import com.econovation.recruit.api.user.usecase.UserLoginUseCase;
import com.econovation.recruit.api.user.usecase.UserRegisterUseCase;
import com.econovation.recruitcommon.dto.TokenResponse;
import com.econovation.recruitcommon.jwt.JwtTokenProvider;
import com.econovation.recruitdomain.domains.dto.LoginRequestDto;
import com.econovation.recruitdomain.domains.dto.SignUpRequestDto;
import com.econovation.recruitdomain.domains.interviewer.domain.Interviewer;
import com.econovation.recruitdomain.domains.interviewer.exception.InterviewerNotMatchException;
import com.econovation.recruitdomain.out.InterviewerLoadPort;
import com.econovation.recruitdomain.out.InterviewerRecordPort;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserService implements UserRegisterUseCase, UserLoginUseCase {
private final InterviewerRecordPort interviewerRecordPort;
private final InterviewerLoadPort interviewerLoadPort;
private final JwtTokenProvider jwtTokenProvider;
private final PasswordEncoder passwordEncoder;

@Override
@Transactional
public TokenResponse execute(LoginRequestDto loginRequestDto) {
Interviewer account =
interviewerLoadPort.loadInterviewerByEmail(loginRequestDto.getEmail());
checkPassword(loginRequestDto.getPassword(), account.getPassword());
return jwtTokenProvider.createToken(account.getId(), account.getRole().name());
}

private void checkPassword(String password, String encodePassword) {
boolean isMatch = passwordEncoder.matches(password, encodePassword);
if (!isMatch) throw InterviewerNotMatchException.EXCEPTION;
}

@Override
@Transactional
public void signUp(SignUpRequestDto signUpRequestDto) {
if (interviewerLoadPort.loadInterviewerByEmail(signUpRequestDto.getEmail()) != null)
throw InterviewerNotMatchException.EXCEPTION;
String encededPassword = passwordEncoder.encode(signUpRequestDto.getPassword());
Interviewer interviewer =
Interviewer.builder()
.year(signUpRequestDto.getYear())
.name(signUpRequestDto.getName())
.email(signUpRequestDto.getEmail())
.password(encededPassword)
.build();
interviewerRecordPort.save(interviewer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.econovation.recruit.api.user.usecase;

import com.econovation.recruitcommon.annotation.UseCase;
import com.econovation.recruitcommon.dto.TokenResponse;
import com.econovation.recruitdomain.domains.dto.LoginRequestDto;

@UseCase
public interface UserLoginUseCase {
TokenResponse execute(LoginRequestDto loginRequestDto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.econovation.recruit.api.user.usecase;

import com.econovation.recruitcommon.annotation.UseCase;
import com.econovation.recruitdomain.domains.dto.SignUpRequestDto;

@UseCase
public interface UserRegisterUseCase {

void signUp(SignUpRequestDto signUpRequestDto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.econovation.recruit.utils.aop;

import com.econovation.recruitcommon.annotation.PasswordValidate;
import com.econovation.recruitdomain.domains.interviewer.exception.InvalidPasswordException;
import java.lang.reflect.Field;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class PasswordValidateAspect {

@Around("@annotation(com.econovation.recruitcommon.annotation.PasswordValidate)")
public Object validatePasswordField(ProceedingJoinPoint joinPoint) throws Throwable {
Object target = joinPoint.getTarget();
Class<?> targetClass = target.getClass();

Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(PasswordValidate.class)) {
field.setAccessible(true);
Object fieldValue = field.get(target);
if (fieldValue instanceof String) {
String password = (String) fieldValue;
if (!isValidPassword(password)) {
throw InvalidPasswordException.EXCEPTION;
}
}
}
}
return joinPoint.proceed();
}

private boolean isValidPassword(String password) {
if (password == null || password.length() < 10) {
return false; // 비밀번호 길이가 10글자 미만인 경우 검증 실패
}

boolean hasDigit = false;
boolean hasSpecialChar = false;

for (char ch : password.toCharArray()) {
if (Character.isDigit(ch)) {
hasDigit = true;
} else if (isSpecialCharacter(ch)) {
hasSpecialChar = true;
}
}

return hasDigit && hasSpecialChar;
}

private boolean isSpecialCharacter(char ch) {
// 특수 문자 여부를 확인하는 로직을 구현
// 예를 들어, 일부 특수 문자를 확인할 수 있습니다.
return ch == '@' || ch == '#' || ch == '!' || ch == '$' || ch == '%' || ch == '^'
|| ch == '&' || ch == '*';
}
}
Loading

0 comments on commit 0d38190

Please sign in to comment.