본문 바로가기

회고록 모음/코드숨-스프링 과정 회고록

[회고록] 코드숨 Spring - 7주차 코드리뷰 (Spring Security, 암호화)

[ 7주차 회고록 📚]

 

 


📌 커리큘럼 

 


📌 코드리뷰 with 깃허브

github.com/CodeSoom/spring-week7-assignment-1/pull/7

 

[Spring 7주차 과제] 인가(Authorization) 구현하기 by developerOlive · Pull Request #7 · CodeSoom/spring-week7-assignm

과제 목표 지금은 로그인을 했다고 하더라도, 다른 사람의 정보를 내 마음대로 수정할 수 있습니다. 내 정보는 오직 나만 수정할 수 있어야 합니다. 오직 나만이 내 정보를 수정할 수 있도록 Sprin

github.com


1. 한 것

- 코드숨 스프링 7회 강의 3개 듣기

 

- 과제 1 : 자신의 정보를 타인이 수정할 수 없도록 Spring Security를 이용하여 구현하기

 

- 과제 2 : 비밀번호를 평문으로 저장하지 않고 암호화하여 저장할 수 있도록 구현하기 

 

- 과제 3 :  API 테스트 통과하기 

 

- 과제 4 : 테스트 커버리지 100% 달성하기

 

- 과제 5 : e2e 테스트 통과하기 

 

테스트 커버리지

 

API 테스트

 


2. 배운 것

 

이번 7주차 강의 내용 중 하나는

6주차에서 인터셉터로 구현한 '인증' 부분을 Spring Security로 바꾸는 상황이다.

 

난이도가 상당히 있는 Spring Security를 처음 접하다 보니 헷갈리는 부분이 너무 많아서 어떻게 공부해야 할지 고민이 많이 됐다. 

결국 내가 택한 방법은... 코드 한 줄 한 줄에 대해 주석을 달아서 최대한 이해를 해보고자 했다.

코드가 깔끔하진 않지만 이렇게라도 정리하지 않으면 금방 머릿속에서 날아가고 흐름이 잘 정리되지 않아서 작성해보았다.

틀린 주석도 있을 수 있겠지만 일단 공부한 선에서 정리한 주석이고, 미래의 나를 위한 정리이다.

 

 

 

 

먼저 아래와 같이 Spring Security를 사용하기 위해 buildGradle에 추가한다.

 

 

 

 

그리고 Spring Secuirty 설정에 관한 코드를 작성하는 SecurityJavaConfig를 만든다.

 

SecurityJavaConfig.java

 

[ SecurityJavaConfig.java ]

 

- 19번째 줄 : @Configuration - 빈 등록 (객체 생성)

 

 

 

- 20번째 줄 : @EnableGlobalMethodSecurity(prePostEnabled = true) 어노테이션을 추가한다.

@EnableGlobalMethodSecurity 어노테이션은 어노테이션 기반의 보안을 적용할 수 있도록 해준다.

prePostEnabled 속성을 true로 설정하면 메소드에 @PreAuthorize을 사용하여 Role기반의 제약을 사용할 수 있다.

 

 

 

- 21번째 줄 : SecurityJavaConfig는 WebSecurityConfigurerAdapter 클래스를 상속받는다. 

 

 

 

- 23~26번째 줄 : JwtAuthenticationFilter에서 토큰을 Parse 한 후 회원 식별자를 추출하기 위해 authenticationService를 생성자에 주입한다.

JwtAuthenticationFilter

 

 

 

- 30번째 줄 : ecurityJavaConfig는 configure() 메소드를 오버라이드 하여 작성하고, 파라미터는 HttpSecurity 객체를 받는다.

 

 

 

- 34~35번째 줄 : Jwt가 유효한 토큰인지 인증하기 위한 JwtAuthenticationFilter를 생성하고, 

authenticationManager와 authenticationService를 매개변수로 넘긴다.

 

SecurityJavaConfig

 

 

- 38번째 줄 : 회원 인증 시 발생하는 에러를 처리하는 필터인 AuthenticationErrorFilter를  생성한다. 

 

AuthenticationErrorFilter.java

 

 

- 41번째 줄 : CSRF(Cross-Site Request Forgery)란 사용자가 의도하지 않은 기능을 실행하도록 하는 공격 방법이다.

여기서는 CSRF를 사용하지 않기 위해 disable처리했다.

 

 

 

[ JwtAuthenticationFilter.java ]

JwtAuthenticationFilter

 

 

- 21번째 줄 : BasicAuthenticationFilter - HTTP 요청의 Authorization 헤더를 처리하여 SecurityContextHolder에 처리한 결과를 담는다.

 

 

- 59번째 줄 : 유저 인증정보를 저장할 UserAuthentication를 생성한 후, userId를 매개변수로 넘긴다. 

 

