SpringSecurity – 실패한 DSL(javaconfig)

원래 스프링시큐리티는 구조가 단순하다.

Filter가 하나 등록된다

public class FilterChainProxy extends GenericFilterBean

public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator

public interface SecurityFilterChain

ChainOfResponsibility패턴으로 구현된 FilterChain….

그리고 이 Chain에서 순차적으로 Filter를 실행시킨다.으로 인증필터 몇개 넣어준 다음
Request들어올 때 마다 반복문 돌려서 주소패턴 match되면 처리해주는거

  1. WebAsyncManagerIntegrationFilter
  2. SecurityContextPersistenceFilter
  3. HeaderWriterFilter
  4. CsrfFilter
  5. LogoutFilter
  6. UsernamePasswordAuthenticationFilter
  7. DefaultLoginPageGeneratingFilter
  8. DefaultLogoutPageGeneratingFilter
  9. BasicAuthenticationFilter
  10. RequestCacheAwareFilter
  11. SecurityContextHolderAwareRequestFilter
  12. AnonymousAuthenticationFilter
  13. SessionManagementFilter
  14. ExceptionTranslationFilter
  15. FilterSecurityInterceptor
https://www.javadevjournal.com/spring-security/spring-security-filters/
https://docs.spring.io/spring-security/reference/6.0.0-M3/servlet/architecture.html#servlet-filterchainproxy

대강 앞에서 막아줄거 막고 -> Authentication -> Principal threadlocal에 쑤셔박기 -> 꺼내서 Authorization하는 구조

좀 복잡한 필터가 몇개 있는데 사실 간단하게 구현해도 되는 부분이 많다.
UsernamePasswordAuthenticationFilter 등등 몇가지에서 너무 많은걸 자동으로 해주려다 보니 과도하게 복잡해진게 있긴한데.. 그래도 쓸만했었다

DSL이 나오기 전까지는…

프레임워크 자체도 조금 개선이 필요하다고 생각하는데
(UserDetails에 기본User를 생성해놓는다던가…하는 구현체를 너무 많이 만들어놓는것)
그래도 꽤 좋았는데

Java Config DSL에다가 Springboot Autoconfiguration까지 들어가니까…

또 스프링에서 자동으로 해주겠다고 하다가 망한 케이스

처음에 xml로 하나하나 더 설정 할 때는 혼란이 없었다.

설정 해 놓은대로
순차적으로 설정된 필터가 몇개인지
어떤게 설정되어 있는지 보였으니까
오류가 발생하면 어디서 발생했는지 이름만 봐도 바로 알 수 있었다.

그런데 JavaConfig는?

이게 몇년전쯤 쓰던 SpringSecurity 자바설정파일인데 이걸 보고 뭐가먼저 동작할지 예상이나 할 수 있을까


@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
	http
			.csrf().disable()
//좆같은거 추가
		.anonymous().and().anonymous().and().csrf().and()
			.authorizeRequests()
			//resource
			.antMatchers("/favicon.ico", "/vendor/**", "/static/**").permitAll()
			.antMatchers("/", "/home", "/join").permitAll()
			.antMatchers("/console/*").permitAll()
			.antMatchers("/user/join").anonymous()
			.anyRequest().authenticated()
			.and()

			.headers()
			.addHeaderWriter(new StaticHeadersWriter("X-Content-Security-Policy", "script-src 'self'"))
			.frameOptions().disable()
			.and()

			.formLogin()
			.loginPage("/login")
			.permitAll()

			.and()

			.logout()
			.permitAll();
}

	@Bean
	public DbUserDetailsService userDetailsService() {
		return new DbUserDetailsService();
	}

	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public DaoAuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider bean = new DaoAuthenticationProvider();
		bean.setUserDetailsService(userDetailsService());
		bean.setPasswordEncoder(passwordEncoder());
		return bean;
	}

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth
				.authenticationProvider(authenticationProvider());
//				메모리 로그인으로 적용시 사용.
//				.inMemoryAuthentication()
//				.withUser("user").password("password").roles("USER");
	}
}

내 코드에서 이정도 설정을 했지만
WebSecurityConfigurerAdapter에서 또 뭔가 존나게 해놨다

