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 : 회원가입 및 로그인 기능 구현(first Version) #1

Open
wants to merge 2 commits into
base: develop-SJB
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
Expand Down
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '2.7.11'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'jacoco'
}

group = 'org.poolc'
Expand All @@ -19,12 +20,19 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'org.eclipse.persistence:javax.persistence:2.2.1'
Copy link
Member

Choose a reason for hiding this comment

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

필요한건가요?

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

}

tasks.named('test') {
Expand Down
29 changes: 29 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# PoolC-Spring-Practice

풀씨 백엔드와 유사하지만, 일부 기능들을 개선한 서버를 만들어봅시다. 물론 풀씨 페이지는 규모가 상당히 크기에, 모든 컴포넌트를 만들 수 없으니 기본적인 것들만 구현해 봅시다.

진행 방식
---
- Spring 프로그래밍 연습 스터디는, 하나의 큰 과제를 수행해야 합니다.
- Java 스터디에 비해 자유도가 조금 높으며, 요구 사항이 다소 추상적입니다.
- 직접적인 main 브랜치로의 커밋은 금지 되며, 반드시 Step 수행 이후 Pull Request 요청을 통해 확인이 진행됩니다.
- 본인의 닉네임/이름에 해당하는 브랜치를 만들고, 각 Step 에 대한 브랜치를 만들어서 PR을 진행해 주세요.
- ex) KBC 브랜치를 만들고, Step 1에 대한 결과물은 KBC-step1 로 만들어 주세요.
- 그 이후, PR은 KBC-step1 -> KBC 꼴로 요청해 주세요.
- 각 커밋의 단위는 최소화 해야하며, 다음과 같은 커밋 메시지 양식을 준수해 주세요.
- https://vsfe.notion.site/Git-Convention-84e1df4868974a58a1609b052e815095

요구 사항 (공통)
---
- 해당 과제는 여러 Step으로 구성되어 있으며, 앞 Step에 대한 PR 및 리뷰가 완료 되어야 뒤 Step을 진행할 수 있습니다.
- 포함된 라이브러리는 기본적인 라이브러리만 포함되어 있으며, 필요에 따라 추가 라이브러리를 사용해도 됩니다.
- 모든 Java 코드는 반드시 Java 코드 컨벤션 가이드를 준수해야 합니다.
- 작성한 메서드에 대한 테스트 코드 작성이 진행되어야 합니다.
- Jacoco 기준, Test Coverage 및 Branch Coverage가 80% 이상이어야 합니다.
- 통합 테스트/단위 테스트 여부는 자유롭게 설정하셔도 됩니다.
- 하지만, 통합 테스트 수행 시, 실제 DB에 전혀 영향이 가지 않아야 합니다.
- 사용하는 DB는 제한이 없습니다.

요구 사항 (단계)
---
- 특정 Step 을 마치지 못했다면, 그 다음 Step의 요구 사항을 보지 않는 것을 권장합니다. https://vsfe.notion.site/Spring-PoolC-Backend-Reborn-281c69c2eaf543459fedace987868ea4
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.poolc.springpractice;
package org.poolc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/org/poolc/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.poolc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;


@Configuration @EnableWebSecurity
public class SecurityConfig{

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http
.csrf().disable() // post 방식으로 값을 전송할 때 token을 사용해야하는 보안 설정을 해제
.authorizeRequests()
.antMatchers("/", "/members/**").permitAll()
.anyRequest().authenticated();
return http.build();
}
}
43 changes: 43 additions & 0 deletions src/main/java/org/poolc/config/SpringConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.poolc.config;



import org.poolc.repository.JpaMemberRepository;
import org.poolc.repository.MemberRepository;
import org.poolc.repository.MemoryMemberRepository;
import org.poolc.service.MemberService;
import org.poolc.service.MemberServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;


@Configuration
public class SpringConfig {

@PersistenceContext
private EntityManager em;

@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public MemberService memberService(){
Copy link
Member

Choose a reason for hiding this comment

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

컴포넌트 스캔을 활용하는게 더 나을 것 같습니다.
MemberServiceMemberRepository@Service@Repository를 활용하여 빈으로 등록되게 하도록 해 주세요.

(+생성자 주입)


return new MemberServiceImpl(memberRepository(),bCryptPasswordEncoder() );
}

@Bean
public MemberRepository memberRepository(){

//return new MemoryMemberRepository();
return new JpaMemberRepository(em);
}
}
37 changes: 37 additions & 0 deletions src/main/java/org/poolc/controller/HomeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.poolc.controller;

import lombok.RequiredArgsConstructor;
import org.poolc.controller.session.SessionConst;
import org.poolc.controller.session.SessionManager;
import org.poolc.domain.Member;
import org.poolc.repository.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.SessionAttribute;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@Controller
@RequiredArgsConstructor
public class HomeController {

private final MemberRepository memberRepository;
private final SessionManager sessionManager;

@GetMapping("/")
public String homeLoginV2(
@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model){

//세션에 회원데이터가 없으면 home
if(loginMember ==null){
return "home";
}

//세션이 유지되면 회원 홈(로그인 모드 홈)으로 이동
model.addAttribute("member", loginMember);
return "loginHome";
}
}
66 changes: 66 additions & 0 deletions src/main/java/org/poolc/controller/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.poolc.controller;

import lombok.RequiredArgsConstructor;
import org.poolc.controller.form.LoginFormController;
import org.poolc.controller.session.SessionConst;
import org.poolc.controller.session.SessionManager;
import org.poolc.domain.Member;
import org.poolc.service.LoginServiceImpl;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.CookieGenerator;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;