JwtAuthenticationFilter

 

 

UserAuthentication의 authorities 메소드에서 권한을 부여한다.

인증된 회원만 상품을 등록할 수 있게 하는 "USER"라는 권한을 부여했다.

 

UserAuthentication

 

 

그 후 ProductController에서 

@PreAuthroize를 활용해

로그인도 성공하고, "USER"라는 권한이 있을 때만 상품을 등록할 수 있도록 설정한다.

 

ProductController

 


 

해시 알고리즘을 통해 암호화를 진행했다.

해시 함수는 정해지지 않은 길이의 문자열을 입력받아 고정된 크기의 결괏값을 출력하는 함수로, 주로 전자 서명에 많이 사용한다.

 

 

TDD 방식으로 진행하기 위해 먼저 UserTest에서 테스트를 작성했다.

 

changePassword 메소드를 통해 패스워드를 "TEST"로 변경한 후,

아래 38번째 줄과 같이 그대로 "TEST"로 들어오지 않아야 암호화가 된 것이다.

 

UserTest

 

 

 

User.java에 있는 changePassword 메소드 매개변수로 passwordEncoder을 받는 것으로 진행했다.

 

User

 

 

UserService에 있는 registerUser 메소드와 

AuthenticationService에 있는 login 메소드에서도

passwordEncoder을 매개변수로 받도록 변경했다.

 

UserService

 

 

 

AuthenticationService

 

 

 

 

User

 

 

아래 57번째 줄 : 저장된 Entity의 Password가 passwordEncoder를 통해 일치하는지를 확인한다.

UserTest

 

 

 

jwtUtil은 우리가 만든 클래스이고, PasswordEncoder은 우리가 직접 만든 클래스가 아니기 때문에

App에서 @Bean으로 처리해야 한다.

 

AuthenticationService
App

 

 

 

UserService에도 passwordEncoder을 생성자에서 주입한다.

 

UserService

 

 

UserServiceTest에서도 setUp 부분에서 passwordEncoder을 생성한다.

 

UserServiceTest

 

 

AuthenticationServiceTest setUp 부분에서 passwordEncoder을 생성한다.

 

AuthenticationServiceTest

 

 


 

추가로, 막판에 나를 괴롭힌 에러는 다음과 같다.

 

존재하는 아이디에 대한 회원 정보를 수정할 때, 200을 응답해야 하는데 403을 응답한다는 에러...

 

 

 

하지만 서버를 다시 껐다가 실행하면? 통과가 된다....

.....?

 

하지만 처음만 통과될 뿐, 바로 한 번 더 테스트를 돌리면 통과가 안 된다. 

 

이미 삭제된 아이디를 수정하려고 해서

저렇게 존재하는 아이디가 없다며 403 에러를 주는 것 같은데.... 왜 그러는지 이해가 안 됐다.

 

 

테스트가 깨질 때의 로그를 확인해보니, authenticatedId와 매개변수로 받는 id가 같아야 하는데

다르게 출력되는 걸 볼 수 있었다.

 

 

 

테스트가 안 깨질 때는 아래와 같이 

authenticatedId와 매개변수로 받는 id가 같다.

 

 

테스트가 깨지려면 처음부터 다 깨지던가 해야지 

1번만 통과되고 그 다음부터 통과가 안 되는 건 왜 때문이었을까?

 

 

해결하는데 시간을 너무 많이 잡아먹고, 로그를 찍어봤지만 왜 그러는지 이해가 안 돼서 트레이너님과 대화를 나눠봤다.

 

 

에러의 원인은 jwtUtil.java에서

encode 메소드 리턴 값에서 claim에 userId를 넣을 때 아래와 같이 userId를 넣어야 하는데

'1L'로 하드코딩되어있었기 때문이다.

후......

jwtUtil


📍 h2-consloe 연결이 안 될 때 참고한 글

 

https://moonsiri.tistory.com/33?category=932632

 

 

원래는 62번째 줄처럼 처리했는데,

위 글을 참고하여 57~60번째 줄처럼도 해봤다.

 


3. 느낀 것

6주차도 많이 힘들었지만 7주차는 역대급이었다.

 

전체 테스트 통과를 위해 디버깅은 해야겠는데,

처음 보는 코드가 너무 많아서 어떻게 돌아가는지도 머리에 잘 안 들어오고 

강의를 정말 여러 번 보고 나서야 이해됐다.

 

회고록 정리하면서 시간이 많이 들었긴 하지만

이렇게라도 기록해두지 않으면 언제 배웠냐는 듯 날아가버릴 것 같은 느낌이다.

 

더 자세히 공부하면 정말 깊은 내용일 것 같은데, 추후에 필수로 공부를 더 해야할 것으로 보인다.

 

8주차도 왠지 역대급이 될 것 같다.

 


4. 자기 선언

살아남자.