이 설정을 보면 /login이 로그인 페이지로 간다는건 대강 눈치챌 수 있지만 몇번째 필터에서 돌아가는지 알 수 있을까? 문제가 발생하면 디버깅이나 로그를 존나 훑어봐야알 수 있게 된다.
스프링 시큐리티에서는 이런식으로 문제가 발생한다.

결론?

스프링시큐리티는 수동으로 하나하나 설정해서 쓰자
어차피 한번 만들어놓으면 계속 쓰니까
이미 JavaConfig(DSL)로 선언 해 놓은것들도 다 하나하나 생성하는 방식으로 만들어야된다고 생각한다.

https://docs.spring.io/spring-security/reference/6.0.0-M3/servlet/architecture.html#servlet-filterchainproxy

Kotlin 때문에 드러난 SpringFramework DTO Validation의 문제점

발견

  • Kotlin: Validation-Mapping
  • Java: Mapping-Validation

기존에 Java만 사용 할 때는 아무 문제가 없었다
자바 dto는 null이건 뭐건 상관이 없었으니까
일단 값을 처넣은(Mapping) 다음에 Validation을 한다

@Data
class PersonRequestDto {
    @NotEmpty
    @Size(min=1, max=30)
    private String name;
    @NotNull
    @Min(15)
    private Int age;
    @NotNull
    private String phone;
}
data class PersonRequestDto(
    @NotEmpty
    @Size(min=1, max=30)
    val name: String,
    @NotNull
    @Min(15)
    val age: Int,
    @NotNull
    val phone: String?,
)

여기서 코틀린을 자바처럼 하면 어떨게 될까?

오류가 난다
NotNull이 다 무의미해진다
Lombok의 @NonNull을 쓰는것처럼 변수 대입자체가 안되니까

그럼 어차피 안 들어가니까 상관없는거 아닌가?? 라고생각할 수 있지만
validation result 자체를 활용하려고 하거나 전체값의 데이터를 다 확인하고 싶은경우에 그게 불가능하다

해결방안

일단 현재로써는 없다

스프링에서 Validation을mapping 전에 해야하는데
이걸 바꿀 수 있을까….
힘들어보이는데

다른 프레임워크의 경우에는 request body에서validation을 처리하면 되지 않으려나

DTO에 설정되어 있는 field, annotation 스캔해서
json을 validation

코틀린에서 조금 부족하지만 쓸만한 벨리데이션툴이 있다

  • https://github.com/rcapraro/kalidation
  • https://github.com/konform-kt/konform

스타트업에서 OKR 설정하기

구글도 페이스북도 OKR로 성공했다고 한다
유니콘, 데카콘은 다 이걸 한다고?
이거 시크릿과 비슷하다고도 할 수 있도?
OKR만 하면 성공하는건가?
한화그룹금융계열과 SK도 OKR을 도입했다고?

유행하던 경영기법이 조금씩 변해왔는데
제조업 중심에서는 식스 시그마
요즘같은 인간 관리와 동기부여가 중요한 시기에 대두되는게
OKR이다

그런데 OKR책을 봐도 기사를 검색 해 봐도
존나 애매한 소리만 가득하다.
이럴거면 KPI에서도 실패를 용인하고 상향식 목표결정을 하면 다를게 없지 않나?

이게 OKR검색하면 또는 책을봐도 제일 많이 나오는 말인데…

목표는 달성 어렵고 가슴 떨리게

그냥 이것만 봐도 우리나라에서 OKR이 얼마나 병신같이 사용될지 가늠할 수 있다.
KPI와 다를게 없겠지

제대로 활용하려면 좀 더 구체적으로 사례를 보는게 낫다

페이스북의 OKR

: A user is engaged if they reach 7 friends in 10 days.
사용자 가입후 연결한다 7명의 친구 10일 이내에

왜 이 목표가 설정되었는가?
이용 통계를 뜯어보니 열흘 안에 페친이 일곱명 이상인 사람들은
페이스북을 안 떠나고 계속 머물렀다

이 OKR를 달성하기 위해서 할 것은?
이걸 각 부서-팀-개인적으로 결정해서 목표를 위해 움직인다

KPI였다면?
이런 느낌의 목표보다는 조금 전사적인 느낌의 목표가 설정된다

