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

프로젝트 세팅 및 회원가입 기능 개발 #2

Closed
wants to merge 8 commits into from

Conversation

1seyoung
Copy link
Collaborator

구현 내용

MyBatis 환경 설정 및 회원가입 기능을 구현하였습니다.
Docker 환경을 구성하여 MySQL과 애플리케이션을 컨테이너에서 실행할 수 있도록 설정하였습니다.

📍mybatis 환경 설정
📍application.yml 작성
📍docker, docker-compose 세팅
📍학생/교수자 모델 구현

  • StudentRegisterRequestDto, InstructorRegisterRequestDto를 통해 회원가입 요청 처리
  • StudentRegisterResponseDto, InstructorRegisterResponseDto를 통해 응답 처리
  • Instructor와 Student의 데이터 저장을 위한 MyBatis 매퍼와 연동

📍회원가입을 위한 컨트롤러/서비스/mapper 구현

컨트롤러: MemberController

  • POST /api/member/instructor/register: 교수 회원가입 API
  • POST /api/member/student/register: 학생 회원가입 API

서비스: InstructorService, StudentService
매퍼: InstructorMapper.xml, StudentMapper.xml

메인 리뷰어 지정 및 Due Date

@blue000927

고민 포인트, 리뷰 시 참고 사항(코드)

코드 바로가기

public class BaseMember {
    private static final Logger logger = LoggerFactory.getLogger(BaseMember.class);

    private final String email;
    private final String password;
    @Setter
    private String name;
    @Setter
    private String phone;
    private boolean isActive;

    public BaseMember(String email, String password, String name, String phone, boolean isActive) {
        this.email = email;
        this.password = password;
        this.name = name;
        this.phone = phone;
        this.isActive = false;
    }

    public void activate() {
        if (this.isActive) {
            logger.info("The user is already activated.");
            return;
        }
        this.isActive = true;
        logger.info("User activated successfully.");
    }
}

BaseMember 클래스를 만들어 학생(Student)과 교수(Instructor)의 공통 속성을 관리하려 했으나
BaseMember 없이 개별 테이블(Student, Instructor)로 변경하는 방향으로 수정하였습니다.

기존 방식 (BaseMember 사용)
• BaseMember 엔티티를 생성하여 email, password, name, phone, isActive 등의 공통 속성을 관리
• Student, Instructor가 BaseMember를 참조하는 형태로 구성
• DB 테이블은 base_members, students, instructors로 분리
현재 방식 (BaseMember 제거)
• Student와 Instructor 테이블을 별도로 두고 각 테이블에 공통 필드를 개별적으로 포함
• DB 테이블은 students, instructors 두 개로 구성됨

고민 : 다른 접근 방식이 있을지?

관련 이슈 : #1 회원가입

체크리스트

  • PR 제목을 명령형으로 작성했습니다.
  • PR을 연관되는 github issue에 연결했습니다.
  • 리뷰 리퀘스트 전에 셀프 리뷰를 진행했습니다.

Sorry, something went wrong.

- base model
- Instructor / Student 구조 개발
- Base Model 삭제
- Base Model 상속 받는 학생 / 교수자 모델, dto 전체 수정
- application.yml 오타 수정 (mybatis 부분)
@1seyoung 1seyoung requested a review from blue000927 January 29, 2025 19:36
- 홈페이지 / 회원가입 페이지 / 로그인 페이지 / 대시보드 페이지
- 로그인 기능 구현 (JWT 토큰 발급 및 검증)
- 학생 / 교수자 로그인 API 개발
- 로그인 시 승인 상태 확인 (교수자는 관리자 승인 필요, 학생은 이메일 인증 필요)
- 대시보드 페이지 기본 구조 구현 (학생 / 교수자 대시보드 분리)
- Spring Security 설정 수정:
  - 기본 로그인 폼 비활성화 (`formLogin().disable()`)
  - JWT 기반 인증 적용(AccessToken)
  - 특정 경로 접근 허용 (`permitAll()`) 설정 조정
