រំលងទៅមាតិកាសំខាន់

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) ក៏ដូចជា OAuth2SAML ផងដែរ។ ហើយវាបង្កើត ទំព័រចូល/ចេញ (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)