기존사용자가 7명의 친구를 초대한다. 기존 100만명 사용자에 추가 700만 800만의 사용자를 확보한다.
그리고 이를 달성하기 위해 사용자 친구초대 이벤트를 시행

추가 다른 회사의 OKR

  • Slack: 2000 messages sent between a team
  • Zynga: User returns 1 day after signing up
  • Facebook: User connects with 10 friends within 7 days
  • Dropbox: One file in one Dropbox folder on one device
  • Twitter: X users followed, Y% followed back
  • LinkedIn: X connections in Y days

OKR 법칙 재정의

  • 사용자에게 보여줘도 당당할 수 있는
  • 데이터에 기반한
  • 핵심적인 가치를 증대시킬 수 있는
  • 고객-직원-회사 모두에게 관련된 목표

이런게 기준이 되야한다고 생각한다

상향식이고 하향식이고는 상관없다고 보고 적절한 목표가 제시되기만 하면 된다
어차피 대부분 사람들은 그런 인사이트가 없다

자칫 KPI스타일의 목표를 세우기 쉬운데 아래같은건 아니라고 본다

  • 글로벌 서비스를 위한 최소 기능을 개발한다
  • 매출 100억을 달성한다
  • 회원 10만을 달성한다

이런KPI스타일의 목표는 달성하기 위해 병신같은 짓을 할 가능성이 높다.
옛날 골드뱅크같은걸 예로들면
회원 100만명을 확보한다 – 이를 위해서 회원1인당 가입리워드를 5000원을 주고 추천할 경우 5000원을 더 준다.
이렇게 하면 회사가??? 망하겠지
물론 그 회원을 이용해서 뭔가를 성공적으로 한다면 망하지 않을수도 있지만
보통은 다 망했다.

너무 개발적인 목표

  • Spring최신버전으로 비동기 MSA 아키텍처를 구현한다
  • Pull Request를 활용한 코드리뷰 시스템을 정착시킨다

개발부서의 서브 목표가 될수도 있겠지만
OKR은 서비스 자체에 집중할 필요가 있다고 생각한다

[개발자가]회사에서 절대 맡으면 안 되는 업무

일반 행정사무직에서 예시

이걸 개발자에 적용 해 보면?

1 쉽지만 중요한거 개발하기

FocusBoard
중요도
긴급도
1사분면을 떼서

파급력 X축
난이도 Y
1사분면 파급력 +, 난이도 + 필수업무
2사분면 파급력 -, 난이도 + 하지마!!!
3사분면 파급력 – , 난이도 – 시간낭비
4사분면 파급력 +, 난이도 – 거저먹기.. 일도 못하는데 인정받는 사람

상호평가가 없는 회사에서는 4번만 주서먹기가 될 것같기는한데… 실력이 안 늘어서 장기적으로는 안되겠지?
1번과 4번의 조화가 좋아보인다
가끔은 3번도.
2번은 회사에서 아예 필요없는 일일 가능성이 높다

2 핵심기능 개발하기

위랑 좀 겹치는 것 같은데

떨거지 모듈 개발하지 말것
예를들어
배달의민족의 개발자라면
(이런게 있는지 없는지 모르지만)
레거시로 남은 php 어드민기능이라던가 이런거 하지 말라는 것
도메인 분야로 보면
로봇배송, 배송관리, 결제, 정산 다 중요하니까 괜찮음

보통 개발자는 PHP는 할 줄 모른다고 하는게 이롭다
PHP전문개발자는 특화분야로 높은 연봉을 받기도 하지만.. 곁다리 PHP개발 경력은 정말 쓸모없고 성가시기만 하다.

3 남들 다 실패해서 퇴사하는 어려운거 하지말기

담당자가 왜 나갈까

남들은 다 병신이라 못했을까

업무가 쉬운데 처리가 안된다면 다 사정이 있다.
연동해야 할 대상이 좆같거나

4 내가 완성할 수 없는거 떠맡지 말기

원래 업무는 조금 어려운 도전적인 정도가 딱 좋은데
신입이나 2~3년차로 들어갔는데 팀장이라고 주고 프로젝트를 떠맡긴다??
바로 퇴사각~
밤새고 같이 고생해서 완성을 할 수도 있긴한건데
대학생도 아니고 ㅋ 잘 하는 사람 하나만 있어도 업무진행이 다르다.

