-
Notifications
You must be signed in to change notification settings - Fork 3
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
base: develop-SJB
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
HELP.md | ||
.gradle | ||
build/ | ||
!gradle/wrapper/gradle-wrapper.jar | ||
|
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 |
---|---|---|
@@ -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(); | ||
} | ||
} |
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(){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 컴포넌트 스캔을 활용하는게 더 나을 것 같습니다. (+생성자 주입) |
||
|
||
return new MemberServiceImpl(memberRepository(),bCryptPasswordEncoder() ); | ||
} | ||
|
||
@Bean | ||
public MemberRepository memberRepository(){ | ||
|
||
//return new MemoryMemberRepository(); | ||
return new JpaMemberRepository(em); | ||
} | ||
} |
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"; | ||
} | ||
} |
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 굳이 /members/를 명시하는 것 보단, 클래스 최상단에 이후 다른 기능을 추가해도, 어떤 경로에 어떤 API가 있는지 확인할 수 있으니까요. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:/"; | ||
} | ||
|
||
|
||
|
||
} |
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 잘못 import 된 것 같네요. |
||
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){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 생성자가 1개인 경우엔 |
||
this.memberService = memberService; | ||
} | ||
|
||
//회원 가입 페이지 | ||
@GetMapping("/members/new") | ||
public String createForm(@ModelAttribute("member") Member member){ | ||
return "members/createMemberForm"; | ||
} | ||
|
||
@PostMapping("/members/new") | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 회원 체크 로직이 생각보다 많이 등장하는 것 같은데, 이걸 묶을 수 있는 방법이 없을지 찾아보면 좋을 것 같습니다. |
||
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"; | ||
} | ||
|
||
|
||
|
||
} |
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; | ||
} | ||
} |
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"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
필요한건가요?