오늘 배운 것

24-06-08 Spring 개인 과제

무무11 2024. 6. 8. 20:22

오늘은 Spring 개인 과제를 진행하면서 시간을 보냈다. 인증/인가 부분이 워낙 알아야할 새로운 것들이 많다보니 파악하는데 시간이 오래걸렸다. 어느 정도 파악을 하고나니 과제를 진행하는데 큰 어려움은 없었고 시간도 생각보다 오래 걸리지는 않았다.

 

여기에는 오늘 과제를 진행하면서 살짝 어려움이 있었던 부분에 대해 정리해보았다.

 

1. Spring 개인 과제

오늘 스프링 개인 과제 중 인증/인가 부분을 진행하다가 살짝 어려움을 겪은 부분이 있었다. 인증을 받지 않은 사용자(로그인을 하지 않은 사용자)가 접근할 수 있는 것과 그렇지 않은 것을 분리해서 적용해두었고, 인증을 받은 사용자의 역할에 따른 인가처리까지 완료하였다.

 

다만 요구 사항 중 하나인 내가 쓴 글만 수정, 삭제가 가능하다는 부분에 대해서는 처리를 하지 못했다. 인증을 받은 사용자고 수정, 삭제 인가를 받기만 하면 내가 쓴 글이 아니라도 삭제가 가능했다.

 

따라서 이 부분이 수정이 필요했는데 쉽게 해결할 수 있을 줄 알았는데 시간이 좀 걸렸다.

 

먼저 인증을 받은 사용자가 가지고 있는 인증 정보에서 사용자의 정보를 가져와야 했다. SecurityContextHolder에서 인증 정보를 가져왔다.

val userPrincipal = SecurityContextHolder.getContext().authentication.principal

이러고 난 뒤가 문제였는데 저 principal에서 내가 원하는 정보를 뽑아올 수가 없었기 때문이다. 나는 유저의 id를 가져오고 싶었는데 이상하게도 id를 가져올 수 없었다. principal.id를 하면 그냥 뽑아올 수 있을 줄 알았는데 그렇지 않았다.

 

일단 문제 해결을 위한 단서들을 찾아가기 시작했다. 먼저 principal의 반환 타입을 찾아보니 그냥 object 였다.

 

그리고 저 SecurityContextHolder의 principal에 해당하는 부분에 내가 넣어줬던 것이 무엇인지 찾아보았다. 나는 직접 커스텀으로 정의한 UserPrincipal을 사용해 인증 정보에 넣어줬었다. UserDetails를 따로 구현해서 사용하지 않았다.

data class UserPrincipal(
    val id: Long,
    val userIdentifier: String,
    val authorities: Collection<GrantedAuthority>
) {
    constructor(id: Long, userIdentifier: String, roles: Set<String>) : this(
        id = id, userIdentifier = userIdentifier, authorities = roles.map { SimpleGrantedAuthority("ROLE_${it}") }
    )
}

그리고 principal의 반환타입인 object 클래스는 자바의 최상위 클래스라는 것도 알게 되었다. 이때 그냥 저 반환되는 object 클래스를 내가 정의한 클래스인 UserPrincipal 클래스로 타입 캐스팅을 하면 될 것 같았다.

val userPrincipal = SecurityContextHolder.getContext().authentication.principal as UserPrincipal

if (userPrincipal.id != todo.user.id) throw UnauthorizedAccessException("todo")

바로 위와같이 타입캐스팅을 해서 userPrincipal 변수에 넣어주었더니 바로 id를 불러와서 사용할 수 있었다. 할 일 목록 작성자의 id와 인증정보에 있는 id가 다르면 예외를 던지도록 코드를 작성해주었다.

 

이렇게 해두었더니 작성자 본인이 아니면 글과 댓글을 삭제, 수정 하는 것이 불가능하게 만들 수 있었다.

 

위와 같은 방법 말고 @PostAuthorize와 @PreAuthorize 어노테이션을 이용해서 하는 방법도 있었다. 매번 이렇게 인증 정보를 불러와서 비교하는 것을 넣어두는 것이 좋아보이지만은 않았기 때문에 이걸 이용하는 것으로 바꿔보려고 시도해보았었다.

 

아래와 같이 코드를 작성하면 request를 통해 보낸 userId와 인증정보에있는 id가 같은지를 비교한다. 또한 ADMIN인 경우에는 확인하지 않도록 간단하게 코드를 작성할 수 있다.

@PutMapping("/{commentId}")
@PreAuthorize("(#request.userId == authentication.principal.id AND hasRole('USER')) or hasRole('ADMIN')")
fun updateComment(@PathVariable todoId: Long, @PathVariable commentId: Long,
                  @RequestBody request: UpdateCommentRequest): ResponseEntity<CommentResponse>

이것을 아직은 적용하지는 않았는데 request에 작성자의 id를 넣는 것이 좋은 것일까에 대한 의문이 들어서이다.보안상으로도 좋아보이지 않고, 꼭 userId를 요청으로 받을 필요가 없기때문이다. 따라서 일단은 보류해둔 상태이다.

 

위와 같이 2가지 방법으로 본인의 글만 삭제, 수정할 수 있게 만들 수 있다는 것을 알게 되었다. 상황에 따라 합리적으로 둘 중 하나를 택해서 사용하면 될 것 같다.

 

 

2. 오늘 배운 것

- 스프링 시큐리티 과제를 진행하면서 필요한 부분에 대해서는 어느정도 이해가 된 것 같다. 막막하게만 느껴졌는데 어느정도 해소된 느낌이 있다.

- 현재 주어진 과제의 내용을 진행하는데에는 무리가 없었지만, 만약 조금 더 복잡한 요구사항이 주어진다면 어려움을 겪을 것 같다. 현재 아는 내용보다 조금 더 자세히 알아볼 필요가 있는 것 같다.