원래 아무것도 모를 때 남한테 배우기가 제일 쉽다. 일정이상 경력이 쌓이면 나보다 잘하는 사람 찾기도 힘들게 되는 경우가 많으니
(부분적으로 서로 가르쳐주고 하지만 다 뛰어난 사람은 없다)

하지만 신입때는 그냥 거의 모든게 나보다 뛰어난 사람이 넘쳐난다
그래서 신입 때 잘 배울 수 있는데루 가라는것…지름길

5 반복 노가다 업무

개발자도 반복업무가 상당히 많은데

무슨 쿼리 뽑아달라던가
이메일 추출해달라던가 하는거

이런건 업무협의를 잘 하거나
시간을 따로 잡아서 개발을 하면
대부분 업무 자체를 없애버릴 수 있다

직접 구축하기 vs 돈내고 쓰기

이성적으로는 돈 내고 쓰면서 빠르게 개발하는게 낫다는걸 알지만
하나하나 구축하려는 욕심이 든다
적정기준을 정해놓고 따르는게 좋을 것 같은데

서비스 구축을 하면서 사용해야 하는 서비스가 몇가지 있는데

  • 인프라
    • 그냥 서버.. linux, unix, windwos ..
    • k8s cluster
    • object storage
    • cdn
  • DevOps
    • 빌드
    • 배포
  • Logging & 모니터링
    • 로그수집
    • 모니터링
    • 알림

구축하는데 들어가는 비용이 이익을 상회하지 않는 경우
비용: 시간, 돈, 인력
이익: 만족감, 기술발전, 속도, 돈

비교

인프라

IDC에 서버 올려서 직접 구축하면 제일 싸긴하다… 효율적으로 사용할 수 없다.
초기비용이 크고 서버 관리하는 기술도 생각보다 어렵고
경험이 없으면 삽질을 엄청 많이 하게 된다
코드만 만지던 사람들이 생각도 못한 변수가…

그리고 서버를 코드화 할 수 없으면 곤란한 상황이 많이 발생한다.
메이저 클라우드 업체를 쓰면 서버를 완전 코드화할 수 있다.

  • AWS
  • GCP
  • Azure
  • Heroku

서버만 쓸 수도 있지만 클러스터까지 쓸 수도 있고

  • k8s
  • ecs

네이버 클라우드 등 국내 서비스는 안됨. XXX

CDN, ObjectStorage는 클라우드플레어 등 다른 회사를 써도 되고 메이저 클라우드를 써도 되는데 다 코드화가 가능하다.
코드화가 가능하면 인프라 구축이 매우 쉬워진다.
SI도 아닌데 한번 구축하면 끝이지 왜 환경을 재현해야 하냐고???? 하는 경우가 있는데

  • CloudFlare

인프라 코드화의 장점

  • 테스트 환경 구축: production 환경에
  • 서비스 확장이 간편: 인프라 규모를 확장할 때 오류 가능성이 낮다.
  • 테스트 자동화가

DevOps

위에 있는거에서 DevOps는 코드화 해서 쉽게 할 수 있고
githubaction같은 무료서비스도 많으니 그냥 써도 된다. 서버를 직접구축하면 조금 더 빠르긴한데 취향에 따라 마음대로 해도 되는 부분…
아직 돈내고 써서 엄청 좋아지는 서비스가 없다

  • kustomize
  • gitops
  • githubaction
  • gitlabaction?
  • drone
  • argocd
  • keel

로깅 & 모니터링

hadoop, elastic search, prometheus 거의 이 중 한쪽 생태계와 주변기술 아닐까

구축하는게 튜토리얼대로 하면 금방 할 수 있기도 한데…
서비스에 맞게 튜닝을 하려면 신경쓸게 많다.
생각보다 잘 안되는 편

어떻게 보면 진짜 비핵심적인 부분이기도 해서
진짜 필요한 부분에서는 직접 로그를 DB에 저장하던지 하고
전체적으로는 돈내고 써야하는 분야

sentry, datadog, … 등등 검색하면 많다

클라우드에서 기본 제공해주는 서비스는 대부분 ES, Hadoop 기반이고
커스터마이징이 불편해서 별도로 작업이 많이 필요하다.