- MyBatis 연동 및 인증 관련 SQL 수정 (`activate` 컬럼 관련 오류 해결)
- 로그인 비밀번호 암호화 (`BCryptPasswordEncoder`) 확인 및 검증 로직 추가
- 오류 로그 확인 및 디버깅 (403 Forbidden, Invalid Password 등)
@1seyoung
Copy link
Collaborator Author

1seyoung commented Feb 2, 2025

구현 내용

프론트 구현
관련 코드 : webfront 폴더, WebCofing

교수자 활성화를 위한 관리자 승인 기능
관련 코드 AdminController, AdminService

  • (목록 불러오기) GET localhost:8080/api/admin/pending-instructors?page=1&size=10
  • (승인) POST localhost:8080/api/admin/approve-instructor/{id}
    image

홈페이지
image

로그인 페이지
관련 코드 AuthController, AuthService, AuthMapper, JwtUtil,

    @PostMapping("/{role}/login")
    public ResponseEntity<?> login(@PathVariable("role") String role, @RequestBody LoginRequestDto request){
        LoginResponseDto response = authService.authenticate(request, role);
        return ResponseEntity.ok(response);
    }

image

아직 학생 이메일 인증은 구현 x , TODO 작성해둠, 이후 smtp 이용해서 구현 예정
로그아웃 기능 구현 필요

로그인 성공 시 대시보드 리다이렉션
로그인 이후 응답에 담긴 값을 이용해서 대시보드 ui 적용 가능한 것 확인
대시보드 연결 전 accesstoken 가지고 있는지 체크 후 연결
image

리뷰어 지정 및 Due Date

@blue000927

고민 포인트, 리뷰 시 참고 사항(코드)

캐싱 기능이 필요할 것 같은데 어떤 방식으로 접근하면 될지

관련이슈

#1
#3
#4

체크리스트

  • PR 제목을 명령형으로 작성했습니다.
  • PR을 연관되는 github issue에 연결했습니다.
  • 리뷰 리퀘스트 전에 셀프 리뷰를 진행했습니다.

Sorry, something went wrong.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.gatheria.mapper")
Copy link
Collaborator

Choose a reason for hiding this comment

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

오잉 이게 필요한가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Mybatis 오류가 있었어서 이것저것 해보다가 시도했던 건데 지우는걸 까먹었네요 수정해두도록 하겠습니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

아직 안지워진거같아요!

Comment on lines 26 to 28
.requestMatchers(
"/", "/index.html", "/static/**", "/assets/**",
"/login", "/register", "/api/auth/**", "/api/member/email-check" , "/api/member/student/register", "/api/member/instructor/register"
Copy link
Collaborator

Choose a reason for hiding this comment

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

"/", "/index.html", "/static/", "/assets/"
요거는 없어도 될거같아요.

사실 메인 페이지는 프론트가 제작하는 것이지, 백엔드에서 관여하는건 아니라서요! (서버사이드렌더링이 아닌이상)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

제가 하던게 서버사이드 렌더링이었나 봅니다. 지웠더니 렌더링이 안됩니다. 나머지 문제 해결 후에 수정해보겠습니다...!

Copy link
Collaborator

Choose a reason for hiding this comment

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

정 어려우면 일단 넘어가시죠..!

Comment on lines +12 to +26
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/register").setViewName("forward:/index.html");
registry.addViewController("/").setViewName("forward:/index.html");
registry.addViewController("/admin").setViewName("forward:/index.html");
registry.addViewController("/login").setViewName("forward:/index.html");
registry.addViewController("/dashboard").setViewName("forward:/index.html");
registry.addViewController("/dashboard/instructor").setViewName("forward:/index.html");
registry.addViewController("/dashboard/student").setViewName("forward:/index.html");
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

요것도 필요없지 않을까요~?? ai 쓰시는거면 아예 별도 리액트 레포를 파셔서 연결해보시면 좋을듯싶어요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요 문제도 프론트 문제 정리하고 수정 반영하겠습니다...!

Comment on lines +28 to +29
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationTime))
Copy link
Collaborator

