SPRING 팀 프로젝트 - Letter Of heart

joyfulwave·2022년 12월 26일
1

💡 SPRING 팀 프로젝트

📅 제작 기간

2022년 12월 9일 - 2022년 12월 26일(총 18일)




🤝 함께한 팀원




🙋‍♀️ 내가 맡은 역할

제가 맡았던 역할은 JWT 기능을 도입한 로그인 기능 구현과 회원가입, 로그아웃 기능 구현이였습니다.




✔️ 주제, 프로젝트 설계

특별한 날에 열어보는 느린 우체통입니다. 저희는 연말을 맞이하여 크리스마스 날에 맞추어 제작하였으며 디데이에 친구들이 작성하여준 편지를 오픈하여 확인할 수 있습니다.

저희는 [내 트리를 꾸며줘!] 사이트를 참고하여 사이트를 제작하였습니다.




✔️ 대표 기능

SENS를 활용한 전화번호 인증과 JWT 토큰을 사용한 미인증, 인증 유저 관리




✔️ 플로우 차트

🟢 전체 사이트 흐름




✔️ 개발 환경

저희 팀의 개발 환경은 아래와 같습니다.

개발 환경
Eclipse , Spring Boot, JPA Hibernate, Thymeleaf , Oracle Cloud

사용 언어
Java , JS, JQuery, Ajax

디자인
html, css

협업
Github

서버
AWS , Jenkins , Docker, Apache Tomcat

데이터베이스
언어 : Oracle
툴 : DBeaver

API
SENS




✔️ ERD




✔️ 기능

🟢 회원가입

유저는 각각의 항목을 채운 후 회원가입을 진행합니다. 하나의 항목이라도 채워지지 않으면유효성 검사에 걸리게 되며 회원가입은 실패하게 됩니다. (아이디 중복, 비밀번호, 닉네임, 휴대폰 인증)

네이버 API인 SENS를 이용하여 유저의 핸드폰 번호로 인증을 할 수 있도록 하였습니다. 휴대폰번호의 형태가 아니거나 유효하지 않은 핸드폰 번호일 경우 인증에 실패하게 됩니다.

  • 회원가입 진행

  • 인증번호

  • 유효성 검사에 실패했을 때

🟢 로그인

없는 아이디거나 아이디, 비밀번호가 일치하지 않을 시 로그인은 실패하게 됩니다. 가입된 아이디와 비밀번호로 로그인을 하게 되면 JWT 토큰이 생성되어 쿠키에 담기게 됩니다.

  • 로그인 진행

  • 쿠키에 저장된 토큰

🟢 로그아웃

메뉴에서 로그아웃을 클릭하면 저장된 쿠키의 내용이 삭제되며 index 페이지로 이동하게 됩니다. application 쿠키 탭에서 쿠키의 저장과 삭제를 확인할 수 있습니다.

  • 로그아웃 진행

  • 삭제된 쿠키

🟢 편지로 트리 꾸미기

가입한 유저는 다른 유저의 트리에 편지를 작성할 수 있습니다. 요구 조건에 맞게 편지를 작성하면 트리에 편지가 달리는 것을 확인할 수 있습니다. 편지를 읽을 수 있는 조건은 저희가 오픈하고자 하는 시간을 설정하여 그 시간이 지났을 때 오픈 할 수 있도록 했습니다. 저희는 12월 25일 크리스마스를 목표로 했습니다.
6개가 넘어가는 편지가 달렸을 시 페이지네이션 버튼이 생성되며 옆으로 옮겨가며 달린 편지를 확인 할 수 있습니다.

  • 편지 작성

  • 트리




💡 예외 상황 발생 및 해결

  • @RestController에서는 String 타입으로 매핑하면 return 타입에 담긴 값이 그대로 출력되는 상황을 발견했습니다. 그래서 modelAndView 타입으로 매핑하여 원하는 페이지로 이동할 수 있도록 수정하였습니다.
// 수정 전
@Controller
...

@GetMapping("/")
public String home(){
	return "home";
}

// 수정 후
@RestController
...

@GetMapping("/")
public ModelAndView home(){
	ModelAndView mv = new ModelAndView("home");
    return mv;
}
  • 사실 헤더에 쿠키를 보내는 것은 실패했습니다... 그래서 쿠키에 access token과 refresh token을 모두 담는 로직으로 작성하였습니다. 보안에는 취약한 단점이 있어 아쉬운 마무리였지만 쿠키에 담긴 jwt로 인증된 유저, 미인증된 유저의 요청을 관리하는 흐름을 경험하게할 수 있었습니다.

  • 로그인을 유지하는 방법에 대해서 많은 고민을 했었습니다. 많은 실패를 경험한 끝에 filter와 interceptor로 많은 흐름을 제어할 수 있음을 배우게 되었습니다. jwt 토큰의 유효함을 filter로 필터링하였고 interceptor 로 통해서 미인증 된 유저가 인증이 필요한 api에 접근하였을 때는 로그인, 회원가입을 할 수 있는 index로 이동하게 하였습니다.