@Controller
@RequiredArgsConstructor
public class LoginController {
Copy link
Member

Choose a reason for hiding this comment

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

굳이 /members/를 명시하는 것 보단, 클래스 최상단에 @RequestMapping("/members") 를 박아버리는게 더 낫습니다.

이후 다른 기능을 추가해도, 어떤 경로에 어떤 API가 있는지 확인할 수 있으니까요.

Copy link
Member

Choose a reason for hiding this comment

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

로그인은 member/login이 아닌, 별도의 경로를 타도록 하는 것이 이상적인 설계일 것 같습니다.


private final LoginServiceImpl loginService;

// 로그인 페이지로 이동
@GetMapping("/members/login")
public String loginForm(@ModelAttribute("loginForm") LoginFormController form){
return "members/loginMemberForm";
}

@PostMapping("/members/login")
public String login(@Valid @ModelAttribute("loginForm") LoginFormController form, BindingResult bindingResult, HttpServletRequest request){
// valid한 입력이 아닐경우(채우지 않은 칸이 존재), 로그인 화면으로 다시 redirection
if(bindingResult.hasErrors()){
return "members/loginMemberForm";
}
// 가입하지 않은 아이디일 경우, 로그인 화면으로 다시 redirection
Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
if(loginMember == null){
return "members/loginMemberForm";
}

//로그인 성공 -> 세션이 있으면 해당 세션 반환, 없으면 신규세션 생성(default=true) false면 세션 없으면 null을 반환
HttpSession session = request.getSession();
//세션에 로그인 회원 정보 보관.
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

return "redirect:/";
}

// 로그아웃 page로 이동
@RequestMapping(value="/members/logout" , method = {RequestMethod.GET, RequestMethod.POST})
public String logout(HttpServletRequest request){
//로그아웃시 해당 세션 삭제 후 홈으로 이동
HttpSession session = request.getSession(false);
if(session != null){
session.invalidate();
}
return "redirect:/";
}



}
96 changes: 96 additions & 0 deletions src/main/java/org/poolc/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package org.poolc.controller;

import com.sun.xml.bind.v2.TODO;
Copy link
Member

Choose a reason for hiding this comment

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

잘못 import 된 것 같네요.
IntelliJ에서 Save Action 플러그인을 설치하여 저장시 자동으로 사용하지 않는 import 를 제거하도록 하거나,
Optimize Import 기능 (Mac 기준 Ctrl + Option + O) 을 사용해서 제거하도록 해 주세요.

import org.poolc.controller.session.SessionConst;
import org.poolc.domain.Member;
import org.poolc.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.SessionAttribute;

import javax.validation.Valid;
import java.util.List;
import java.util.Optional;

@Controller
public class MemberController {

private final MemberService memberService;

@Autowired
public MemberController(MemberService memberService){
Copy link
Member

Choose a reason for hiding this comment

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

생성자가 1개인 경우엔 @Autowired가 필요하지 않습니다.
또한, 다른 빈들은 @RequiredArgConstructor를 사용하고 있는데 여긴 수동으로 만들고 있네요.
통일하는게 좋을 것 같습니다.

this.memberService = memberService;
}

//회원 가입 페이지
@GetMapping("/members/new")
public String createForm(@ModelAttribute("member") Member member){
return "members/createMemberForm";
}

@PostMapping("/members/new")

Copy link
Member

Choose a reason for hiding this comment

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

붙여주세요~

public String create(@Valid @ModelAttribute("member") Member member, BindingResult bindingResult){
// 회원가입 정보가 valid하지않은 경우(채우지 않은 칸 존재) -> 다시 회원 가입 창으로 redirection
if(bindingResult.hasErrors()){
Copy link
Member

Choose a reason for hiding this comment

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

회원 체크 로직이 생각보다 많이 등장하는 것 같은데, 이걸 묶을 수 있는 방법이 없을지 찾아보면 좋을 것 같습니다.
(힌트: HttpServletRequest의 동작 과정을 알아보거나, AOP에 대해 알아보는 것을 권장합니다.)

return "members/createMemberForm";
}

//기존에 있는 회원 아이디와 동이한지 체크
if(memberService.findByUserId(member.getUserId()).isPresent()){
return "members/createMemberForm";
}

memberService.join(member);

return "redirect:/";
}

// 회원들 리스트 나열
@GetMapping("/members")
public String list(Model model){
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}

// 회원정보 수정 창 -> 회원 전용 홈(후술 로그인 홈)에서만 접근 가능해야함
@GetMapping("/members/update")
public String UpdateForm(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false)
Member loginMember, Model model){
//세션에 회원데이터가 없으면 home
if(loginMember ==null){
return "home";
}

//세션이 유지되면(로그인된 상태면) 회원 업데이트 화면으로 이동
model.addAttribute("member", loginMember);
return "members/updateMemberForm";
}


@PostMapping("/members/update")
public String update(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember,
@Valid @ModelAttribute("member") Member member, BindingResult bindingResult){
//세션에 회원데이터가 없으면 home ->의도적 API접근 막기 위함
if(loginMember ==null){
return "home";
}
if(bindingResult.hasErrors()){
return "members/updateMemberForm";
}
// 업데이트
memberService.update(loginMember.getId(), member);

return "redirect:/members/logout";
}



}
30 changes: 30 additions & 0 deletions src/main/java/org/poolc/controller/form/LoginFormController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.poolc.controller.form;

import lombok.Data;

import javax.validation.constraints.NotEmpty;

public class LoginFormController {

@NotEmpty
private String loginId;

@NotEmpty
private String password;

public String getLoginId() {
return loginId;
}

public void setLoginId(String loginId) {
this.loginId = loginId;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}
5 changes: 5 additions & 0 deletions src/main/java/org/poolc/controller/session/SessionConst.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.poolc.controller.session;

public abstract class SessionConst {
public static final String LOGIN_MEMBER = "loginMember";
}
Loading