작고 안전한 웹 어플리케이션
어플리케이션의 제품 정보와 사용자는 데이터베이스에 저장되고,
각 사용자의 암호는 BCrypt
를 사용하여 암호화된다.
이 작은 프로젝트의 구현은 다음과 같다
데이터베이스
사용자 관리
인증 논리 구현
주 페이지
실행 및 테스트
사용자 관리
사용자는 데이터베이스에 저장되며, 사용자의 암호는 BCrypt
를 사용하여 암호화된다.
PasswordEncoder 등록
@Configuration
class UserManagementConfig {
@Bean
fun bCryptPasswordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
@Bean
fun sCryptPasswordEncoder(): PasswordEncoder = SCryptPasswordEncoder()
}
User 등록
USER ENTITY
@Entity
class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
val username: String,
val password: String,
@Enumerated(EnumType.STRING)
val algorithm: EncryptAlgorithm,
@OneToMany
val authorities: List<Authority>
) {
enum class EncryptAlgorithm {
BCRYPT, SCRYPT
}
}
interface UserRepository: JpaRepository<User, Long> {
fun findByUsername(username: String): User?
}
AUTHORITY ENTITY
@Entity
class Authority(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
val name: String,
@ManyToOne
@JoinColumn(name = "user")
val user: User
) {
}
interface AuthorityRepository: JpaRepository<Authority, Long> {
}
인증 논리 구현
UserDetails
class CustomUserDetails(
private val user: User
): UserDetails {
override fun getAuthorities(): List<GrantedAuthority> = user.authorities
.map { authority -> GrantedAuthority { authority.name } }
.toList()
override fun getPassword(): String = user.password
override fun getUsername(): String = user.username
override fun isAccountNonExpired(): Boolean = true
override fun isAccountNonLocked(): Boolean = true
override fun isCredentialsNonExpired(): Boolean = true
override fun isEnabled(): Boolean = true
}
UserDetailsService
@Service
class CustomUserDetailService(
private val userRepository: UserRepository
) : UserDetailsService {
override fun loadUserByUsername(username: String): UserDetails = userRepository.findByUsername(username)
?.let { CustomUserDetails(it) }
?: throw UsernameNotFoundException("User not found")
}
AuthenticationProvider
@Service
class CustomAuthenticationProvider(
private val userDetailService: CustomUserDetailService,
) : AuthenticationProvider {
private val sCryptPasswordEncoder: SCryptPasswordEncoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
private val bCryptPasswordEncoder: BCryptPasswordEncoder = BCryptPasswordEncoder()
override fun authenticate(authentication: Authentication): Authentication {
val name = authentication.name
val password = authentication.credentials.toString()
val user: CustomUserDetails = userDetailService.loadUserByUsername(name)
return when (user.getUser.algorithm) {
BCRYPT -> {
bCryptPasswordEncoder.matches(password, user.password)
}
SCRYPT -> {
sCryptPasswordEncoder.matches(password, user.password)
}
}
.takeIf { it }
?.let { return UsernamePasswordAuthenticationToken(user.username, user.password, user.authorities) }
?: throw BadCredentialsException("Bad credentials")
}
override fun supports(authentication: Class<*>): Boolean = UsernamePasswordAuthenticationToken::class.java.isAssignableFrom(authentication)
}
SecurityConfiguration
@Configuration
@EnableWebSecurity
class SecurityConfiguration(
private val authenticationProvider: CustomAuthenticationProvider
) {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain = http
.formLogin { it.loginPage("/login")
.successHandler { _, response, _ -> response.sendRedirect("/hello") }
.failureHandler { _, response, _ -> response.sendRedirect("/error") }
}
.authorizeHttpRequests { it.anyRequest().authenticated() }
.authenticationProvider(authenticationProvider)
.build()
}
Last updated