한참을 미뤄오던 패스워드 암호화도 진행하기로 했다. 사실 대충 해쉬 함수가 내장되어 있다던지 하는 식으로 별 거 없을줄 알았는데 Spring security를 이용하는 걸 권장하길래 결국 Spring security를 추가하게 됐다.
Spring security의 config class를 만들어 @Configuration 정의 후 PasswordEncoder를 반환하는 Bean을 만들고 내부에선 구현체인 BCryptPasswordEncoder을 반환한다.
이걸 서비스에서 private val passwordEncoder: PasswordEncoder
처럼 주입받아 사용하면 된다.
BCryptPasswordEncoder는 BCrypt 해싱 함수를 사용한다는데 salt도 자기가 알아서 추가하고 해싱을 여러차례 걸쳐서 진행한다고 한다. 정확히는 모르겠지만 대충 꽤 안전하겠구나라는 생각은 든다.
Encoder를 사용하는 방식, 사용하는 위치는 또 제각각인 것 같은데 나는 비즈니스 로직에서 일단은 처리하기로 했다. 밑에서 다룰 주제처럼 이것도 언젠가 체크만 전문적으로 책임지는 객체를 생성하지 않을까 싶다.
CRUD중 CUD는 거의 유저 정보를 조회해서 권한이 있는지 체크를 해야하기 때문에 비즈니스 로직과 Entity에서 유저를 조회하고 권한을 체크하는 식으로 권한을 체크하게 됐다.
그런데 hasPermission(userDto) 같은 걸 만들긴 했지만 유저 조회가 너무 많아지고 Entity에서 권한 체크하는 조건도 추가될 수도 있고 하니 뭔가 깔끔해보이지가 않았다. 그리고 매개변수로 추가로 현재 세션의 userId를 받아야 하는 것도 서비스가 커질수록 별로일 것 같다는 생각은 들었다.
그런데 이것 말고 별 방법 없지 않나? 싶어 체크해보니 방법이 더 있는 것 같다.
지금은 세션을 쓰지만 토큰으로 검증을 바꾸게 되면 토큰의 유효성 검증도 매번 진행하게 된다. 그런 점에서 AOP(Aspect oriented Programming), 관점 지향 프로그래밍 의 개념을 채택해서 코드의 중복을 해결하는 점이다.
(ex: 권한을 체크해야하는 관점 -> 세션을 이용해야하는 관점이 생기고 이 부분을 어노테이션으로 만들어 사용가능하게 한다.)
@SpringBootApplication의 @EnableAutoConfiguration 안에는 이미 AOP를 위한 기본 설정이 되어 있고 @Aspect 어노테이션을 사용한 class를 만들고 어노테이션을 정의해 권한 체크를 위임할 수 있다.
실제로 원하는대로 작동하는지 예측할 정도로 공부하진 않아 직접 사용하지는 못했지만 구조를 잡아보면 이렇게 어노테이션을 통해 인터셉트 한다는 느낌인 것 같다.
권한이 없는 유저에 대한 별도 처리가 필요한 게 아니면 꽤 유연하게 사용 가능해보인다.
@Aspect
@Component
class AuthorizationAspect(
private val userService: UserService
) {
@Before("@annotation(CheckPermission) && args(userId,..)")
fun checkPermission(userId: Long) {
val currentUser = userService.getCurrentUser()
if (/*권한체크*/) {
throw /*...*/
}
}
}
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class CheckPermission
// 서비스
@CheckPermission
fun createTodo(userId: Long) { /*...*/}
아직 Jwt를 써보지 않았기에 손도 못대본 Spring security에서도 역할 기반 접근 제어(Role-Based Access Control, RBAC) 를 지원하기 때문에 어플리케이션 전반적인 권한 체크를 진행할 수 있다고 한다.
이쪽은 Config부터 전반적인 설정의 규모가 달라 엄격해보여서 내 프로젝트에 적용하기는 꽤 어려워보인다. 엄격하기 때문에 더욱 조심해서 설정해야할 것 같다.
WebSecurityConfigurerAdapter
를 상속받은 Config class가 이런 저런 어노테이션을 상속받고 구현해야하는 멤버(ex. userDetailsService)도 꽤 다양해서 이 쪽은 각잡고 주로 쓰는 부분을 공부해야할 것 같다. 도무지 정리할 수준이 안된다.
코드 중복이 많은 수동 방식, AOP를 이용한 방식 보다 인증, 인가에서 가장 강력한 보안을 자랑하는 Spring security를 결국 잘 써먹어야할 것 같다.
어플리케이션 전반적인 권한 체크자체가 신뢰가 가는 키워드고 보장이 되는 반면에 수동 체크, AOP 체크는 명확하지만 휴먼 에러의 위험성이 존재할 거라 본다.
이번 과제 해셜 세션은 실시간이 아닌 Step별로 녹화 영상이 제공됐다.
확실히 미숙한 영역의 Spring이라 그런지 놓친 부분이 많아서 비교하면서 또 기록해보기로 했다.
문자열 s
가 입력되었을 때 다음 규칙을 따라서 이 문자열을 여러 문자열로 분해하려고 합니다.
s
에서 분리한 문자열을 빼고 남은 부분에 대해서 이 과정을 반복합니다. 남은 부분이 없다면 종료합니다.문자열 s
가 매개변수로 주어질 때, 위 과정과 같이 문자열들로 분해하고, 분해한 문자열의 개수를 return 하는 함수 solution을 완성하세요.
class Solution {
fun solution(s: String): Int {
var answer: Int = 0
var x = ' '
var xCount = 0
var notXCount = 0
s.forEachIndexed { index, it ->
val isLastIndex = index == s.length - 1
if (!isLastIndex && xCount == 0) {
xCount++
x = it
} else {
if (it == x) xCount++
else notXCount++
if (xCount == notXCount) {
answer++
xCount = 0
notXCount = 0
} else if (isLastIndex) {
answer++
}
}
}
return answer
}
}
효율 좋게 풀었다고 생각이 들긴 하는데 너무 C style 코드인게 마음에 안든다.
이번 문제는 문자열을 나누라고 되어있지만 실제로 s를 나누거나 빼가면서 풀어야 하는 문제는 아니고 쉽게 생각해서 나누는 횟수만 지정하면 되기 때문에 O(n)으로 s만 순회하고 answer를 이용하기로 했는데 그 과정에서 코드와 변수가 너무 많아졌다.
알고리즘적으로는 사실 어려울 것 없이
1. 첫번째 글자 x를 지정한다
2. 첫번째 글자가 아니면 x, notX를 계산하며 기록한다
3. 기록이 끝난 숫자가 같으면 문자열을 나눈 걸로 가정하고 횟수를 올린다.
4. 마지막 글자에서 문자열 계산이 안되면 횟수를 올린다.
시간 복잡도를 최소화 하는 방식으로만 접근해서 이렇게 됐는데 아무래도 Kotlin 스럽게 짠 부분은 전혀 없는 것 같은게 아쉬울 따름이다.
위안이라면 다른 코드중에서도 Kotlin 스럽게 짜보려는 코드보단 어쩔 수 없이 복잡한 조건이 많이 들어간 경우가 많았다.
그 중에서도 가장 깔끔한 코드를 골라오자면 Stack을 이용한 풀이가 가장 보기 좋았다.
fun solution2(s: String): Int {
var answer: Int = 0
val stack = mutableListOf<Char>()
s.forEach {
if (stack.isEmpty()) {
answer++
stack.add(it)
} else if (stack.first() == it) {
stack.add(it)
} else {
stack.removeFirst()
}
}
return answer
}
이걸 보고 든 생각이 stack을 이용하면 count를 할 필요가 없어 보이고 문제가 stack 구조를 유도한 건가 싶다.