...
This commit is contained in:
parent
576932da3d
commit
cd1072430e
@ -1,6 +1,7 @@
|
|||||||
package kr.lunaticbum.back.lun.configs
|
package kr.lunaticbum.back.lun.configs
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||||
@ -14,12 +15,16 @@ class AppConfig : WebMvcConfigurer {
|
|||||||
@Value("\${resource.location}")
|
@Value("\${resource.location}")
|
||||||
private val resourceLocation: String? = null
|
private val resourceLocation: String? = null
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun authInterceptor(): BumsInterceptor {
|
||||||
|
return BumsInterceptor()
|
||||||
|
}
|
||||||
override fun addResourceHandlers(registry: org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry) {
|
override fun addResourceHandlers(registry: org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry) {
|
||||||
registry.addResourceHandler(resourceHandler).addResourceLocations(resourceLocation)
|
registry.addResourceHandler(resourceHandler).addResourceLocations(resourceLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addInterceptors(registry: InterceptorRegistry) {
|
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||||
registry.addInterceptor(BumsInterceptor())
|
registry.addInterceptor(authInterceptor())
|
||||||
super.addInterceptors(registry)
|
super.addInterceptors(registry)
|
||||||
}
|
}
|
||||||
// @Bean
|
// @Bean
|
||||||
|
|||||||
@ -4,12 +4,24 @@ import com.google.gson.Gson
|
|||||||
import jakarta.servlet.http.Cookie
|
import jakarta.servlet.http.Cookie
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import kr.lunaticbum.back.lun.service.JwtService
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.lang.Nullable
|
import org.springframework.lang.Nullable
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.web.servlet.HandlerInterceptor
|
import org.springframework.web.servlet.HandlerInterceptor
|
||||||
import org.springframework.web.servlet.ModelAndView
|
import org.springframework.web.servlet.ModelAndView
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class BumsInterceptor : HandlerInterceptor {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
lateinit var jwtService : JwtService
|
||||||
|
@Autowired
|
||||||
|
lateinit var globalEvv : GlobalEnvironment
|
||||||
|
|
||||||
|
val WRITE_PERMISSION_KEY = "PERMISSION"
|
||||||
|
|
||||||
class BumsInterceptor : HandlerInterceptor{
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
|
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
|
||||||
println("===============================================")
|
println("===============================================")
|
||||||
@ -17,7 +29,8 @@ class BumsInterceptor : HandlerInterceptor{
|
|||||||
println("Request URL ===> " + request.requestURL)
|
println("Request URL ===> " + request.requestURL)
|
||||||
return super.preHandle(request, response, handler)
|
return super.preHandle(request, response, handler)
|
||||||
}
|
}
|
||||||
val WRITE_PERMISSION_KEY = "PERMISSION"
|
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun postHandle(
|
override fun postHandle(
|
||||||
request: HttpServletRequest,
|
request: HttpServletRequest,
|
||||||
@ -25,12 +38,37 @@ class BumsInterceptor : HandlerInterceptor{
|
|||||||
handler: Any,
|
handler: Any,
|
||||||
@Nullable modelAndView: ModelAndView?
|
@Nullable modelAndView: ModelAndView?
|
||||||
) {
|
) {
|
||||||
if (request.requestURI.contains("logout") == false && !request.cookies.isNullOrEmpty() && request.cookies.filter { it.name.equals("S33-DATA") && it.value.length > 0 }.size > 0) {
|
if (request.requestURI.contains("logout") == false && !request.cookies.isNullOrEmpty() && request.cookies.filter { it.name.equals("access") && it.value.length > 0 }.size > 0) {
|
||||||
response.addCookie(Cookie("S33-DATA",request.cookies.filter { it.name.equals("S33-DATA") && it.value.length > 0 }.get(0).value))
|
var correctUserCheck = -1;
|
||||||
modelAndView?.modelMap?.put(WRITE_PERMISSION_KEY,"OK")
|
var access : Cookie?= null
|
||||||
|
var refresh : Cookie?= null
|
||||||
|
request.cookies.forEach {
|
||||||
|
if (it.name.equals("access", true) && jwtService.validateAccessToken(it.value)){
|
||||||
|
access = it
|
||||||
|
correctUserCheck += 1
|
||||||
|
println("Response access correctUserCheck ===> ${correctUserCheck}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.cookies.forEach {
|
||||||
|
if (it.name.equals("refresh", true) && jwtService.validateRefreshToken(access?.value,it.value)){
|
||||||
|
refresh = it
|
||||||
|
correctUserCheck += 1
|
||||||
|
println("Response refresh correctUserCheck ===> ${correctUserCheck}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (correctUserCheck > 0) {
|
||||||
|
println("Response correctUserCheck ===> ${correctUserCheck}")
|
||||||
|
response.addCookie(cookieUpdate(refresh))
|
||||||
|
response.addCookie(cookieUpdate(access))
|
||||||
|
modelAndView?.modelMap?.put(WRITE_PERMISSION_KEY,"OK")
|
||||||
|
} else {
|
||||||
|
println("Response correctUserCheck ===> ${correctUserCheck}")
|
||||||
|
response.addCookie(Cookie("access","").apply { maxAge = -1 })
|
||||||
|
response.addCookie(Cookie("refresh","").apply { maxAge = -1 })
|
||||||
|
modelAndView?.modelMap?.put(WRITE_PERMISSION_KEY,"NO")
|
||||||
|
}
|
||||||
println("Response modelMap ===> ${Gson().toJson(modelAndView?.modelMap)}")
|
println("Response modelMap ===> ${Gson().toJson(modelAndView?.modelMap)}")
|
||||||
} else if (request.requestURI.contains("logout")) {
|
} else if (request.requestURI.contains("logout")) {
|
||||||
response.addCookie(Cookie("S33-DATA",null))
|
|
||||||
modelAndView?.modelMap?.put(WRITE_PERMISSION_KEY,"NO")
|
modelAndView?.modelMap?.put(WRITE_PERMISSION_KEY,"NO")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,5 +77,10 @@ class BumsInterceptor : HandlerInterceptor{
|
|||||||
|
|
||||||
super.postHandle(request, response, handler, modelAndView)
|
super.postHandle(request, response, handler, modelAndView)
|
||||||
}
|
}
|
||||||
|
fun cookieUpdate(cookie: Cookie?) : Cookie? {
|
||||||
|
cookie?.maxAge = (globalEvv.ACCESS_EXPIRATION / 1000).toInt()
|
||||||
|
cookie?.domain = "lunaticbum.kr"
|
||||||
|
cookie?.secure = true
|
||||||
|
return cookie
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -29,7 +29,14 @@ class GlobalEnvironment : EnvironmentAware {
|
|||||||
@Value("\${weather.api.key}")
|
@Value("\${weather.api.key}")
|
||||||
var weatherApiKey: String? = ""
|
var weatherApiKey: String? = ""
|
||||||
|
|
||||||
|
// @Value("jwt.access-secret")
|
||||||
|
var ACCESS_SECRET_KEY: String = "l00u00n00a00t00i00c00b00u00m00a00c00sk"
|
||||||
|
// @Value("jwt.refresh-secret")
|
||||||
|
var REFRESH_SECRET_KEY: String = "l00u00n00a00t00i00c00b00u00m00r00f00sk"
|
||||||
|
// @Value("jwt.access-expiration")
|
||||||
|
var ACCESS_EXPIRATION: Long = 60 * 5 * 1000L
|
||||||
|
// @Value("jwt.refresh-expiration")
|
||||||
|
var REFRESH_EXPIRATION: Long = 60 * 5 * 1000L
|
||||||
|
|
||||||
override fun setEnvironment(environment: Environment) {
|
override fun setEnvironment(environment: Environment) {
|
||||||
println ("telegramBotKey $telegramBotKey")
|
println ("telegramBotKey $telegramBotKey")
|
||||||
|
|||||||
@ -0,0 +1,69 @@
|
|||||||
|
package kr.lunaticbum.back.lun.configs
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Jwts
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import kr.lunaticbum.back.lun.model.User
|
||||||
|
import lombok.Getter
|
||||||
|
import lombok.RequiredArgsConstructor
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import java.security.Key
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class JwtGenerator {
|
||||||
|
fun generateAccessToken(ACCESS_SECRET: Key?, ACCESS_EXPIRATION: Long, user: User): String {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.setHeader(createHeader())
|
||||||
|
.setClaims(createClaims(user))
|
||||||
|
.setSubject(user.userId)
|
||||||
|
.setExpiration(Date(now + ACCESS_EXPIRATION))
|
||||||
|
.signWith(ACCESS_SECRET, SignatureAlgorithm.HS256)
|
||||||
|
.compact()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateRefreshToken(REFRESH_SECRET: Key?, REFRESH_EXPIRATION: Long, user: User): String {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.setHeader(createHeader())
|
||||||
|
.setSubject(user.getIdentifier())
|
||||||
|
.setExpiration(Date(now + REFRESH_EXPIRATION))
|
||||||
|
.signWith(REFRESH_SECRET, SignatureAlgorithm.HS256)
|
||||||
|
.compact()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createHeader(): Map<String, Any> {
|
||||||
|
val header: MutableMap<String, Any> = HashMap()
|
||||||
|
header["typ"] = "JWT"
|
||||||
|
header["alg"] = "HS256"
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createClaims(user: User): Map<String, Any?> {
|
||||||
|
val claims: MutableMap<String, Any?> = HashMap()
|
||||||
|
claims["Identifier"] = user.getIdentifier()
|
||||||
|
claims["Role"] = user.getRole()
|
||||||
|
return claims
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
enum class TokenStatus {
|
||||||
|
AUTHENTICATED,
|
||||||
|
EXPIRED,
|
||||||
|
INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
enum class JwtRule(val value: String) {
|
||||||
|
JWT_ISSUE_HEADER("Set-Cookie"),
|
||||||
|
JWT_RESOLVE_HEADER("Cookie"),
|
||||||
|
ACCESS_PREFIX("access"),
|
||||||
|
REFRESH_PREFIX("refresh");
|
||||||
|
}
|
||||||
@ -136,10 +136,16 @@ class BlogController() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("modify")
|
@GetMapping("modify")
|
||||||
fun modify(@RequestParam("token") token : String?) : ResultMV{
|
fun modify(httpServletRequest: HttpServletRequest,@RequestParam("token") token : String?) : ResultMV{
|
||||||
logService.log("incoming modify")
|
logService.log("incoming modify")
|
||||||
val vm = ResultMV("content/blog/modify")
|
val vm = ResultMV("content/blog/modify")
|
||||||
if (TEMPTOKEN.equals(token)) {
|
var s33Key : String? = null
|
||||||
|
if (!httpServletRequest.cookies.isNullOrEmpty()) {
|
||||||
|
httpServletRequest.cookies.forEach { if (it.name.equals("S33-DATA")){
|
||||||
|
s33Key = it.value
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
if (TEMPTOKEN.equals(token)|| s33Key?.length ?: 0 > 5) {
|
||||||
postManageg.find20()?.apply {
|
postManageg.find20()?.apply {
|
||||||
forEach {
|
forEach {
|
||||||
it.title = URLDecoder.decode(it.title)
|
it.title = URLDecoder.decode(it.title)
|
||||||
|
|||||||
@ -2,15 +2,20 @@ package kr.lunaticbum.back.lun.controllers
|
|||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
import kr.lunaticbum.back.lun.configs.GlobalEnvironment
|
import kr.lunaticbum.back.lun.configs.GlobalEnvironment
|
||||||
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.ApiKeyWordKey
|
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.ApiKeyWordKey
|
||||||
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.EncType11
|
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.EncType11
|
||||||
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.EncTypeKey
|
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.EncTypeKey
|
||||||
|
import kr.lunaticbum.back.lun.configs.JwtRule
|
||||||
import kr.lunaticbum.back.lun.model.*
|
import kr.lunaticbum.back.lun.model.*
|
||||||
|
import kr.lunaticbum.back.lun.service.JwtService
|
||||||
|
import kr.lunaticbum.back.lun.utils.JwtUtil
|
||||||
import kr.lunaticbum.back.lun.utils.LogService
|
import kr.lunaticbum.back.lun.utils.LogService
|
||||||
import kr.lunaticbum.back.lun.utils.extractModelData
|
import kr.lunaticbum.back.lun.utils.extractModelData
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
|
import org.springframework.http.ResponseCookie
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.security.core.userdetails.UserDetails
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
@ -33,6 +38,8 @@ class UserController {
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
lateinit var userManager: UserManager
|
lateinit var userManager: UserManager
|
||||||
|
@Autowired
|
||||||
|
lateinit var jwtService : JwtService
|
||||||
|
|
||||||
@GetMapping("join")
|
@GetMapping("join")
|
||||||
fun hello(httpServletRequest: HttpServletRequest): ResultMV {
|
fun hello(httpServletRequest: HttpServletRequest): ResultMV {
|
||||||
@ -57,12 +64,6 @@ class UserController {
|
|||||||
fun userLogin(httpServletRequest: HttpServletRequest): ResultMV {
|
fun userLogin(httpServletRequest: HttpServletRequest): ResultMV {
|
||||||
logService.log("onJoin")
|
logService.log("onJoin")
|
||||||
val vm = ResultMV("content/user/login")
|
val vm = ResultMV("content/user/login")
|
||||||
// when(System.currentTimeMillis() % 5L) {
|
|
||||||
// 0L -> vm.modelMap.put(EncTypeKey,"T4")
|
|
||||||
// 1L -> vm.modelMap.put(EncTypeKey,"T3")
|
|
||||||
// 2L -> vm.modelMap.put(EncTypeKey,"T2")
|
|
||||||
// else -> vm.modelMap.put(EncTypeKey,"T0")
|
|
||||||
// }
|
|
||||||
vm.modelMap.put(EncTypeKey,EncType11)
|
vm.modelMap.put(EncTypeKey,EncType11)
|
||||||
vm.modelMap.put(ApiKeyWordKey,"LOGIN")
|
vm.modelMap.put(ApiKeyWordKey,"LOGIN")
|
||||||
return vm
|
return vm
|
||||||
@ -71,23 +72,25 @@ class UserController {
|
|||||||
|
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@PostMapping("login.ajax")
|
@PostMapping("login.ajax")
|
||||||
fun login(httpServletRequest: HttpServletRequest, @RequestBody jsonString: String) : ResponseEntity<ResponceResult> {
|
fun login(httpServletRequest: HttpServletRequest, @RequestBody jsonString: String) : ResponseEntity<LoginResult> {
|
||||||
logService.log(httpServletRequest.requestURI)
|
logService.log(httpServletRequest.requestURI)
|
||||||
logService.log(jsonString)
|
logService.log(jsonString)
|
||||||
var lResultCode = 0
|
var lResultCode = 0
|
||||||
var lResultMsg = "Suscces"
|
var lResultMsg = "Suscces"
|
||||||
var u : UserDetails? = null
|
var u : UserDetails? = null
|
||||||
|
var user : User? = null
|
||||||
|
var tokenData : TokenData? = null
|
||||||
jsonString.extractModelData { exception, originDataString ->
|
jsonString.extractModelData { exception, originDataString ->
|
||||||
if (exception == null) {
|
if (exception == null) {
|
||||||
logService.log(originDataString)
|
logService.log(originDataString)
|
||||||
val target = Gson().fromJson(originDataString, User::class.java) ?: User()
|
val target = Gson().fromJson(originDataString, User::class.java) ?: User()
|
||||||
var user = userManager.findById(target.user_id!!)?.block()
|
user = userManager.findById(target.user_id!!)?.block()
|
||||||
if (user == null && ((target.user_id?.length ?: 0) > 3 == true)) {
|
if (user == null && ((target.user_id?.length ?: 0) > 3 == true)) {
|
||||||
user = userManager.findByEmail(target.user_id!!)?.block()
|
user = userManager.findByEmail(target.user_id!!)?.block()
|
||||||
}
|
}
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
if(userManager.isCorrectUser(user,target.user_pw!!)){
|
if(userManager.isCorrectUser(user!!,target.user_pw!!)){
|
||||||
|
tokenData = jwtService.generate(user!!)
|
||||||
} else {
|
} else {
|
||||||
lResultMsg = "is wrong infomation id or passord"
|
lResultMsg = "is wrong infomation id or passord"
|
||||||
lResultCode = 7100
|
lResultCode = 7100
|
||||||
@ -103,14 +106,34 @@ class UserController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val responce = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).headers {
|
val responce = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).headers {
|
||||||
it.put("S33-DATA", listOf(UUID.randomUUID().toString()))
|
|
||||||
}.body(ResponceResult().apply {
|
|
||||||
|
|
||||||
|
|
||||||
|
}.body(LoginResult().apply {
|
||||||
this.resultCode = lResultCode
|
this.resultCode = lResultCode
|
||||||
this.resultMsg = lResultMsg
|
this.resultMsg = lResultMsg
|
||||||
})
|
this.token = setTokenToCookie(JwtRule.ACCESS_PREFIX.value, tokenData?.tokenKey ?: "", globalEvv.ACCESS_EXPIRATION / 1000).toString().replace("access=","")
|
||||||
|
this.refresh = setTokenToCookie(JwtRule.REFRESH_PREFIX.value, tokenData?.refreshToken ?: "", globalEvv.REFRESH_EXPIRATION / 1000).toString().replace("refresh=","")
|
||||||
|
}).apply {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return responce
|
return responce
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun setTokenToCookie(tokenPrefix: String, token: String, maxAgeSeconds: Long): ResponseCookie {
|
||||||
|
return ResponseCookie.from(tokenPrefix, token)
|
||||||
|
.path("/")
|
||||||
|
.maxAge(maxAgeSeconds)
|
||||||
|
.httpOnly(true)
|
||||||
|
.sameSite("None")
|
||||||
|
.secure(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@PostMapping("logout.ajax")
|
@PostMapping("logout.ajax")
|
||||||
fun logout(httpServletRequest: HttpServletRequest) : ResponseEntity<ResponceResult> {
|
fun logout(httpServletRequest: HttpServletRequest) : ResponseEntity<ResponceResult> {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ open class ResponceResult : BaseResult() {
|
|||||||
@Getter
|
@Getter
|
||||||
open class LoginResult : ResponceResult() {
|
open class LoginResult : ResponceResult() {
|
||||||
var token: String? = null
|
var token: String? = null
|
||||||
|
var refresh: String? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
|||||||
@ -6,17 +6,37 @@ import lombok.NoArgsConstructor
|
|||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.Id
|
||||||
import org.springframework.data.mongodb.core.index.Indexed
|
import org.springframework.data.mongodb.core.index.Indexed
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
|
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import java.time.LocalDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Document(collection = "TokenData")
|
@Document(collection = "TokenData")
|
||||||
class TokenData {
|
class TokenData {
|
||||||
@Indexed(expireAfterSeconds = 60 * 5)
|
@Indexed(name = "ttl",expireAfterSeconds = 0)
|
||||||
var expireAt: Date? = null
|
var expireAt: LocalDateTime? = null
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
var tokenKey : String? = null
|
var tokenKey : String? = null
|
||||||
|
|
||||||
var refreshToken : String? = null
|
var refreshToken : String? = null
|
||||||
|
|
||||||
|
constructor(tokenKey: String?, refreshToken: String?) {
|
||||||
|
this.expireAt = LocalDateTime.now().plusSeconds(300)
|
||||||
|
this.tokenKey = tokenKey
|
||||||
|
this.refreshToken = refreshToken
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface TokenDataRepository : ReactiveMongoRepository<TokenData, String> {
|
||||||
|
fun findBytokenKey(tokenKey: String): Mono<TokenData>
|
||||||
|
fun deleteBytokenKey(tokenKey: String)
|
||||||
}
|
}
|
||||||
@ -84,6 +84,16 @@ class User {
|
|||||||
fun checkPassword(plainPassword: String?, passwordEncoder: PasswordEncoder): Boolean {
|
fun checkPassword(plainPassword: String?, passwordEncoder: PasswordEncoder): Boolean {
|
||||||
return passwordEncoder.matches(plainPassword, this.user_pw)
|
return passwordEncoder.matches(plainPassword, this.user_pw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getIdentifier(): String? {
|
||||||
|
return userId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRole(): UserRole {
|
||||||
|
if ("Y".equals(isAdmin)) return UserRole.ADMIN
|
||||||
|
if ("Y".equals(isAccept)) return UserRole.WRITE
|
||||||
|
return UserRole.READ
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ -107,6 +117,10 @@ interface UserRepository : ReactiveMongoRepository<User, String> {
|
|||||||
|
|
||||||
fun save(user: User): Mono<User>
|
fun save(user: User): Mono<User>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class UserRole {
|
||||||
|
NOT_REGISTERED,READ,WRITE,ADMIN
|
||||||
|
}
|
||||||
interface UserService {
|
interface UserService {
|
||||||
fun findById(id: String): Mono<User>?
|
fun findById(id: String): Mono<User>?
|
||||||
fun findByEmail(id: String): Mono<User>?
|
fun findByEmail(id: String): Mono<User>?
|
||||||
|
|||||||
132
src/main/kotlin/kr/lunaticbum/back/lun/service/JwtService.kt
Normal file
132
src/main/kotlin/kr/lunaticbum/back/lun/service/JwtService.kt
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package kr.lunaticbum.back.lun.service
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Jwts
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import kr.lunaticbum.back.lun.configs.GlobalEnvironment
|
||||||
|
import kr.lunaticbum.back.lun.configs.JwtGenerator
|
||||||
|
import kr.lunaticbum.back.lun.configs.JwtRule
|
||||||
|
import kr.lunaticbum.back.lun.configs.TokenStatus
|
||||||
|
import kr.lunaticbum.back.lun.model.*
|
||||||
|
import kr.lunaticbum.back.lun.utils.BusinessException
|
||||||
|
import kr.lunaticbum.back.lun.utils.ErrorCode
|
||||||
|
import kr.lunaticbum.back.lun.utils.JwtUtil
|
||||||
|
import lombok.extern.slf4j.Slf4j
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.http.HttpHeaders
|
||||||
|
import org.springframework.http.ResponseCookie
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
|
import org.springframework.security.core.Authentication
|
||||||
|
import org.springframework.security.core.token.Token
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.security.Key
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.*
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
@Slf4j
|
||||||
|
class JwtService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
lateinit var globalEvv : GlobalEnvironment
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var jwtGenerator: JwtGenerator
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var jwtUtil: JwtUtil
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var customUserDetailsService: UserManager
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var tokenRepository : TokenDataRepository
|
||||||
|
|
||||||
|
// private val ACCESS_SECRET_KEY: Key = jwtUtil.getSigningKey(globalEvv.ACCESS_SECRET_KEY)
|
||||||
|
// private val REFRESH_SECRET_KEY: Key = jwtUtil.getSigningKey(globalEvv.REFRESH_SECRET_KEY)
|
||||||
|
|
||||||
|
fun validateUser(requestUser: User) {
|
||||||
|
if (requestUser.getRole() === UserRole.NOT_REGISTERED) {
|
||||||
|
throw BusinessException(ErrorCode.NOT_AUTHENTICATED_USER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun generate(requestUser: User): TokenData? {
|
||||||
|
var accessToken = jwtGenerator.generateAccessToken(jwtUtil.getSigningKey(globalEvv.ACCESS_SECRET_KEY), globalEvv.ACCESS_EXPIRATION, requestUser)
|
||||||
|
var refreshToken = jwtGenerator.generateRefreshToken(jwtUtil.getSigningKey(globalEvv.REFRESH_SECRET_KEY), globalEvv.REFRESH_EXPIRATION, requestUser)
|
||||||
|
var token = TokenData(accessToken, refreshToken)
|
||||||
|
return tokenRepository.save(token).block()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun setTokenToCookie(tokenPrefix: String, token: String, maxAgeSeconds: Long): ResponseCookie {
|
||||||
|
return ResponseCookie.from(tokenPrefix, token)
|
||||||
|
.path("/")
|
||||||
|
.maxAge(maxAgeSeconds)
|
||||||
|
.httpOnly(true)
|
||||||
|
.sameSite("None")
|
||||||
|
.secure(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateAccessToken(token: String?): Boolean {
|
||||||
|
return jwtUtil.getTokenStatus(token, jwtUtil.getSigningKey(globalEvv.ACCESS_SECRET_KEY)) == TokenStatus.AUTHENTICATED
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateRefreshToken(token: String?, refreshToken: String?): Boolean {
|
||||||
|
val isRefreshValid = jwtUtil.getTokenStatus(refreshToken, jwtUtil.getSigningKey(globalEvv.REFRESH_SECRET_KEY)) == TokenStatus.AUTHENTICATED
|
||||||
|
val storedToken: TokenData? = tokenRepository.findBytokenKey(token ?: "").block(Duration.ofSeconds(10))
|
||||||
|
val isTokenMatched: Boolean = storedToken?.refreshToken.equals(refreshToken)
|
||||||
|
return isRefreshValid && isTokenMatched
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveTokenFromCookie(request: HttpServletRequest, tokenPrefix: JwtRule?): String {
|
||||||
|
val cookies = request.cookies ?: throw BusinessException(ErrorCode.JWT_TOKEN_NOT_FOUND)
|
||||||
|
return jwtUtil.resolveTokenFromCookie(cookies, tokenPrefix!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAuthentication(token: String): Authentication {
|
||||||
|
val principal: UserDetails = customUserDetailsService.loadUserByUsername(getUserPk(token, jwtUtil.getSigningKey(globalEvv.ACCESS_SECRET_KEY)))
|
||||||
|
return UsernamePasswordAuthenticationToken(principal, "", principal.authorities)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUserPk(token: String, secretKey: Key): String {
|
||||||
|
return Jwts.parserBuilder()
|
||||||
|
.setSigningKey(secretKey)
|
||||||
|
.build()
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody()
|
||||||
|
.getSubject()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIdentifierFromRefresh(refreshToken: String?): String {
|
||||||
|
try {
|
||||||
|
return Jwts.parserBuilder()
|
||||||
|
.setSigningKey(jwtUtil.getSigningKey(globalEvv.REFRESH_SECRET_KEY))
|
||||||
|
.build()
|
||||||
|
.parseClaimsJws(refreshToken)
|
||||||
|
.getBody()
|
||||||
|
.getSubject()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw BusinessException(ErrorCode.INVALID_JWT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logout(token: String, response: HttpServletResponse) {
|
||||||
|
tokenRepository.deleteBytokenKey(token)
|
||||||
|
|
||||||
|
val accessCookie = jwtUtil.resetToken(JwtRule.ACCESS_PREFIX)
|
||||||
|
val refreshCookie = jwtUtil.resetToken(JwtRule.REFRESH_PREFIX)
|
||||||
|
|
||||||
|
response.addCookie(accessCookie)
|
||||||
|
response.addCookie(refreshCookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/main/kotlin/kr/lunaticbum/back/lun/utils/JwtUtil.kt
Normal file
79
src/main/kotlin/kr/lunaticbum/back/lun/utils/JwtUtil.kt
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package kr.lunaticbum.back.lun.utils
|
||||||
|
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException
|
||||||
|
import io.jsonwebtoken.JwtException
|
||||||
|
import io.jsonwebtoken.Jwts
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
|
import jakarta.servlet.http.Cookie
|
||||||
|
import kr.lunaticbum.back.lun.configs.JwtRule
|
||||||
|
import kr.lunaticbum.back.lun.configs.TokenStatus
|
||||||
|
import lombok.RequiredArgsConstructor
|
||||||
|
import lombok.extern.slf4j.Slf4j
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.security.Key
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
class JwtUtil {
|
||||||
|
|
||||||
|
|
||||||
|
fun getTokenStatus(token: String?, secretKey: Key?): TokenStatus {
|
||||||
|
try {
|
||||||
|
var cls = Jwts.parserBuilder()
|
||||||
|
.setSigningKey(secretKey)
|
||||||
|
.build()
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
cls.body.keys.forEach {
|
||||||
|
println("${it} >>> ${cls.body.get(it).toString()}")
|
||||||
|
}
|
||||||
|
return TokenStatus.AUTHENTICATED
|
||||||
|
} catch (e: ExpiredJwtException) {
|
||||||
|
// log.error(INVALID_EXPIRED_JWT.getMessage())
|
||||||
|
return TokenStatus.EXPIRED
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
// log.error(INVALID_EXPIRED_JWT.getMessage())
|
||||||
|
return TokenStatus.EXPIRED
|
||||||
|
} catch (e: JwtException) {
|
||||||
|
throw BusinessException(ErrorCode.INVALID_JWT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveTokenFromCookie(cookies: Array<Cookie>?, tokenPrefix: JwtRule): String {
|
||||||
|
return Arrays.stream(cookies)
|
||||||
|
.filter { cookie -> cookie.getName().equals(tokenPrefix.value, ignoreCase = true) }
|
||||||
|
.findFirst()
|
||||||
|
.map { it.value }
|
||||||
|
.orElse("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSigningKey(secretKey: String): Key {
|
||||||
|
val encodedKey = encodeToBase64(secretKey)
|
||||||
|
return Keys.hmacShaKeyFor(encodedKey.toByteArray(StandardCharsets.UTF_8))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun encodeToBase64(secretKey: String): String {
|
||||||
|
return Base64.getEncoder().encodeToString(secretKey.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetToken(tokenPrefix: JwtRule): Cookie {
|
||||||
|
val cookie: Cookie = Cookie(tokenPrefix.value, null)
|
||||||
|
cookie.setMaxAge(0)
|
||||||
|
cookie.setPath("/")
|
||||||
|
return cookie
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class BusinessException(error : ErrorCode) : Exception(error.name)
|
||||||
|
|
||||||
|
enum class ErrorCode {
|
||||||
|
JWT_TOKEN_NOT_FOUND,
|
||||||
|
NOT_AUTHENTICATED_USER,
|
||||||
|
INVALID_EXPIRED_JWT,
|
||||||
|
INVALID_JWT
|
||||||
|
}
|
||||||
@ -163,4 +163,4 @@ a.btn_layerClose:hover {
|
|||||||
padding: 1px;
|
padding: 1px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #00000044;
|
background: #00000044;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,12 @@ body > *{
|
|||||||
/*}*/
|
/*}*/
|
||||||
|
|
||||||
|
|
||||||
|
.center_menu {
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
top: 0;
|
top: 0;
|
||||||
/*background: var(--DEFAULT_LAYER_BACK);*/
|
/*background: var(--DEFAULT_LAYER_BACK);*/
|
||||||
@ -51,8 +57,10 @@ header {
|
|||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
height: 5vh;
|
height: 5vh;
|
||||||
min-height: 5vh;
|
min-height: 5vh;
|
||||||
|
grid-auto-flow: column;
|
||||||
display: flex;;
|
display: flex;;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
justify-content: space-around;
|
||||||
}
|
}
|
||||||
.user_info {
|
.user_info {
|
||||||
|
|
||||||
@ -95,7 +103,7 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#top {
|
#top {
|
||||||
float: left;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|||||||
@ -81,12 +81,6 @@ function postLogin(target,type, data, key,callBackResult) {
|
|||||||
if (httpRequest.readyState === XMLHttpRequest.DONE) {
|
if (httpRequest.readyState === XMLHttpRequest.DONE) {
|
||||||
if (httpRequest.status === 200) {
|
if (httpRequest.status === 200) {
|
||||||
try {
|
try {
|
||||||
var uuid= httpRequest.getResponseHeader("S33-DATA")
|
|
||||||
console.log("LOG __ 0" + document.cookie)
|
|
||||||
console.log("LOG __ 1" + uuid)
|
|
||||||
document.cookie = "S33-DATA="+uuid
|
|
||||||
console.log("LOG __ 2" + document.cookie)
|
|
||||||
console.log("LOG __ 3" + uuid)
|
|
||||||
callBackResult(httpRequest.response)
|
callBackResult(httpRequest.response)
|
||||||
document.location.href = document.location
|
document.location.href = document.location
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -121,19 +115,22 @@ function mainPath() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gotoWrite() {
|
||||||
|
document.location.href = getMainPath()+"/blog/write"
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotoModify() {
|
||||||
|
document.location.href = getMainPath()+"/blog/modify"
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotoWhere() {
|
||||||
|
document.location.href = getMainPath()+"/bums/where"
|
||||||
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
if(document.cookie.split(";").length > 1) {
|
// retrieve all cookies
|
||||||
document.cookie.split(";").forEach(function (v,i,a){
|
document.cookie = "access=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
|
||||||
if(v.search("S33-DATA") > 0) {
|
document.cookie = "refresh=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
|
||||||
document.cookie.replace(v,"S33-DATA=")
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
document.cookie = "S33-DATA="
|
|
||||||
}
|
|
||||||
let logOutUrl = getMainPath() + "/user/logout.ajax";
|
let logOutUrl = getMainPath() + "/user/logout.ajax";
|
||||||
post(logOutUrl,"","","", function (resultData) {
|
post(logOutUrl,"","","", function (resultData) {
|
||||||
alert(resultData)
|
alert(resultData)
|
||||||
@ -159,7 +156,10 @@ function onclickLogin(type, keyword) {
|
|||||||
'user_pw': user_pw.value,
|
'user_pw': user_pw.value,
|
||||||
}
|
}
|
||||||
postLogin(getMainPath()+"/user/login.ajax",type,JSON.stringify(data),keyword, function (resultData) {
|
postLogin(getMainPath()+"/user/login.ajax",type,JSON.stringify(data),keyword, function (resultData) {
|
||||||
alert(resultData)
|
var data = JSON.parse(resultData)
|
||||||
|
// alert(resultData)
|
||||||
|
document.cookie = "access=" + data.token.split(";")[0]+";"
|
||||||
|
document.cookie = "refresh=" + data.refresh.split(";")[0]+";"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
console.log(window.c)
|
console.log(window.c)
|
||||||
console.log(style.getPropertyValue('--FooterHeight'))
|
console.log(style.getPropertyValue('--FooterHeight'))
|
||||||
console.log(style.getPropertyValue('--TopHeight'))
|
console.log(style.getPropertyValue('--TopHeight'))
|
||||||
|
document.querySelector("#title_field").value = baseData.title
|
||||||
var editorHeght = (
|
var editorHeght = (
|
||||||
document.querySelector('#main_layer').getBoundingClientRect().height -
|
document.querySelector('#main_layer').getBoundingClientRect().height -
|
||||||
(
|
(
|
||||||
@ -88,9 +89,9 @@
|
|||||||
<h1>권한이 없는 뎁쇼?!</h1>
|
<h1>권한이 없는 뎁쇼?!</h1>
|
||||||
</th:block>
|
</th:block>
|
||||||
<th:block th:if="${PERMISSION == 'OK'}">
|
<th:block th:if="${PERMISSION == 'OK'}">
|
||||||
<div class="layer">
|
|
||||||
<input id="title_field" />
|
<label for="title_field"></label><input id="title_field" class="write_option">
|
||||||
</div>
|
|
||||||
<div id="editor" ></div>
|
<div id="editor" ></div>
|
||||||
<div class="write_controllbox">
|
<div class="write_controllbox">
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
<html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
|
<html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
|
||||||
<th:block th:fragment="header">
|
<th:block th:fragment="header">
|
||||||
<header>
|
<header>
|
||||||
|
|
||||||
<div id="top">
|
<div id="top">
|
||||||
<td><h3><a aria-label="goToMain" style="color: white" href="javascript:mainPath()" title="goToMain">HOME</a></h3></td>
|
<td><h3><a aria-label="goToMain" style="color: white" href="javascript:mainPath()" title="goToMain">HOME</a></h3></td>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<th:block th:if="${PERMISSION != 'OK'}">
|
<th:block th:if="${PERMISSION != 'OK'}">
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
document.addEventListener("DOMContentLoaded", addListen);
|
document.addEventListener("DOMContentLoaded", addListen);
|
||||||
@ -18,6 +18,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
<th:block th:if="${PERMISSION == 'OK'}">
|
<th:block th:if="${PERMISSION == 'OK'}">
|
||||||
|
<div></div>
|
||||||
|
<td><h3><a aria-label="create New Post" style="color: white" href="javascript:gotoWrite()" title="create New Post">create New Post</a></h3></td>
|
||||||
|
<td><h3><a aria-label="where's Bum" style="color: white" href="javascript:gotoWhere()" title="where's Bum">where's Bum</a></h3></td>
|
||||||
|
<td><h3><a aria-label="modify & open Post" style="color: white" href="javascript:gotoModify()" title="modify & open Post">modify & open Post</a></h3></td>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
<div class="user_info" >
|
<div class="user_info" >
|
||||||
<td><h3><a aria-label="logout" style="color: white" href="javascript:logout()" title="logout">logout</a></h3></td>
|
<td><h3><a aria-label="logout" style="color: white" href="javascript:logout()" title="logout">logout</a></h3></td>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,26 +4,17 @@
|
|||||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
xmlns="http://www.w3.org/1999/html">
|
xmlns="http://www.w3.org/1999/html">
|
||||||
<head>
|
<head>
|
||||||
<!-- <th:block layout:replace="fragments/includes" ></th:block>-->
|
|
||||||
<th:block th:replace="~{fragments/includes :: includes}"></th:block>
|
<th:block th:replace="~{fragments/includes :: includes}"></th:block>
|
||||||
<!-- layout:fragment="head" -->
|
|
||||||
<th:block layout:fragment="head"></th:block>
|
<th:block layout:fragment="head"></th:block>
|
||||||
<!-- <th:block layout:replace="fragments/title" ></th:block> -->
|
|
||||||
<th:block th:replace="~{fragments/title :: title}"></th:block>
|
<th:block th:replace="~{fragments/title :: title}"></th:block>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!--<th:block th:replace="fragments/header :: header"></th:block>-->
|
|
||||||
<th:block th:replace="~{fragments/header :: header}"></th:block>
|
<th:block th:replace="~{fragments/header :: header}"></th:block>
|
||||||
<!--<th:block layout:fragment="content"></th:block>-->
|
|
||||||
|
|
||||||
<th:block layout:fragment="content"></th:block>
|
<th:block layout:fragment="content"></th:block>
|
||||||
<div class="dim_layer">
|
<div class="dim_layer">
|
||||||
<div class="dimBg"></div>
|
<div class="dimBg"></div>
|
||||||
<th:block layout:fragment="popup_layer"></th:block>
|
<th:block layout:fragment="popup_layer"></th:block>
|
||||||
</div>
|
</div>
|
||||||
<!--<th:block th:replace="fragments/footer :: footer"></th:block>-->
|
|
||||||
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
|
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user