Querydsl crash – lombok plugin

plugin: 'org.jetbrains.kotlin.plugin.lombok

롬복 x 코틀린 오류

롬복과 코틀린을 함께 쓰려고 하면 오류가 많이 발생한다

컴파일타임에 진행되는 일의 순서 때문에 문제가 발생해서 매우 처리하기 힘들다
lombok, kapt, querydsl, protocol buffer, kotlin

이런저런것들이 컴파일타임에 순서대로 돌아가는데
이 오픈소스들이 애초에 이렇게 같이 쓰는 환경을 고려하고 만든게 아닌지라…
자바에서 코틀린 라이브러리를 쓰는 것 조차 쓸 수는 있는정도니까…
꼭 같이 써야겠다면 그레이들에서 모듈을 분리해서 다른 라이브러리를 쓰듯이 만들어 놔야한다
같은 프로젝트에서 같은모듈에서는 그냥 포기하자

lombok

> Task :server:core:compileKotlin
w: Lombok Kotlin compiler plugin is an experimental feature. See: https://kotlinlang.org/docs/components-stability.html.

Lombok Kotlin compiler plugin is an experimental feature. See: https://kotlinlang.org/docs/components-stability.html.

compileKotlin

java.lang.IllegalStateException: Error during writing proto for descriptor: @javax.annotation.Generated public open class QAdminEntity : com.querydsl.core.types.dsl.EntityPathBase<(kr.steppay.account.domain.model.actor.AdminEntity..kr.steppay.account.domain.model.actor.AdminEntity?)> defined in kr.steppay.account.domain.model.actor in file QAdminEntity.java
Source file: /Users/archmagece/work/steppay/account-service/server/domain/build/generated/source/kapt/main/kr/steppay/account/domain/model/actor/QAdminEntity.java
	at org.jetbrains.kotlin.incremental.JavaClassesTrackerImplKt.convertToProto(JavaClassesTrackerImpl.kt:90)
	at org.jetbrains.kotlin.incremental.JavaClassesTrackerImpl$onCompletedAnalysis$2.invoke(JavaClassesTrackerImpl.kt:68)
	at org.jetbrains.kotlin.incremental.JavaClassesTrackerImpl$onCompletedAnalysis$2.invoke(JavaClassesTrackerImpl.kt:67)
	at org.jetbrains.kotlin.util.PerformanceCounter.time(PerformanceCounter.kt:101)
	at org.jetbrains.kotlin.incremental.JavaClassesTrackerImpl.onCompletedAnalysis(JavaClassesTrackerImpl.kt:67)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:110)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:129)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:86)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:540)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:531)
	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:113)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:531)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:188)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:154)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:169)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:90)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
	at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:386)
	at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:110)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:303)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl$rebuild(IncrementalCompilerRunner.kt:99)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:124)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:74)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:607)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:96)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1659)
	at jdk.internal.reflect.GeneratedMethodAccessor105.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.NullPointerException
	at org.jetbrains.kotlin.lombok.processor.RequiredArgsConstructorProcessor.isFieldRequired(RequiredArgsConstructorProcessor.kt:27)
	at org.jetbrains.kotlin.lombok.processor.RequiredArgsConstructorProcessor.getPropertiesForParameters(RequiredArgsConstructorProcessor.kt:24)
	at org.jetbrains.kotlin.lombok.processor.AbstractConstructorProcessor.contribute(AbstractConstructorProcessor.kt:19)
	at org.jetbrains.kotlin.lombok.LombokSyntheticJavaPartsProvider.computeSyntheticParts(LombokSyntheticJavaPartsProvider.kt:80)
	at org.jetbrains.kotlin.lombok.LombokSyntheticJavaPartsProvider.getSyntheticParts(LombokSyntheticJavaPartsProvider.kt:74)
	at org.jetbrains.kotlin.lombok.LombokSyntheticJavaPartsProvider.generateConstructors(LombokSyntheticJavaPartsProvider.kt:63)
	at org.jetbrains.kotlin.resolve.jvm.CompositeSyntheticJavaPartsProvider.generateConstructors(SyntheticJavaPartsProvider.kt:59)
	at org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassMemberScope$constructors$1.invoke(LazyJavaClassMemberScope.kt:102)
	at org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassMemberScope$constructors$1.invoke(LazyJavaClassMemberScope.kt:84)
	at org.jetbrains.kotlin.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408)
	at org.jetbrains.kotlin.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527)
	at org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor.getConstructors(LazyJavaClassDescriptor.kt:146)
	at org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor.getConstructors(LazyJavaClassDescriptor.kt:45)
	at org.jetbrains.kotlin.serialization.DescriptorSerializer.classProto(DescriptorSerializer.kt:115)
	at org.jetbrains.kotlin.incremental.JavaClassesTrackerImplKt.convertToProto(JavaClassesTrackerImpl.kt:88)
	... 43 more


Keycloak 인증 솔루션 선택 – 그리고 ext개발

관련기술 모음

Keycloak vs CAS 선택은

국내에서는 Springboot을 많이 쓰다보니 CAS를 많이 쓸 것 같지만
사용의 난해성 때문에 Keycloak이 더 많이 사용된다
Keycloak에서 커스터마이징 해서 추가기능은 구현할 수 있지만 약간의 문제가 있다
– 별도 인스턴스를 띄워야한다는 문제
– 기본지원되는 형태로 사용해야하는 문제
극한의 커스터마이징을 하려면… 그냥 따로 만드는게 낫다
서비스 초반에는 있는 그대로 쓰자….

CAS는 뭔가 실행하는 것도 매우 힘들고 라이브러리처럼 사용해야하는데 그것도 힘들다. 도큐먼트도 없고… 그냥 인증스펙 구현을 어떻게 했는지 참고하는 코드로 쓰기는 좋다
https://memo.polypia.net/archives/3783

간단하게 결론을 내면
뭘 쓰건 기본기능 그대로 쓸게 아니라면 사용이 쉽지는 않다.

초반에는 서비스를 오픈소스에 맞춰서 쓰자

Keycloak 사용후기

개인적인 경험으로 봤을 때 keycloak이 CAS보다 간단하다.
그냥 기본기능을 가지고 사용하기도 쉽고
추가기능 구현한달정도 보다보니 구조파악이 되고 좀 할만해졌다

JBoss기반으로 만들어져서 Spring생태계에 익숙한 개발자가 접근하기 쉽지는 않다
jas-rx 기반 resteasy, wildfly 서버, jboss의존성 관리 등등..
그냥 도큐먼트만 보고 하려면 rest api 호출하는것도 쉽지가 않다.(공식문서에 주소를 엉터리로 적어놔서 ㅋ)
keycloak 소스코드와 인터넷 질문답변을 잘 읽어봐야한다
그리고 샘플 확장코드들

  • https://github.com/keycloak
  • https://github.com/ScriptonBasestar-io/keycloak-providers
  • https://github.com/thomasdarimont/keycloak-user-storage-provider-demo
  • https://www.baeldung.com/java-keycloak-custom-user-providers
  • https://github.com/thomasdarimont/keycloak-extension-playground
  • https://github.com/zonaut/keycloak-extensions

Keycloak 문제점? 헷갈리는 포인트

Keycloak 설명

오픈소스
회원관리 oauth2-oidc, saml 서버기능 지원
외부 Oauth 소셜로그인 지원(구글,페북,인스타..등)

헷갈리는 점

이해를 못해서 헷갈리는 점 말고 설계가 잘못됐다고 생각하는 부분

import가 아니고 partial import

json으로 export하고 import 한다고 하면 서버 설정전체를 집어넣고 내보내는거라고 생각하기 쉬운데
지원되는 항목이 정해져 있다
추가기능으로 설치한것들은 지원이 안되는 것 같다

rabbitmq EventListener를 추가했더니 export는되는데 import가 안된다

기본 client

realm을 처음 생성할 때 clients가 생성된다

  • {realm_name}-realm 이건 렐ㅁ을 생성할 때 마다 master 렒에 추가된다
  • master-realm은 master 렒의 Clients에 추가된다
  • account 계정관리 권한 이걸 지우면 로그인도 안된다 https://keycloak.com/auth/realms/master/account/
  • account-console 콘솔로그인 권한
  • account-cli
  • broker 소셜로그인 권한인가
  • security-admin-console

기본 옵션을 클라이언트에 달아놓은게 맘에 안들어.. 수정/삭제 버튼도 달려있다.

role

기본 client의 문제가 이어진다

기본 client는 고유 권한을 가지고 있다.

keycloak에서는 previlege를 별도로 관리하지 않아서
고유 client가 role이라는 이름으로 previlege(role)을 들고있다
소스에 보니까 string으로 선언이 돼 있어서 이름만 같으면 되나 싶어서
같은 이름 생성 해 봤는데… 이름같아서 헷갈리기만 하도 안된다

전체에서 관리되는 role과 client에서만 관리되는 role 그리고 기본client의 role이 별도로 존재한다고 봐야할까

새로운 client에 previlege를 부여하려면 composite roles를 선택해서 기본 client의 previlege를 associate 해야한다

그냥 잘못 만든 점

Events – Evnets Config에서

EventListeners를 설정하고 evnet종류를 지정할 수 있는데

EventListener가 한개일 때는 괜찮은데 여러개일 때
리스너 종류에 따라 다른 이벤트 설정을 못 하게 돼 있다.

ex) jboss-logging이 기본인데 이건 전부 수신하고
email로는 회원가입만 보내고 싶다… 라면..?? 이거 안된다.

User – Role – Previlege

구분이 좀 됐으면

아쉬운 점

플러그인 참여가 오픈되어있지 않다

사용자가 꽤 많은 것 같은데 기본지원되는 소셜로그인 말곤 따로 만들어야된다
커뮤니티 좀 활성화 돼 있으면 사람들이 알아서 만들고 할텐데

과도한 커스터마이징 미지원

버전이 14까지 올라갔는데 지원이 안되고 있네
좀 시도해볼만하지 않나
oauth말고 그냥 서비스 특화된 방식

커스텀 필드

되긴되는데 좀 불편

주소 문제

https://auth.service.com
/ 주소는 무슨 홍보페이지같은거고
/auth가 실제 서비스 주소가 된다
/auth/realms/master 처럼 주소가 나온다

기본 서비스 clients랑 같은 문제인데… master realm, 서비스 어카운트 등은 일반사용자와 다른 테이블에 다른 차원으로 관리되는게 좋은데 개인적으로는

각 렒별로 주소를 별도로 할당하고싶기도 한데 그것도 안된다
누가봐도 keycloak를 쓰고 있다는걸 알 수 있는 상태로 오픈해야한다

reverse proxy로 처리하면 할 수는 있을 것 같은데 좀 봐야겠다

좆같은 캐시

infinispan과 jpa 두 계층으로 데이터를 저장하는데
캐시를 굉장히 무겁게 사용하고 있는 것 같다
솔직히 잘 모르겠다. 그냥 좆같다
어디서 뭐가 저장 돼 있는지 따라갈수가 없어서 뭘 수정 할 수도 없다

RestEasy

이런 그지같은 rest api client는 스프링 web client 말고는 없다

호출 수백번 반복

회원가입할 때 저장을 수십번 하는 것 같은데
디비에 필드단위로 정규화한 데이터를 저장하는 병신짓은 아무도 안하는게 당연한데
그런식으로 저장하고 있는거 아닐까

성능

뭐 이따윈지

DDD

DDD를 잘 쓰려고 노력한 것 같은데
잘 하지는 못했다

hmm

초기서비스용으로 있는그대로 쓰면 괜찮다
학습용으로 좋다
개인서버용 정도로 쓰기 좋다

암호화폐 거래소 프로젝트

오픈소스 프로젝트

  • peatio
    https://rubykube.io/#
    https://github.com/openware/peatio
    https://github.com/rubykube/peatio-trading-ui
    https://github.com/rubykube/peatio-react
    https://github.com/peatio/helm-charts
  • CoreCX
    https://bitbucket.org/account/user/margincallio/projects/MRGC
  • WLOX
    https://github.com/wlox/wlox/
  • bitex
    https://github.com/blinktrade/bitex
  • viabtc
    https://github.com/viabtc/viabtc_exchange_server
  • OpenDAX
    https://github.com/openware/opendax

상용 프로젝트

이건 그냥 서비스 중인 회사들

프로젝트별 특징

Peatio

개발언어: RubyOnRails
데이터 저장소: Mysql