프로젝트 목적
: 스프링 부트를 활용한 기본 CRUD 기능 구현 + 추가 구현
프로젝트 기간
: 240108 -240115
기능 구현 사항
- 게시글 / 댓글 CRUD
- 게시글 조회 수
- 사용자 인증 / 인가
- 이미지 파일 업로드
ERD
와이어 프레임 구상도
내가 맡은 부분
: 게시글 / 댓글 조회, 댓글 수정, 사용자 인증 / 인가
기능 구현 회고
게시글 / 댓글 조회
@Controller
@Service
전체 조회 : sortOrder enum 클래스로 DESC / ASC 정렬기준을 받아와서 정렬했다.
필터까지 넣고 싶었으나 이전 투두리스트 개인과제때 했던 것처럼 이름을 받아와서 정렬조건에 따라 if 문으로 분기하는 패턴이 마음에 들지 않아 넣지 않았다. 그렇게 되면 필터 조건이 늘어날때마다 if 문이 중첩되거나 쿼리문이 길어지길래 보기 싫었는 이 이외의 방법을 찾아볼 생각을 못했었다. => JPA specification
단건 조회 : 게시글을 불러올 때 그 게시글 id 를 갖고 있는 댓글도 함께 불러오기 위해 PostWithReplyResponse 라는 새 DTO 를 만들어줬었다. 다른 방법은 없나? => @EntityGraph?
댓글 수정
@controller
@Service
댓글만 수정하는데 postId 가 왜 필요한가싶어서 request에 postId 를 뺏었다.
그런데 해당하는 게시글이 없을 때 댓글 작성이 그냥 안되는게 아니라 게시글이 없다는 적절한 응답을 주는게 좋다는 피드백을 받았다 .!
사용자 인증 / 인가
토큰 생성을 위해 jwt.yml 파일에서 토큰 관련 설정을 해줬다. 그런데 토큰 생성 메소드 부분에 직접 집어넣는 경우도 있는 것 같다.
회원가입
로그인
입력받은 이메일을 찾은 다음에 저장된 비밀번호와 입력받은 비밀번호를 비교검사했는데
가입된 이메일이 없는 경우를 고려안했다.
이메일을 찾아서 없는 경우 예외를 던지고
그 다음에야 비밀번호검사하는게 옳은 방식일까?
이메일, 비밀번호 둘 다 틀린 경우
이런 에러가 뜬다. 적절한 예외처리를 안해줘서 보기 안좋다 !
시큐리티 설정
시큐리티를 통해서 나오는 에러페이지에 적절한 예외처리를 해보고 싶어서 "/error" 를 포함시켜줬는데 결국 못했다. 403 에러가 뜰 때 메시지가 던져지게 하고 싶다.
세션을 사용해보지 않았는데 해보는게 좋지 않을까??
토큰 생성
@PropertySource("classpath:jwt.yml") // 설정 파일 경로
@Service
class TokenProvider(
@Value("\${secret-key}")
private val secretKey: String,
@Value("\${expiration-hours}")
private val expirationHours: Long,
@Value("\${issuer}")
private val issuer: String
) {
val keyBytes: ByteArray = Decoders.BASE64.decode(secretKey)
val key: Key = Keys.hmacShaKeyFor(keyBytes)
// user 고유 정보로 토큰 생성
fun createToken(userSpecification: String) = Jwts.builder()
.signWith(key, io.jsonwebtoken.SignatureAlgorithm.HS256)
.setSubject(userSpecification) // 고유 정보로 주체 설정
.setIssuer(issuer) // 발급자
.setIssuedAt(Timestamp.valueOf(LocalDateTime.now())) // 발급 시간
.setExpiration(Date.from(Instant.now().plus(expirationHours, ChronoUnit.HOURS))) // 토큰 만료 설정
.compact()!!
// 토큰의 subject 를 복호화하여 문자열 형태로 반환 ( 유효한지 확인, 유효하지 않을 경우 null )
fun validateTokenAndGetSubject(token: String): String? = Jwts.parserBuilder()
.setSigningKey(key) // 비밀키로 복호화
.build()
.parseClaimsJws(token) // 파싱 : jwt 형식에 맞게 데이터를 해석하고 추출
.body // 토큰의 본문 (claims) 에 접근
.subject // 주체 반환
}
설정 파일 경로를 @PropertySource 로 참조해서 가져왔다.
그 안의 값은 @Value() 를 통해 생성, 선언했다.
signWith 메소드에 디코딩 된 바이트 배열로 생성한 Key 객체를 넣었다.
토큰 필터
@Component
class JwtAuthenticationFilter(
private val tokenProvider: TokenProvider
): OncePerRequestFilter() {
// 헤더에서 토큰 추출
private fun parseBearerToken(request: HttpServletRequest)
= request
.getHeader(HttpHeaders.AUTHORIZATION) // http 요청의 헤더에서 authorizarion 값을 찾아서
.takeIf { it?.startsWith("Bearer ", true) ?: false } // 접두어 확인, 제거 ( Bearer 타입 )
?.substring(7)
// 토큰으로 정보 추출
private fun parseUserSpecification(token: String?) = (
token?.takeIf { it.length >= 10 }
?.let { tokenProvider.validateTokenAndGetSubject(it) } // 토큰이 유효한지 확인, 복호화
?: "anonymous:anonymous" // 유효하지 않을 때 설정
).split(":")
.let { User(it[0], "", listOf(SimpleGrantedAuthority(it[1]))) } // 사용자 정보 가져와서 스프링 시큐리티 User 객체 생성
// 인증 정보 설정
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val token = parseBearerToken(request) // 토큰 추출
val user = parseUserSpecification(token) // 사용자 정보 추출
UsernamePasswordAuthenticationToken.authenticated(user, token, user.authorities) // 인증된 사용자를 나타내는 토큰 생성
.apply { details = WebAuthenticationDetails(request) } // 요청날린 client 또는 프록시의 ip 주소와 세션 id 저장
.also { SecurityContextHolder.getContext().authentication = it } // sercurityContextHolder 에 인증 정보 저장
filterChain.doFilter(request, response) // 다음 필터
}
}
내가 본 블로그를 토대로 토큰을 생성할 때 헤더에서 추출 -> 복호화(유효성 검사) -> 인증 정보 설정 이렇게 크게 3구역으로 나눠졌는데 이 형태도 사람마다 만드는게 다 다른 것 같다.
솔직히 여러 블로그와 책을 참조해서 만들다보니 어느 부분이 필수적인 요소인지 잘 모르겠다.
이번주 개인과제에 더해서 이번에 했던 프로젝트의 인증 / 인가 부분 요소들을 넣었다 뺐다 하면서 다 실험해봐야겠다.
회고 + 느낀 점
첫 스프링 프로젝트를 진행하면서 솔직히 인증 / 인가 그거 책 보고 따라하면 되겠지 ! 하고 가벼운 마음으로 시작했었다.
근데 웬걸 책 보고 다 따라할 수 있으면 누가 어렵다고 느꼈겠냐 ㅋ 하려던 대로 잘 안됐고 급한 마음에 온갖 곳에서 다 긁어모아서 되는대로 해봤다가 망해서 다 엎기도 했었다. 그 상태에서 튜터님의 도움을 받아 시큐리티의 틀을 잡고 추가로 다듬었었는데 구현을 해내서 너무 기쁜 마음 반 + 튜터님 없었으면 어쩔 뻔 했나 안심하는 마음 반이었다.
아직 배우지 않은 기능을 해냈다는 것도 좋지만 그 과정에서 내가 내 자신과 약속한 일들을 수행하지 못했다는 것에 마이너스를 주고 싶다. 코드타카를 열심히 하지 않았고, cs 강의도 듣지 않았다. 황금같은 일주일을 흘려보내서 아깝다.
거기다 일주일간 쓸데없는 부담을 느낀게 내 몸에 무리였던 것 같다.
아무래도 미니프로젝트랑 달리 진짜 배운 걸 써먹는 때여서 너무 잘하고 싶었나보다. 그래서 혹시나 구현을 못할까봐 더 긴장한 것도 있고. 그래도 완성했을 때의 뿌듯함은 여전히 느낄 수있어서 다행이었지만.
한 주 동안 팀원분들도 고생했는데 특히 너무 무리였던 것 같다고 후회하면서도 발표날 오전까지 붙잡고 결국은 구현해낸 팀원의 모습을 보고 많은 걸 느꼈다. 나는 안되는게 생겼을 때 저렇게 할 수 있을까? 많이 고민했다.
그리고 우리집 네트워크가 자꾸 불안정해서 발표도 제대로 못했는데 이 문제를 어떻게 해결하나 싶다.
아파트에서 같이? 연결한케이블이라는데 휴대폰 무선연결도 가끔씩 끊기는 마당에 노트북 연결은 🥲🥲 이 문제를 고쳐야 민폐안끼칠 텐데 팀원들은 오히려 이런 일이 생길 수도 있는거라며 탓하지 않았다.....이 상태로면 난 발표는 계속 못할 것 같은데 ㅜㅜㅜㅜ
매니저님과 상담하면서 마음을 약간 내려놓았다. 내가 할 수 있으면 할 수 있는거고 할 수 없다는거에 스트레스 받지 말자!
가볍게 생각하고 진지하게 임하면 될 것 같다.
스터디할까도 고민해봤는데 해야될게 많고 내 스스로 챙겨야할게 많은 이 타이밍에 남들 하는거 다 하는데 내가 못 낄까봐 내 체력 생각 안하고 시도하는건 잘못된 것 같다.! 남들보다 약한 체력을 내가 제일 잘 알면서 이것저것 무리할려고 하는건 오바다. 내 할거 하면서 막히는 부분이 생기면 그때 다른 사람들에게 도움을 청해보고 나도 도움을 줄 수 있는 부분을 찾는게 나을 것 같다.
이번 주 두통과 함께 시작했지만 잠 푹 자고 내일 또 새로운 강의와 함께 기분 좋게 시작하면 좋겠다.
'회고' 카테고리의 다른 글
백오피스 프로젝트 회고 (0) | 2024.01.29 |
---|---|
2023년을 보내며 (6) | 2024.01.01 |
1주차 미니 프로젝트 회고록 (0) | 2023.12.01 |