๋ฐ์ํ
๐ก JWT + SpringSecurity๋ฅผ ์ฌ์ฉํ์ฌ ํ์๊ฐ์ Rest API ๊ตฌํ
โก๏ธ 2ํ ๋ฐ๋ก๊ฐ๊ธฐ: JWT ๋ก๊ทธ์ธ ๋ฐ ํ์ ์ ๋ณด ์กฐํ
โก๏ธ 3ํ ๋ฐ๋ก๊ฐ๊ธฐ: Refresh Token
๊ฐ๋ฐํ๊ฒฝ
- SpringBoot 3.1.4
- kotlin / java 17
- MariaDB
๊ธฐ๋ณธ JPA ์ค์ ์ ๋ค๋ฃจ์ง ์๋๋ค. ๋ฏธ๋ฆฌ ์ค์ ํ ์ํ์ ํ๋ก์ ํธ์์ ๊ฐ์ !!!
1. build.gradle.kts ์ค์
dependencies {
...
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
}
- spring security ๋ฐ jwt ๊ด๋ จ dependency ์ถ๊ฐ
2. Spring Security ๊ธฐ๋ณธ ์ค์
Spring Security์ ๊ธฐ๋ณธ ์ค์ ์ ๋ด์ Config ํ์ผ ์์ฑ
@Configuration
class SecurityConfig{
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain =
http
.httpBasic { it.disable() }
.csrf { it.disable() }
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.authorizeHttpRequests {
it.requestMatchers("/auth/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated()
}
.build()
}
- `httpBasic{ it.disable() }`
- HttpBasic ๊ธฐ๋ณธ ๋ก๊ทธ์ธ ์ธ์ฆ์ฐฝ ์ฌ์ฉ ์ฌ๋ถ
- API ์๋ฒ๋ก ๋ณ๋์ ๋ก๊ทธ์ธ ์ธ์ฆ์ฐฝ ๋ถํ์
- `csrf{ it.disable() }`
- ์ฌ์ดํธ ๊ฐ ์์กฐ ์์ฒญ ์ฐจ๋จ ์ฌ๋ถ
- API ์๋ฒ๋ก ์๋ฒ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ถํ์
- `sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }`
- ์ธ์ ์ ์ฑ ์ค์
- JWT Token์ผ๋ก ์ธ์ฆํ๊ธฐ ๋๋ฌธ์ ์ธ์ ์ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก STATELESS ์ค์
- ` requestMatchers("...").permitAll()`
- ์์ฑํ url์ ์ธ์ฆ์ ์ฌ์ฉํ์ง ์๊ณ , ๋ชจ๋ ์ ์ ํ์ฉ
- ๋๋ถ๋ถ swagger๋ฌธ์๋ ๋ก๊ทธ์ธ ๋ฐ ํ์๊ฐ์ ํ์ด์ง๋ฅผ ์ค์ ํ๋ค.
- `.anyRequest().authenticated()`
- ์ค์ ํ requestMatchers ์ธ ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ๋ ์ฌ์ฉ์์๊ฒ๋ง ์ ๊ทผ ํ์ฉ
์ํธํ ์ค์
Spring Security์ PasswordEncoder๋ฅผ ๊ตฌํํ ํด๋์ค๋ฅผ ๋น์ผ๋ก ์ถ๊ฐ
- ๊ฐ์ฅ ๊ฐ๋ ฅํ ์ํธํ ๋ชจ๋์ธ `BCryptPasswordEncoder()` ๋ก ์์ฑ
@Configuration
class SecurityConfig{
...
@Bean
fun passwordEncoder() = BCryptPasswordEncoder()
}
3. Member Entity ๋ฐ Repository ์์ฑ
ํ์ ์ ๋ณด๋ฅผ ๋ด์ Member Entity ์์ฑ
@Entity
class Member(
@Id
@GeneratedValue(strategy = GenerationType.UUID)
val id: UUID? = null, // ์ฑ ๋ด์์ ์ฌ์ฉ๋๋ ์ฌ์ฉ์์ ๊ณ ์ ํค
@Column(nullable = false, unique = true) // ์ค๋ณต์ ํ์ฉํ์ง ์์
val email: String, // ๋ก๊ทธ์ธ ์ ์ฌ์ฉ๋๋ ์ฌ์ฉ์์ email
@Column(nullable = false)
val password: String,
val name: String, // ์ฑ ๋ด์์ ์ฌ์ฉ๋๋ ์ฌ์ฉ์์ ๋๋ค์
@Enumerated(value = EnumType.STRING)
val type: MemberType = MemberType.MEMBER, // ์ฌ์ฉ์์ ํ์
(Member, Admin..)
val createdAt: LocalDateTime = LocalDateTime.now()
)
- ๊ธฐ์กด์ ํ์์ ๋ด์ ๊ฐ์ฒด ๋ช ์ User ๋ก ์ง์ ํ์์ผ๋, SpringSecurity์์ ์ฌ์ฉ๋๋ User ์ ํผ์ฉ๋๊ธฐ ๋๋ฌธ์ Member๋ก ๋ณ๊ฒฝ
MemberRepository ์์ฑ
interface MemberRepository : JpaRepository<Member, UUID> {
fun findByEmail(email: String?): Member?
}
- `JpaRepository`๋ฅผ ์์
- ํด๋น ํ๋ก์ ํธ์์ email ์ด Member๋ฅผ ํน์ ํ key ๊ฐ์ด ๋๊ธฐ ๋๋ฌธ์ findByEmail ๋ฉ์๋ ์ถ๊ฐ
4. ํ์๊ฐ์
SignController ์์ฑ
@RequestMapping("/auth")
@RestController
class SignController(
private val signService: SignService
) {
@PostMapping("/sign-up")
fun singUp(@RequestBody singUpRequest: SingUpRequest): BaseResponse<String> =
signService.signUp(singUpRequest).run {
BaseResponse(this) // "SUCCESS!"
}
}
data class SingUpRequest(
val email: String,
val password: String,
val name: String
)
- ํ์๊ฐ์ ์ ํ์ํ ์ ์ ์ ๋ณด๋ฅผ ๋ด์ Request DTO ์์ฑ
- DTO๋ฅผ ํ์๊ฐ์ ์ ์ฒ๋ฆฌํ๋ service๋ก ์ ๋ฌ
SignService ์์ฑ
@Service
class SignService(
private val memberRepository: MemberRepository,
private val memberRoleRepository: MemberRoleRepository,
private val passwordEncoder: PasswordEncoder
) {
fun signUp(singUpRequest: SingUpRequest): String {
// (1)
memberRepository.findByEmail(singUpRequest.email)?.let {
throw DuplicateKeyException("Already Exists")
}
// (2)
val member = Member(
email = singUpRequest.email,
password = passwordEncoder.encode(singUpRequest.password),
name = singUpRequest.name
)
memberRepository.save(member)
return "Success!"
}
}
(1) ์ค๋ณต ์ด๋ฉ์ผ ์ฒดํฌ
- ํด๋น ํ๋ก์ ํธ์์ email์ Member๋ฅผ ๊ตฌ๋ถํ๋ Key๊ฐ์ผ๋ก ์ฐ์ด๊ธฐ ๋๋ฌธ์, ์ค๋ณต์ ํ์ฉํ์ง ์๋๋ค
- ์ด๋ฏธ ํด๋น ์ด๋ฉ์ผ์ ๊ฐ์ง Member๊ฐ ์์ ๊ฒฝ์ฐ, `DuplicateKeyException`์ ๋ฑ์
(2) Member ์์ฑ
- Controller๋ก๋ถํฐ ๋ฐ์ DTO ์ ๋ณด๋ก Member ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ password๋ฅผ ๊ทธ๋๋ก ์ ์ฅํ ์ ์๊ธฐ ๋๋ฌธ์ passwordEncoder๋ฅผ ํตํด ์ธ์ฝ๋ฉ ๋ ๊ฐ์ผ๋ก password๋ฅผ ์ ์ฅ
ํ์๊ฐ์ ์์ฒญ
requestMatchers ์ค์ ๋ url ์ธ ์์ฒญ์ผ ๊ฒฝ์ฐ 403
โ๏ธ์ฝ๋๋ ์๋์์โ๏ธ
https://github.com/jeongum/spring-security-kotlin
๋ฐ์ํ