Spring Boot Kotlin - Security
ជំពូកនេះនឹងពន្យល់អ្នកពី Spring Security's Dependency ជាអ្វីហើយអាចធ្វើអ្វីបានខ្លះនៅលើកម្មវិធី Spring Boot។
Spring Boot Security
Spring Security ជា servlet filters
ដែលជួយអ្នកបន្ថែមនូវ ការផ្ទៀងផ្ទាត់(authentication)
និង ការអនុញ្ញាតកម្មវិធីគេហទំព័ររបស់អ្នក(authorization)
។
វាក៏អាចរួមបញ្ចូលយ៉ាងល្អជាមួយ frameworks
ដទៃដូចជា Spring Web MVC (ឬ Spring Boot)
ក៏ដូចជា OAuth2
ឬ SAML
ផងដែរ។ ហើយវាបង្កើត ទំព័រចូល/ចេញ (login/logout pages)
ដោយស្វ័យប្រវត្តិ និងការពារប្រឆាំងនឹងការហែកចូលគេហទំព័រទូទៅដូចជា CSRF
ជាដើម។
បើសិនជាអ្នកចង់ប្រើ dependency
មួយនេះ អ្នកត្រូវបន្ថែមនូវ dependency
មួយនេះនៅក្នុងឯកសារ build.gradle.kts
:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
testImplementation("org.springframework.security:spring-security-test")
}
បន្ទាប់ពីអ្នកបន្ថែមនូវ spring-boot-starter-security's dependency
រួចរាល់គឺអ្នកត្រូវបន្ថែមនូវ dependency
មួយទៀតដើម្បីអាចប្រើ jwt
:
dependencies {
implementation("io.jsonwebtoken:jjwt:0.9.1")
}
សម្រាប់ក្នុងមេរៀននេះគឺនឹងនាំអ្នកទៅប្រើប្រាស់ Spring Boot Security ជាមួយនឹង JWT។
package com.springboot.security.config
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SpringSecurityConfig {
@Bean
@Throws(Exception::class)
fun filterChain(http: HttpSecurity): SecurityFilterChain? {
// basic http configurer to use with token service
http
.httpBasic().disable()
.cors().and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// authorization request filters
http
.authorizeRequests {
it.anyRequest().permitAll()
}
return http.build()
}
@Bean
fun getPasswordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder(10)
}
}
package com.springboot.security.modules.security
import com.springboot.security.model.entity.UserEntity
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
class UserAuthDetails @Autowired constructor(
private val user: UserEntity,
) : UserDetails {
fun getUser() = user
override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
return this.user.roles?.map { role -> SimpleGrantedAuthority("ROLE_${role.name}") }?.toMutableList()!!
}
override fun getPassword(): String = this.user.password ?: ""
override fun getUsername(): String = this.user.username ?: ""
override fun isAccountNonExpired(): Boolean = true
override fun isAccountNonLocked(): Boolean = true
override fun isCredentialsNonExpired(): Boolean = true
override fun isEnabled(): Boolean = this.user.enabledUser ?: false
}
package com.springboot.security.modules.security.util
import com.springboot.security.modules.security.exception.*
import io.jsonwebtoken.*
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import java.util.*
import javax.servlet.http.HttpServletRequest
object JwtUtils {
private const val AUTHORIZATION_HEADER: String = "Authorization"
private const val AUTHORIZATION_TYPE: String = "Bearer "
private var secretKey: String = "blog123"
private var tokenExpiredInMillis: Long = 604800000 // 1 week
private var passwordStrength: Int = 10
private var passwordEncoder: PasswordEncoder? = null
private var userDetailsService: UserDetailsService? = null
// setters
fun setUserDetailsService(_userDetailsService: UserDetailsService) = apply {
this.userDetailsService = _userDetailsService
}
fun setPasswordStrength(_passwordStrength: Int) = apply {
this.passwordStrength = _passwordStrength
}
fun setSecretKey(_secretKey: String) = apply {
this.secretKey = _secretKey
}
fun setTokenExpiredInMillis(_tokenExpiredInMillis: Long) = apply {
this.tokenExpiredInMillis = _tokenExpiredInMillis
}
// getters
fun getUserDetailsService(): UserDetailsService {
if (this.userDetailsService == null)
throw JwtNotImplementException()
return this.userDetailsService!!
}
fun getPasswordEncoder(): PasswordEncoder {
if (this.passwordEncoder == null)
this.passwordEncoder = BCryptPasswordEncoder(this.passwordStrength)
return this.passwordEncoder!!
}
private fun getSecretKey(): String = Base64.getEncoder().encodeToString(secretKey.toByteArray())
private fun getTokenExpiredInMillis(): Long = this.tokenExpiredInMillis
fun extractToken(request: HttpServletRequest): String? {
val headerToken = request.getHeader(AUTHORIZATION_HEADER)?.toString() ?: ""
val isBearerToken = headerToken.trim().lowercase().startsWith(AUTHORIZATION_TYPE.lowercase())
if (!isBearerToken)
return null
val token = headerToken.substring(AUTHORIZATION_TYPE.length)
val isValidJwtThreePart = token.split(".").size == 3
if (!isValidJwtThreePart)
return null
return token
}
private fun validateTokenExpired(claims: Claims): Boolean {
if (claims.expiration.after(Date()))
return true
return false
}
fun resolveUserFromToken(token: String?): UsernamePasswordAuthenticationToken? {
val claims = this.decryptToken(token) ?: return null
val isTokenExpired = this.validateTokenExpired(claims)
if (!isTokenExpired)
return null
val username = claims.subject
val user = this.getUserDetailsService().loadUserByUsername(username) ?: return null
if (!user.isEnabled)
throw UserNotEnabledException("User is disabled!")
return resolveAuthFromUser(user)
}
private fun resolveAuthFromUser(user: UserDetails): UsernamePasswordAuthenticationToken {
val auth = UsernamePasswordAuthenticationToken(user.username, user.password, user.authorities)
auth.details = user
return auth
}
fun encryptToken(user: UserDetails): String {
val currentDateMillisecond = Date().time + this.getTokenExpiredInMillis()
val expireDate = Date(currentDateMillisecond)
return Jwts.builder()
.setSubject(user.username)
.setIssuedAt(Date())
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS256, this.getSecretKey())
.compact()
}
private fun decryptToken(token: String?): Claims? {
token ?: return null
val secretKey = this.getSecretKey()
return try {
Jwts.parser()
.setSigningKey(secretKey)
.parse(token)
.body as? Claims
} catch (ex: SignatureException) {
throw SignatureTokenException("Invalid JWT Signature")
} catch (ex: MalformedJwtException) {
throw MalformedJwtTokenException("Invalid JWT token")
} catch (ex: ExpiredJwtException) {
throw ExpiredJwtTokenException("Expired JWT token")
} catch (ex: UnsupportedJwtException) {
throw UnsupportedJwtTokenException("Unsupported JWT exception")
} catch (ex: IllegalArgumentException) {
throw EmptyJwtClaimsException("Jwt claims string is empty")
}
}
}
package com.springboot.security.modules.security.exception
import com.springboot.security.exception.BaseException
class EmptyJwtClaimsException(
message: String? = "",
) : BaseException(message)
package com.springboot.security.modules.security.exception
import com.springboot.security.exception.BaseException
class ExpiredJwtTokenException(
message: String? = "",
) : BaseException(message)
package com.springboot.security.modules.security.exception
import com.springboot.security.exception.BaseException
class JwtNotImplementException(
message: String? = "User details service not implement yet!"
) : BaseException(message)
package com.springboot.security.modules.security.exception
import com.springboot.security.exception.BaseException
class MalformedJwtTokenException(
message: String? = "",
) : BaseException(message)
package com.springboot.security.modules.security.exception
import com.springboot.security.exception.BaseException
class SignatureTokenException(
message: String? = "",
) : BaseException(message)
package com.springboot.security.modules.security.exception
import com.springboot.security.exception.BaseException
class UnsupportedJwtTokenException(
message: String? = "",
) : BaseException(message)
package com.springboot.security.modules.security.exception
import com.springboot.security.exception.BaseException
class UserNotEnabledException(
message: String? = "",
) : BaseException(message)