Choose a reason for hiding this comment

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

issuedAt 시각에서 expirationTime을 더해야 하지 않을까요?
지금은 issuedAt할 때 따로 현재 시각 구하고, expiration할때 현재 시각 구하는데 이러면 현재 시각이 약간 달라질 수 있어요.

Copy link
Collaborator

Choose a reason for hiding this comment

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

이거 반영안된거같아요..!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

        Date now = new Date();
        Date expiration = new Date(now.getTime() + expirationTime);
        ...
                 setIssuedAt(now)
                .setExpiration(expiration)
        

한번 생성된 now를 활용하여 setIssuedAt과 setExpiration을 설정하도록 변경했습니다.

Comment on lines +34 to +36
public boolean validateAccessToken(String token) {
//TODO : Access Token 검증 로직
return true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

어떻게 검증할까요 ㅎㅎ

Copy link
Collaborator

Choose a reason for hiding this comment

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

답변 부탁드립니다..!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

        try {
            Jwts.parserBuilder()
                    .setSigningKey(getSigningKey())
                    .build()
                    .parseClaimsJws(token);

            Claims claims = extractClaims(token);
            Date expiration = claims.getExpiration();
            return !expiration.before(new Date());
        }  catch (JwtException e){
            System.out.println("JWT 검증 실패: " + e.getMessage());
            return false;
        }

토큰의 서명을 검증한 후, Claims에서 만료 시간을 추출하여 현재 시각과 비교하는 방식으로 유효성을 검사하도록 구현했습니다|
(다른 브랜치에서 추가 개발한 내용이라 PR에 코드가 없어 코멘트로 남깁니다)

import org.slf4j.LoggerFactory;

@Getter
public class BaseMember {
Copy link
Collaborator

Choose a reason for hiding this comment

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

저는 abstract class Member를 선언하고 하위 클래스로 Student, Instructor로 설계했으면 합니다.
테이블의 경우 총 3개가 나올 것이고 학생과 교수는 member_id를 외래 키로 가지고 있으면 됩니다.

기존 방식은 중복 컬럼을 불필요하게 들고 있어야 하고, 컬럼 변경이 생기면 양쪽을 바꿔야 한다는 문제점이 있습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

현재 abstract class Member를 사용하여 Student와 Instructor를 상속하는 구조로 변경했습니다.
이후 API 설계 시, 역할별 엔드포인트(/student/{id}, /instructor/{id})와 통합 엔드포인트(/member/{id}) 중 어떤 방식이 더 적절할까요?

예시
과제 생성 기능은 교수자만 사용 가능해야 하지만, 과제 상태 조회는 교수자 학생 모두 접근가능해야함

  • 교수자는 모든 학생의 제출 상태를 조회
  • 학생은 본인의 제출 상태만 조회 가능

요구사항을 고려했을 때, 어떤 규칙을 정해두고 설계를 하는게 적절할까요?

Comment on lines 27 to 33
if ("student".equalsIgnoreCase(role)) {
return authenticateStudent(request);
} else if ("instructor".equalsIgnoreCase(role)) {
return authenticateInstructor(request);
} else {
throw new RuntimeException("Invalid role");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

student, instructor은 하드 코딩 하지 말고 "role" 자체를 enum으로 뺄 수 없을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

MemberRole 작성해서 반영했습니다

throw new RuntimeException("Invalid Password");
}

String accessToken = jwtUtil.createAccessToken(request.getEmail(),"student");
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 부분도 "student" 하드 코딩을 피했으면 합니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

MemberRole enum 클래스 사용해서 사용하도록 코드 수정했습니다.

Comment on lines 7 to 15
<insert id="insertInstructor" useGeneratedKeys="true" keyProperty="id">
INSERT INTO instructors (email, password, name, phone, affiliation, active, created_at, updated_at)
VALUES (#{email}, #{password}, #{name}, #{phone}, #{affiliation}, #{active}, NOW(), NOW())
</insert>

<insert id="insertStudent" useGeneratedKeys="true" keyProperty="id" >
INSERT INTO students (email, password, name, phone, active, created_at, updated_at)
VALUES (#{email}, #{password}, #{name}, #{phone}, #{active}, NOW(), NOW())
</insert>
Copy link
Collaborator

Choose a reason for hiding this comment

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

xml보다 mapper 단에서 어노테이션으로 쿼리 적는 것에 대해서는 어떻게 생각하시나요~?? 요거는 취향 차이긴해서 수정하라는 의미는 아닙니당 (의견을 여쭙는 정도)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

두 방법을 같이쓰는 것도 괜찮나요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

네네
간단한건 쿼리 베이스로 가고, 복잡한건 xml로 많이 씁니당

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

간단한건 어노테이션으로 변경해서 수정해보겠습니다~

- Member 상속 구조 도입
- DB 테이블 구조 개선 (members + students/instructors)
- MemberRole enum으로 설정 후 하드코딩 방지
- 불필요한 role 구분 제거 (이메일 중복 체크)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.gatheria.mapper")
Copy link
Collaborator

Choose a reason for hiding this comment

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

아직 안지워진거같아요!

Comment on lines +28 to +29
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationTime))
Copy link
Collaborator

Choose a reason for hiding this comment

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

이거 반영안된거같아요..!

Comment on lines +34 to +36
public boolean validateAccessToken(String token) {
//TODO : Access Token 검증 로직
return true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

답변 부탁드립니다..!

Comment on lines 37 to 39
if (token == null || !token.startsWith("Bearer ")) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("토큰이 필요합니다.");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

답변 부탁드립니다..!

Comment on lines 30 to 40
public ResponseEntity<?> registerUser(@RequestBody InstructorRegisterRequestDto request) {
InstructorRegisterResponseDto response = memberService.register(request);
return ResponseEntity.ok(response);
}


@PostMapping("/student/register")
public ResponseEntity<?> registerUser(@RequestBody StudentRegisterRequestDto request) {
StudentRegisterResponseDto response = memberService.register(request);
return ResponseEntity.ok(response);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기도 ResponseEntity<?>가 아닌 명확한 표현 부탁드립니다.
사실 ? = Object랑 다를바가 없어서 제네릭의 이점을 누리지 못합니다 ㅠ

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 전체적으로 와일드카드로 된 부분 수정했습니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

이건 무슨 파일인가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요것도 제거 완료했습니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

제거해주시죵 ㅋㅋㅋ 로고 같네요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요것도 제거했습니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

이거도 필요없지 않은지..?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요것도 제거했습니다

Comment on lines 7 to 15
<insert id="insertInstructor" useGeneratedKeys="true" keyProperty="id">
INSERT INTO instructors (email, password, name, phone, affiliation, active, created_at, updated_at)
VALUES (#{email}, #{password}, #{name}, #{phone}, #{affiliation}, #{active}, NOW(), NOW())
</insert>

<insert id="insertStudent" useGeneratedKeys="true" keyProperty="id" >
INSERT INTO students (email, password, name, phone, active, created_at, updated_at)
VALUES (#{email}, #{password}, #{name}, #{phone}, #{active}, NOW(), NOW())
</insert>
Copy link
Collaborator

Choose a reason for hiding this comment

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

네네
간단한건 쿼리 베이스로 가고, 복잡한건 xml로 많이 씁니당

Comment on lines 26 to 28
.requestMatchers(
"/", "/index.html", "/static/**", "/assets/**",
"/login", "/register", "/api/auth/**", "/api/member/email-check" , "/api/member/student/register", "/api/member/instructor/register"
Copy link
Collaborator

Choose a reason for hiding this comment

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

정 어려우면 일단 넘어가시죠..!

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot

See analysis details on SonarQube Cloud

@1seyoung 1seyoung closed this Feb 17, 2025
@1seyoung 1seyoung deleted the feature/register branch February 21, 2025 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants