Skip to content

Commit 235f04a

Browse files
committed
initial commit
0 parents  commit 235f04a

20 files changed

+1692
-0
lines changed

.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
.gradle
2+
build/
3+
!gradle/wrapper/gradle-wrapper.jar
4+
!**/src/main/**/build/
5+
!**/src/test/**/build/
6+
7+
### STS ###
8+
.apt_generated
9+
.classpath
10+
.factorypath
11+
.project
12+
.settings
13+
.springBeans
14+
.sts4-cache
15+
bin/
16+
!**/src/main/**/bin/
17+
!**/src/test/**/bin/
18+
19+
### IntelliJ IDEA ###
20+
.idea
21+
*.iws
22+
*.iml
23+
*.ipr
24+
out/
25+
!**/src/main/**/out/
26+
!**/src/test/**/out/
27+
28+
### NetBeans ###
29+
/nbproject/private/
30+
/nbbuild/
31+
/dist/
32+
/nbdist/
33+
/.nb-gradle/
34+
35+
### VS Code ###
36+
.vscode/

README.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# About API Module
2+
3+
## Bundled
4+
5+
- letsdev-password-encoder-api
6+
`com.github.merge-simpson:letsdev-password-encoder-api:${version}`
7+
- letsdev-password-encoder-port
8+
`com.github.merge-simpson:letsdev-password-encoder-port:${version}`
9+
- letsdev-password-encoder-exception
10+
`com.github.merge-simpson:letsdev-password-encoder-exception:${version}`
11+
12+
## Installation
13+
14+
`build.gradle.kts`에서 다음과 같이 추가합니다. (또는 번들된 의존성을 각각 추가할 수 있습니다.)
15+
16+
```kotlin
17+
repositories {
18+
mavenCentral()
19+
maven { url = uri("https://jitpack.io") } // added
20+
}
21+
22+
dependencies {
23+
implementation("com.github.merge-simpson:letsdev-password-encoder-factory:0.1.0") // added
24+
}
25+
```
26+
27+
# Features
28+
29+
이 모듈은 Spring Crypto 라이브러리에 의존성을 갖고 있습니다. (`org.springframework.security:spring-security-crypto`)
30+
31+
- [Password Encoder Factory](#password-encoder-factory)
32+
- [Cache Password Encoder Instances And Expire Them Automatically.](#패스워드-인코더-객체-캐싱)
33+
34+
# Password Encoder Factory
35+
36+
제공하는 패스워드 인코더 목록은 다음과 같습니다.
37+
38+
- bcrypt
39+
- argon2
40+
- argon2id
41+
- argon2d
42+
43+
## 패스워드 인코더 인스턴스 생성하기
44+
45+
팩토리는 다음처럼 두 방식으로 패스워드 인코더를 생성합니다.
46+
47+
```java
48+
// 일반 패스워드 인코더 (내부에서 솔트를 자동으로 생성합니다.)
49+
var encoder = factory.create(option);
50+
51+
// 커스텀 솔트를 사용하는 패스워드 인코더
52+
// 직접 만든 솔트를 넣을 수 있으며, 내부에서 추가적인 랜덤 솔트를 만들지 않습니다.
53+
var customSaltingEncoder = factory.createCustomSaltingEncoder(option);
54+
```
55+
56+
- option: <<interface>> PasswordEncoderOption
57+
- option 인스턴스는 `PasswordEncoderType` 객체를 반환하는 `encoderType()` 메서드를 갖습니다.
58+
59+
Examples
60+
61+
- [Create bcrypt password encoder](#bcrypt)
62+
- [Create argon2id password encoder](#argon2id)
63+
- [Create argon2d password encoder](#argon2d)
64+
65+
### BCrypt
66+
67+
```java
68+
var factory = new PasswordEncoderFactory();
69+
var option = new BcryptPasswordEncoderOption(12);
70+
71+
var bcryptPasswordEncoder = factory.create(option);
72+
```
73+
74+
### Argon2id
75+
76+
```java
77+
var factory = new PasswordEncoderFactory();
78+
var option = Argon2idPasswordEncoderOption.fromDefaultBuilder()
79+
.gain(3f) // optional
80+
.build();
81+
82+
var bcryptPasswordEncoder = factory.create(option);
83+
```
84+
85+
**Argon2id의 옵션들**
86+
87+
- memoryInput: 입력한 메모리 비용
88+
- memory: 계산된 메모리 비용 (빌더는 이것 대신 위 파라미터를 입력하게 함.)
89+
- memoryInput이 없을 때 m ≥ 93750 ÷ ((3 × t − 1) × α) (단위: kB)
90+
- saltLength: 솔트 길이. 기본 값: 16 Byte
91+
- hashLength: 해시 길이. 기본 값: 32 Byte
92+
- parallelism: 병렬성. 기본값: 1
93+
- iterations: 반복성. t ≥ 1
94+
- alpha: m ≲ 64 MiB일 때 α ≈ 95% 범위를 추천. m이 충분히 크면 α를 감소시켜도 됨.
95+
- gain: 메모리 비용 계수(증폭비)
96+
97+
### Argon2d
98+
99+
```java
100+
var factory = new PasswordEncoderFactory();
101+
var option = Argon2dPasswordEncoderOption.fromDefaultBuilder()
102+
.gain(3f)
103+
.build();
104+
105+
var bcryptPasswordEncoder = factory.create(option);
106+
```
107+
108+
**Argon2d의 옵션은 Argon2id와 같은 항목을 같습니다.**
109+
110+
## 패스워드 인코더 객체 캐싱
111+
112+
패스워드 인코더 인스턴스를 캐싱합니다. ([Caffeine Cache](https://github.com/ben-manes/caffeine) 기반 팩토리)
113+
114+
**기본 생성자를 사용한 캐시 만료 기본값**
115+
116+
```java
117+
var factory = new PasswordEncoderFactory();
118+
```
119+
120+
- 마지막 접근으로부터 최대 10분 동안 살아 있습니다.
121+
- 최대 100개를 보존합니다.
122+
123+
**Builder 사용으로 캐시의 만료 설정을 커스텀하기**
124+
125+
```java
126+
var factory = PasswordEncoderFactory.builder()
127+
.expireAfterAccess(3, TimeUnit.MINUTES)
128+
.maximumSize(3)
129+
.removalListener((key, value, cause) ->
130+
log.info("Cache entry for key {} was removed because of: {}", key, cause)
131+
)
132+
.build();
133+
```
134+
135+
- expireAfterAccess(duration), expireAfterAccess(duration, TimeUnit)
136+
- 마지막 조회로부터 해당 시간 후 만료됩니다.
137+
- expireAfterWrite(duration), expireAfterWrite(duration, TimeUnit)
138+
- 생성으로부터 해당 시간 후 만료됩니다.
139+
- maximumSize: 최대 저장 개수
140+
- maximumWeight: 최대 저장 용량
141+
- removalListener((k, v, cause) -> {}): 요소 삭제 이벤트 리스너를 추가할 수 있습니다.
142+
143+
### 메서드 간 공유되는 캐시
144+
145+
지금 데모 버전에서 사용하는 패스워드 인코더 인스턴스는 모두
146+
`PasswordEncoder` 인터페이스와 `CustomSaltingPasswordEncoder`를 동시에 구현합니다.
147+
148+
```java
149+
var factory = new PasswordEncoderFactory();
150+
var argon2IdOption = Argon2idPasswordEncoderOption.fromDefaultBuilder()
151+
.gain(3f)
152+
.build();
153+
154+
// 같은 옵션을 사용하여 생성하면, 다음 두 메서드는 같은 인스턴스를 캐싱하여 반환합니다.
155+
var passwordEncoder = factory.create(argon2IdOption);
156+
var customSaltingPasswordEncoder = factory.createCustomSaltingEncoder(argon2IdOption);
157+
```
158+
159+
```kotlin
160+
// 다음 결과를 기대할 수 있습니다.
161+
assertSame(passwordEncoder, customSaltingPasswordEncoder)
162+
```

build.gradle.kts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2+
import org.springframework.boot.gradle.tasks.bundling.BootJar
3+
4+
plugins {
5+
java
6+
id("java-library")
7+
id("org.springframework.boot") version "3.3.4"
8+
id("io.spring.dependency-management") version "1.1.6"
9+
kotlin("jvm") version "1.9.22"
10+
kotlin("plugin.spring") version "1.9.22"
11+
id("maven-publish")
12+
}
13+
14+
group = "me.letsdev"
15+
version = "0.1.0"
16+
17+
java {
18+
toolchain {
19+
languageVersion = JavaLanguageVersion.of(21)
20+
sourceCompatibility = JavaVersion.VERSION_21
21+
targetCompatibility = JavaVersion.VERSION_21
22+
}
23+
}
24+
25+
repositories {
26+
mavenCentral()
27+
maven { url = uri("https://jitpack.io") }
28+
}
29+
30+
dependencies {
31+
// instance cache
32+
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
33+
34+
// spring dependencies
35+
api("org.springframework:spring-web")
36+
api("org.springframework.boot:spring-boot-starter")
37+
api("jakarta.annotation:jakarta.annotation-api")
38+
39+
// error code
40+
api("com.github.merge-simpson:letsdev-password-encoder-api:0.1.0")
41+
42+
// spring crypto (password encoder delegator)
43+
api("org.springframework.security:spring-security-crypto")
44+
api("org.bouncycastle:bcprov-jdk18on:1.78.1")
45+
46+
// test
47+
testImplementation("org.springframework.boot:spring-boot-starter-test")
48+
testImplementation(kotlin("test"))
49+
testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
50+
testImplementation("org.jetbrains.kotlin:kotlin-reflect")
51+
testImplementation("io.mockk:mockk:1.13.12")
52+
}
53+
54+
tasks.withType<Test> {
55+
useJUnitPlatform()
56+
}
57+
58+
tasks.withType<KotlinCompile> {
59+
kotlinOptions {
60+
jvmTarget = "21"
61+
}
62+
}
63+
64+
tasks.named<BootJar>("bootJar") {
65+
enabled = false
66+
}
67+
68+
tasks.named<Jar>("jar") {
69+
enabled = true
70+
archiveClassifier.set("") // remove suffix "-plain"
71+
}
72+
73+
sourceSets {
74+
test {
75+
java {
76+
setSrcDirs(listOf("src/test/kotlin"))
77+
}
78+
}
79+
}
80+
81+
publishing {
82+
publications {
83+
create<MavenPublication>("mavenJava") {
84+
from(components["java"])
85+
groupId = "com.github.merge-simpson"
86+
artifactId = project.name
87+
version = project.version.toString()
88+
}
89+
}
90+
repositories {
91+
maven {
92+
name = "localMaven"
93+
url = uri("${rootProject.projectDir}/build/repos")
94+
}
95+
}
96+
}
97+
98+
tasks.named("publishToMavenLocal").configure {
99+
dependsOn("assemble")
100+
}

gradle/wrapper/gradle-wrapper.jar

42.6 KB
Binary file not shown.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
distributionBase=GRADLE_USER_HOME
2+
distributionPath=wrapper/dists
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4+
networkTimeout=10000
5+
validateDistributionUrl=true
6+
zipStoreBase=GRADLE_USER_HOME
7+
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)