package com.project.letterOfHeart.jwt;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;

import lombok.extern.java.Log;
import lombok.extern.log4j.Log4j;
import lombok.extern.log4j.Log4j2;

@Log4j2
@Log4j
public class LoginCheckInterceptor implements HandlerInterceptor {

	private JwtTokenProvider jwtTokenProvider;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		String requestURI = request.getRequestURI();
		System.out.println("[interceptor] : " + requestURI);
		// Thread.sleep(50000);
		Cookie[] list = request.getCookies();

		String token = "";

		if (list != null) {

			boolean Flag = false;

			for (Cookie cookie : list) {
				if (cookie.getName().equals("accessToken")) {
					token = cookie.getValue();
					Flag = true;

					System.out.println("[인증된 사용자 요청]");
					System.out.println("뭐로오나요? : " + requestURI);
					log.info("뭐로오나요? : " + requestURI);

					//Thread.sleep(5000);
				}
			}

			if (Flag) {
				if( "/".equals( requestURI ) ) {
					System.out.println("여기로오는게 맞지") ;
					response.sendRedirect( "/users/logout" );
				} else {
					response.sendRedirect( requestURI );
				}
				
				return true ;
			} else {
				System.out.println("여기올텐데?");
				response.sendRedirect( "/users/logout" );
				return true ;
			}
			
		} else {
			System.out.println("[미인증 사용자 요청]");
			if (requestURI.equals("/")) {
				return true;
			}
		}
		// 로그인 되어있을 떄
		return true;
	}

}




💡 배운점

  • security config로 유저의 권한(user, admin / junior, senior, manager 등)에 따라 접근을 제어할 수 있음을 배우게 되었습니다.
package com.project.letterOfHeart.jwt;

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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
 
    private final JwtTokenProvider jwtTokenProvider;
 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable() // 기본 스프링 시큐리티 로그인 페이지 사용 x
                .csrf().disable() // csrf 사용 x
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용 x
                .and()
                .authorizeRequests()
//                .antMatchers("/").access("isAnonymous() == true ? ")
                .antMatchers("/").permitAll()
                .antMatchers("/**").permitAll()
                .antMatchers("/users/*").hasRole("USER")
                .anyRequest().authenticated()
                .and()
                // filter 사용 위치 설정
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}
  • json token이 [헤더/페이로드/서명] 이렇게 세 부분으로 나눠지는데, 헤더는 토큰의 유형이고 페이로드는 사용자의 정보를 담고 서명은 암호화 알고리즘입니다. 누군가에 의해 변조 되지 않도록 '비밀키'를 이용하여 암호화하는데 application.yml 에 jwt secret 키에 담아두며 주로 패키지, 프로젝트 명으로 base64로 포맷한값을 넣어주면 된다고 합니다. (https://www.base64encode.org/ 여기서 포맷했습니다!)




💡 팀 프로젝트 어려웠던 점과 소감

수업시간때 배웠던 내용 이상으로 구현하려고 하다보니 많은 공부가 필요했습니다. 처음에는 모르는 코드들에 낙담하고 포기하고 싶은 마음이 들었지만 그런 마음이 들 때 이해되지 않더라도 코드를 수백번 봤으며 그 이후에는 어떤 파일엔 어떤 로직이 들어가는지 익히고 활용하도록 노력했습니다. 처음에 기획했던 모습대로 완벽하게 구사해내지는 못했지만 낯선 기능들을 공부하여 조금이라도 그 모습을 따라갈 수 있었다는 점에서 큰 성취감이 있었습니다.
github를 좀 제대로 사용해보는 프로젝트 였는데 다들 익숙치 않다보니 코드가 사라지고 이전 버전으로 업로드가 되는 등의 어려움을 겪었지만 나중에는 pr 순서를 잘다뤄 잘 마무리 할 수 있었습니다.
국비 과정 중 마지막 팀 프로젝트였는데 잘 마무리 할 수 있어서 감사했고 또 각자의 역할을 잘 해주었던 팀원들에게 고맙습니다. 앞으로도 포기하지 않는 집요함으로 많은 성장을 해가는 제가 될 수 있었으면 좋겠습니다 ! 😀

1개의 댓글

comment-user-thumbnail
2023년 3월 3일

안녕하세요!! 프로젝트 너무 잘 봤습니다! 혹시 부트캠프나 학원 다니신걸까요? 그렇다면 어느 학원 다니셨는지 알 수 있을까용,,.?

답글 달기