修改为token认证方式

This commit is contained in:
2022-09-14 09:40:25 +08:00
parent 0ccb49afde
commit 19c3640f4f
31 changed files with 406 additions and 218 deletions

View File

@@ -17,7 +17,7 @@ buildscript {
subprojects { subprojects {
group 'com.synebula' group 'com.synebula'
version '1.4.0' version '1.5.0'
buildscript { buildscript {
repositories { repositories {

View File

@@ -17,5 +17,6 @@ dependencies {
api group: 'org.apache.poi', name: 'poi-ooxml', version: '5.0.0' api group: 'org.apache.poi', name: 'poi-ooxml', version: '5.0.0'
api group: 'com.google.code.gson', name: 'gson', version: '2.8.6' api group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
api group: 'com.auth0', name: 'java-jwt', version: '3.14.0' api group: 'com.auth0', name: 'java-jwt', version: '3.14.0'
api group: 'com.google.guava', name: 'guava', version: '31.1-jre'
} }

View File

@@ -2,12 +2,12 @@ package com.synebula.gaea.app
import com.synebula.gaea.app.cmd.ICommandApp import com.synebula.gaea.app.cmd.ICommandApp
import com.synebula.gaea.app.query.IQueryApp import com.synebula.gaea.app.query.IQueryApp
import com.synebula.gaea.data.serialization.json.IJsonSerializer import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.domain.service.ICommand import com.synebula.gaea.domain.service.ICommand
import com.synebula.gaea.domain.service.IService import com.synebula.gaea.domain.service.IService
import com.synebula.gaea.log.ILogger import com.synebula.gaea.log.ILogger
import com.synebula.gaea.query.IQuery import com.synebula.gaea.query.IQuery
import javax.annotation.Resource import org.springframework.beans.factory.annotation.Autowired
/** /**
* 联合服务同时实现了ICommandApp和IQueryApp接口 * 联合服务同时实现了ICommandApp和IQueryApp接口
@@ -24,6 +24,6 @@ open class Application<TCommand : ICommand, TView, ID>(
override var logger: ILogger, override var logger: ILogger,
) : ICommandApp<TCommand, ID>, IQueryApp<TView, ID> { ) : ICommandApp<TCommand, ID>, IQueryApp<TView, ID> {
@Resource @Autowired
override var jsonSerializer: IJsonSerializer? = null override lateinit var httpMessageFactory: HttpMessageFactory
} }

View File

@@ -2,6 +2,7 @@ package com.synebula.gaea.app
import com.google.gson.Gson import com.google.gson.Gson
import com.synebula.gaea.data.message.HttpMessage import com.synebula.gaea.data.message.HttpMessage
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.data.message.Status import com.synebula.gaea.data.message.Status
import com.synebula.gaea.log.ILogger import com.synebula.gaea.log.ILogger
import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.core.context.SecurityContextHolder
@@ -18,12 +19,17 @@ interface IApplication {
*/ */
var logger: ILogger var logger: ILogger
/**
* 日志详细的构造器
*/
var httpMessageFactory: HttpMessageFactory
/** /**
* 安全执行 * 安全执行
*/ */
fun safeExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage { fun safeExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage {
val msg = HttpMessage() val msg = this.httpMessageFactory.create()
try { try {
process(msg) process(msg)
logger.debug(this, "$name business execute success") logger.debug(this, "$name business execute success")
@@ -39,7 +45,7 @@ interface IApplication {
* 可抛出自定义异常信息的安全controller实现了异常捕获和消息组成。 * 可抛出自定义异常信息的安全controller实现了异常捕获和消息组成。
*/ */
fun throwExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage { fun throwExecute(error: String, process: ((msg: HttpMessage) -> Unit)): HttpMessage {
val msg = HttpMessage() val msg = this.httpMessageFactory.create()
try { try {
process(msg) process(msg)
logger.debug(this, "$name business execute success") logger.debug(this, "$name business execute success")
@@ -54,7 +60,7 @@ interface IApplication {
* 获取用户信息 * 获取用户信息
* @param clazz 用户信息结构类 * @param clazz 用户信息结构类
*/ */
fun <T> sessionUser(clazz: Class<T>): T? { fun <T> userSession(clazz: Class<T>): T? {
try { try {
val authentication = SecurityContextHolder.getContext().authentication.principal.toString() val authentication = SecurityContextHolder.getContext().authentication.principal.toString()
try { try {

View File

@@ -2,12 +2,12 @@ package com.synebula.gaea.app
import com.synebula.gaea.app.cmd.ISimpleCommandApp import com.synebula.gaea.app.cmd.ISimpleCommandApp
import com.synebula.gaea.app.query.IQueryApp import com.synebula.gaea.app.query.IQueryApp
import com.synebula.gaea.data.serialization.json.IJsonSerializer import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.domain.model.IAggregateRoot import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.service.ISimpleService import com.synebula.gaea.domain.service.ISimpleService
import com.synebula.gaea.log.ILogger import com.synebula.gaea.log.ILogger
import com.synebula.gaea.query.IQuery import com.synebula.gaea.query.IQuery
import javax.annotation.Resource import org.springframework.beans.factory.annotation.Autowired
/** /**
* 简单的服务, 取消了Command对象 * 简单的服务, 取消了Command对象
@@ -24,6 +24,7 @@ open class SimpleApplication<TRoot : IAggregateRoot<ID>, ID>(
override var logger: ILogger, override var logger: ILogger,
) : ISimpleCommandApp<TRoot, ID>, IQueryApp<TRoot, ID> { ) : ISimpleCommandApp<TRoot, ID>, IQueryApp<TRoot, ID> {
@Resource
override var jsonSerializer: IJsonSerializer? = null @Autowired
override lateinit var httpMessageFactory: HttpMessageFactory
} }

View File

@@ -1,10 +1,10 @@
package com.synebula.gaea.app.cmd package com.synebula.gaea.app.cmd
import com.synebula.gaea.data.serialization.json.IJsonSerializer import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.domain.service.ICommand import com.synebula.gaea.domain.service.ICommand
import com.synebula.gaea.domain.service.IService import com.synebula.gaea.domain.service.IService
import com.synebula.gaea.log.ILogger import com.synebula.gaea.log.ILogger
import javax.annotation.Resource import org.springframework.beans.factory.annotation.Autowired
/** /**
* 指令服务同时实现ICommandApp * 指令服务同时实现ICommandApp
@@ -18,6 +18,6 @@ open class CommandApp<TCommand : ICommand, ID>(
override var service: IService<ID>, override var service: IService<ID>,
override var logger: ILogger, override var logger: ILogger,
) : ICommandApp<TCommand, ID> { ) : ICommandApp<TCommand, ID> {
@Resource @Autowired
override var jsonSerializer: IJsonSerializer? = null override lateinit var httpMessageFactory: HttpMessageFactory
} }

View File

@@ -3,7 +3,6 @@ package com.synebula.gaea.app.cmd
import com.synebula.gaea.app.IApplication import com.synebula.gaea.app.IApplication
import com.synebula.gaea.data.message.HttpMessage import com.synebula.gaea.data.message.HttpMessage
import com.synebula.gaea.data.message.Status import com.synebula.gaea.data.message.Status
import com.synebula.gaea.data.serialization.json.IJsonSerializer
import com.synebula.gaea.domain.service.ICommand import com.synebula.gaea.domain.service.ICommand
import com.synebula.gaea.domain.service.IService import com.synebula.gaea.domain.service.IService
import com.synebula.gaea.spring.aop.annotation.Method import com.synebula.gaea.spring.aop.annotation.Method
@@ -17,27 +16,25 @@ import org.springframework.web.bind.annotation.*
* @since 2020-05-15 * @since 2020-05-15
*/ */
interface ICommandApp<TCommand : ICommand, ID> : IApplication { interface ICommandApp<TCommand : ICommand, ID> : IApplication {
var jsonSerializer: IJsonSerializer?
var service: IService<ID> var service: IService<ID>
@PostMapping @PostMapping
@Method("添加") @Method("添加")
fun add(@RequestBody command: TCommand): HttpMessage { fun add(@RequestBody command: TCommand): HttpMessage {
return HttpMessage(service.add(command)) return this.httpMessageFactory.create(service.add(command))
} }
@PutMapping("/{id:.+}") @PutMapping("/{id:.+}")
@Method("更新") @Method("更新")
fun update(@PathVariable id: ID, @RequestBody command: TCommand): HttpMessage { fun update(@PathVariable id: ID, @RequestBody command: TCommand): HttpMessage {
this.service.update(id, command) this.service.update(id, command)
return HttpMessage() return this.httpMessageFactory.create()
} }
@DeleteMapping("/{id:.+}") @DeleteMapping("/{id:.+}")
@Method("删除") @Method("删除")
fun remove(@PathVariable id: ID): HttpMessage { fun remove(@PathVariable id: ID): HttpMessage {
val msg = HttpMessage() val msg = this.httpMessageFactory.create()
try { try {
msg.data = this.service.remove(id) msg.data = this.service.remove(id)
} catch (ex: IllegalStateException) { } catch (ex: IllegalStateException) {

View File

@@ -3,7 +3,6 @@ package com.synebula.gaea.app.cmd
import com.synebula.gaea.app.IApplication import com.synebula.gaea.app.IApplication
import com.synebula.gaea.data.message.HttpMessage import com.synebula.gaea.data.message.HttpMessage
import com.synebula.gaea.data.message.Status import com.synebula.gaea.data.message.Status
import com.synebula.gaea.data.serialization.json.IJsonSerializer
import com.synebula.gaea.domain.model.IAggregateRoot import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.service.ISimpleService import com.synebula.gaea.domain.service.ISimpleService
import com.synebula.gaea.spring.aop.annotation.Method import com.synebula.gaea.spring.aop.annotation.Method
@@ -17,27 +16,25 @@ import org.springframework.web.bind.annotation.*
* @since 2020-05-15 * @since 2020-05-15
*/ */
interface ISimpleCommandApp<TRoot : IAggregateRoot<ID>, ID> : IApplication { interface ISimpleCommandApp<TRoot : IAggregateRoot<ID>, ID> : IApplication {
var jsonSerializer: IJsonSerializer?
var service: ISimpleService<TRoot, ID> var service: ISimpleService<TRoot, ID>
@PostMapping @PostMapping
@Method("添加") @Method("添加")
fun add(@RequestBody entity: TRoot): HttpMessage { fun add(@RequestBody entity: TRoot): HttpMessage {
return HttpMessage(service.add(entity)) return this.httpMessageFactory.create(service.add(entity))
} }
@PutMapping("/{id:.+}") @PutMapping("/{id:.+}")
@Method("更新") @Method("更新")
fun update(@PathVariable id: ID, @RequestBody entity: TRoot): HttpMessage { fun update(@PathVariable id: ID, @RequestBody entity: TRoot): HttpMessage {
this.service.update(id, entity) this.service.update(id, entity)
return HttpMessage() return this.httpMessageFactory.create()
} }
@DeleteMapping("/{id:.+}") @DeleteMapping("/{id:.+}")
@Method("删除") @Method("删除")
fun remove(@PathVariable id: ID): HttpMessage { fun remove(@PathVariable id: ID): HttpMessage {
val msg = HttpMessage() val msg = this.httpMessageFactory.create()
try { try {
msg.data = this.service.remove(id) msg.data = this.service.remove(id)
} catch (ex: IllegalStateException) { } catch (ex: IllegalStateException) {

View File

@@ -1,10 +1,10 @@
package com.synebula.gaea.app.cmd package com.synebula.gaea.app.cmd
import com.synebula.gaea.data.serialization.json.IJsonSerializer import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.domain.model.IAggregateRoot import com.synebula.gaea.domain.model.IAggregateRoot
import com.synebula.gaea.domain.service.ISimpleService import com.synebula.gaea.domain.service.ISimpleService
import com.synebula.gaea.log.ILogger import com.synebula.gaea.log.ILogger
import javax.annotation.Resource import org.springframework.beans.factory.annotation.Autowired
/** /**
* 指令服务同时实现ICommandApp * 指令服务同时实现ICommandApp
@@ -18,6 +18,6 @@ open class SimpleCommandApp<TRoot : IAggregateRoot<ID>, ID>(
override var service: ISimpleService<TRoot, ID>, override var service: ISimpleService<TRoot, ID>,
override var logger: ILogger, override var logger: ILogger,
) : ISimpleCommandApp<TRoot, ID> { ) : ISimpleCommandApp<TRoot, ID> {
@Resource @Autowired
override var jsonSerializer: IJsonSerializer? = null override lateinit var httpMessageFactory: HttpMessageFactory
} }

View File

@@ -5,7 +5,7 @@ import org.dozer.DozerBeanMapper
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class DozerConverter : IObjectMapper { class DozerMapper : IObjectMapper {
private val converter = DozerBeanMapper() private val converter = DozerBeanMapper()
override fun <T> deserialize(src: Any, targetClass: Class<T>): T = converter.map(src, targetClass) override fun <T> deserialize(src: Any, targetClass: Class<T>): T = converter.map(src, targetClass)

View File

@@ -0,0 +1,101 @@
package com.synebula.gaea.app.component.cache
import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder
import com.synebula.gaea.data.cache.ICache
import java.util.concurrent.ConcurrentMap
import java.util.concurrent.TimeUnit
/**
* 缓存组件
*
* @param expire 滑动过期时间,单位秒
*/
open class Cache<K, V>(expire: Int) : ICache<K, V> {
private val guavaCache: Cache<K, V>
init {
var builder = CacheBuilder.newBuilder()
if (expire != 0) {
builder = builder.expireAfterAccess(expire.toLong(), TimeUnit.SECONDS)
}
this.guavaCache = builder.build()
}
override fun add(key: K, value: V) {
this.guavaCache.put(key, value)
}
/**
* 添加多条缓存记录
*
* @param entries 需要添加的多条缓存记录Map
*/
override fun add(entries: Map<K, V>) {
this.guavaCache.putAll(entries)
}
override fun get(key: K): V? {
return this.guavaCache.getIfPresent(key ?: "")
}
/**
* 获取给出列表key中存在的缓存元素
*
* @param keys 尝试获取的key列表
*/
override fun get(keys: Iterable<K>): Map<K, V> {
return this.guavaCache.getAllPresent(keys)
}
/**
* 清除给出key的缓存
*
* @param key 需要清楚的缓存key
*/
override fun remove(key: K) {
this.guavaCache.invalidate(key ?: "")
}
/**
* 清除给出列表key的缓存
*
* @param keys 需要清楚的缓存key列表
*/
override fun remove(keys: Iterable<K>) {
this.guavaCache.invalidateAll(keys)
}
/**
* 判断keys是否存在
*
* @param keys 需要判断的缓存key列表
*/
override fun exists(keys: Iterable<K>): Map<K, Boolean> {
val result = mutableMapOf<K, Boolean>()
keys.forEach { result[it] = guavaCache.asMap().containsKey(it) }
return result
}
/**
* 判断key是否存在
*
* @param key 需要判断的key
*/
override fun exists(key: K): Boolean {
return this.guavaCache.asMap().containsKey(key)
}
/**
* 清除所有缓存记录
*/
override fun clear() {
this.guavaCache.cleanUp()
}
fun asMap(): ConcurrentMap<K, V> {
return this.guavaCache.asMap()
}
}

View File

@@ -1,12 +1,11 @@
package com.synebula.gaea.app.component.security package com.synebula.gaea.app.component.security
import com.synebula.gaea.app.struct.exception.TokenCloseExpireException import com.synebula.gaea.app.component.security.session.UserSessionManager
import com.synebula.gaea.data.message.HttpMessage import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.data.message.Status import com.synebula.gaea.data.message.Status
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter import org.springframework.web.filter.OncePerRequestFilter
import java.io.IOException import java.io.IOException
import javax.servlet.FilterChain import javax.servlet.FilterChain
import javax.servlet.ServletException import javax.servlet.ServletException
@@ -16,27 +15,50 @@ import javax.servlet.http.HttpServletResponse
/** /**
* 登录成功后 走此类进行鉴权操作 * 登录成功后 走此类进行鉴权操作
*
* @param httpMessageFactory HttpMessage 构建器
* @param userSessionManager 用户信息缓存
* @param storage 存储识别信息存储方式 token/cookie
*/ */
class WebAuthorization(authenticationManager: AuthenticationManager, var tokenManager: TokenManager) : class WebAuthorization(
BasicAuthenticationFilter(authenticationManager) { var httpMessageFactory: HttpMessageFactory,
var userSessionManager: UserSessionManager,
var storage: String = "token"
) : OncePerRequestFilter() {
/**
* token 在 header 中的 key
*/
private val tokenKey = "token"
/** /**
* 在过滤之前和之后执行的事件 * 在过滤之前和之后执行的事件
*/ */
@Throws(IOException::class, ServletException::class) @Throws(IOException::class, ServletException::class)
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) { override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
val token = request.getHeader(tokenManager.header) val identity = if (this.storage == "cookie") {
try { request.cookies.find { it.name == tokenKey }?.value ?: ""
val user = tokenManager.verify(token) } else {
val authentication = request.getHeader(tokenKey) ?: ""
UsernamePasswordAuthenticationToken(user, null, null) }
val user = this.userSessionManager.userSession(identity)
if (user != null) {
user.ip = request.remoteAddr
user.url = request.requestURI
val authentication = UsernamePasswordAuthenticationToken(user, null, null)
SecurityContextHolder.getContext().authentication = authentication SecurityContextHolder.getContext().authentication = authentication
super.doFilterInternal(request, response, chain) chain.doFilter(request, response)
} catch (ex: TokenCloseExpireException) { } else {
response.status = Status.Success response.status = Status.Success
response.characterEncoding = "utf-8" response.characterEncoding = "utf-8"
response.contentType = "text/javascript;charset=utf-8" response.contentType = "text/javascript;charset=utf-8"
response.writer.print(HttpMessage(Status.Reauthorize, tokenManager.sign(ex.payload), "重新下发认证消息")) response.writer.print(
this.httpMessageFactory.create(
Status.Unauthorized,
"登录信息失效, 请重新登录"
)
)
response.flushBuffer() response.flushBuffer()
} }
} }

View File

@@ -1,56 +1,68 @@
package com.synebula.gaea.app.component.security package com.synebula.gaea.app.component.security
import com.synebula.gaea.data.message.HttpMessage import com.synebula.gaea.app.component.security.session.UserSessionManager
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.data.message.Status import com.synebula.gaea.data.message.Status
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.security.authentication.AuthenticationManager import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.SecurityFilterChain
import org.springframework.stereotype.Component import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.UrlBasedCorsConfigurationSource import org.springframework.web.cors.UrlBasedCorsConfigurationSource
@Component @Configuration
@EnableWebSecurity
class WebSecurity { class WebSecurity {
@Autowired @Autowired
lateinit var tokenManager: TokenManager lateinit var userSessionManager: UserSessionManager
@Autowired @Autowired
lateinit var authenticationManager: AuthenticationManager lateinit var httpMessageFactory: HttpMessageFactory
@Value("\${spring.sign-in-url}")
var signInPath = "/sign/in"
/** /**
* 安全配置 * 安全配置
*/ */
@Bean
@Throws(Exception::class) @Throws(Exception::class)
fun filterChain(http: HttpSecurity): SecurityFilterChain { fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
// 跨域共享 httpSecurity.cors().and().csrf().disable() // 跨域伪造请求限制无效
http.cors() // 设置Session的创建策略为Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext
.and().csrf().disable() // 跨域伪造请求限制无效 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.authorizeRequests() // 除了登录接口其他资源都必须登录访问
.anyRequest().authenticated()// 资源任何人都可访问 .and().authorizeRequests().antMatchers(this.signInPath).permitAll().anyRequest().authenticated()
.and() // 添加鉴权拦截器
.addFilter(WebAuthorization(authenticationManager, tokenManager))// 添加JWT鉴权拦截器 .and().addFilterBefore(
.sessionManagement() WebAuthorization(httpMessageFactory, userSessionManager),
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置Session的创建策略为Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext UsernamePasswordAuthenticationFilter::class.java
.and() ).exceptionHandling().authenticationEntryPoint { _, response, _ ->
.exceptionHandling()
.authenticationEntryPoint { _, response, _ ->
response.status = Status.Success response.status = Status.Success
response.characterEncoding = "utf-8" response.characterEncoding = "utf-8"
response.contentType = "text/javascript;charset=utf-8" response.contentType = "text/javascript;charset=utf-8"
response.writer.print(HttpMessage(Status.Unauthorized, "用户未登录,请重新登录后尝试!")) response.writer.print(
this.httpMessageFactory.create(
Status.Unauthorized, "用户未登录,请重新登录后尝试!"
)
)
} }
return http.build()
return httpSecurity.build()
} }
@Bean
@Throws(Exception::class) @Throws(Exception::class)
fun filterChain(): WebSecurityCustomizer { fun ignoringCustomizer(): WebSecurityCustomizer {
return WebSecurityCustomizer { web -> web.ignoring().antMatchers("/sign/**") } return WebSecurityCustomizer { web -> web.ignoring().antMatchers(this.signInPath) }
} }
/** /**

View File

@@ -0,0 +1,33 @@
package com.synebula.gaea.app.component.security.session
/**
* 登陆用户会话信息
*
* @param uid 用户id
* @param user 用户信息
*/
class UserSession(var uid: String, var user: Any) {
/**
* 登录IP
*/
var ip = ""
/**
* 访问的url
*/
var url = ""
/**
* 当前token
*/
var token = ""
/**
* 获取指定类型的用户信息
*/
@Suppress("UNCHECKED_CAST")
fun <T> user(): T {
return user as T
}
}

View File

@@ -0,0 +1,51 @@
package com.synebula.gaea.app.component.security.session
import com.synebula.gaea.app.component.cache.Cache
import com.synebula.gaea.ext.toMd5
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import java.util.*
@Component
class UserSessionManager {
@Value("\${spring.allow-multi-sign}")
private var allowMultiSign = "true"
/**
* 用户id加盐, (尽量)避免泄漏真是用户id
*/
private val salt = "68d84e0d7b9e"
/**
* token分割符
*/
private val delimiter = "-"
// 用户的实际信息
var userSessionCache = Cache<String, UserSession>(7 * 24 * 60 * 60)
/**
* 用户登入方法
*/
fun signIn(uid: String, user: Any): String {
val session = UserSession(uid, user)
val id = "$uid$delimiter$salt".toMd5()
if (allowMultiSign != true.toString()) {
if (userSessionCache.asMap().keys.any { k -> k.split(delimiter).last() == id })
throw UnsupportedOperationException("用户已在另一客户端登录, 请退出后重新尝试登录")
}
val token = "${UUID.randomUUID()}$delimiter${id}"
session.token = token
userSessionCache.add(token, session)
return token
}
fun signOut(token: String) {
this.userSessionCache.remove(token)
}
fun userSession(token: String): UserSession? {
return userSessionCache[token]
}
}

View File

@@ -19,7 +19,7 @@ interface IQueryApp<TView, ID> : IApplication {
@GetMapping("/{id:.+}") @GetMapping("/{id:.+}")
fun get(@PathVariable id: ID): HttpMessage { fun get(@PathVariable id: ID): HttpMessage {
val data = this.query.get(id) val data = this.query.get(id)
val msg = HttpMessage() val msg = this.httpMessageFactory.create()
msg.data = data msg.data = data
return msg return msg
} }
@@ -28,7 +28,7 @@ interface IQueryApp<TView, ID> : IApplication {
@GetMapping @GetMapping
fun list(@RequestParam params: LinkedHashMap<String, String>): HttpMessage { fun list(@RequestParam params: LinkedHashMap<String, String>): HttpMessage {
val data = this.query.list(params) val data = this.query.list(params)
return HttpMessage(data) return this.httpMessageFactory.create(data)
} }
@Method("获取分页数据") @Method("获取分页数据")
@@ -40,6 +40,6 @@ interface IQueryApp<TView, ID> : IApplication {
): HttpMessage { ): HttpMessage {
val params = Params(page, size, parameters) val params = Params(page, size, parameters)
val data = this.query.paging(params) val data = this.query.paging(params)
return HttpMessage(data) return this.httpMessageFactory.create(data)
} }
} }

View File

@@ -1,7 +1,9 @@
package com.synebula.gaea.app.query package com.synebula.gaea.app.query
import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.log.ILogger import com.synebula.gaea.log.ILogger
import com.synebula.gaea.query.IQuery import com.synebula.gaea.query.IQuery
import org.springframework.beans.factory.annotation.Autowired
/** /**
* 联合服务同时实现了ICommandApp和IQueryApp接口 * 联合服务同时实现了ICommandApp和IQueryApp接口
@@ -14,4 +16,8 @@ open class QueryApp<TView, ID>(
override var name: String, override var name: String,
override var query: IQuery<TView, ID>, override var query: IQuery<TView, ID>,
override var logger: ILogger, override var logger: ILogger,
) : IQueryApp<TView, ID> ) : IQueryApp<TView, ID> {
@Autowired
override lateinit var httpMessageFactory: HttpMessageFactory
}

View File

@@ -1,7 +1,7 @@
package com.synebula.gaea.spring.aop package com.synebula.gaea.spring.aop
import com.google.gson.Gson import com.google.gson.Gson
import com.synebula.gaea.data.message.HttpMessage import com.synebula.gaea.data.message.HttpMessageFactory
import com.synebula.gaea.data.message.Status import com.synebula.gaea.data.message.Status
import com.synebula.gaea.exception.NoticeUserException import com.synebula.gaea.exception.NoticeUserException
import com.synebula.gaea.log.ILogger import com.synebula.gaea.log.ILogger
@@ -25,6 +25,9 @@ abstract class AppAspect {
@Autowired @Autowired
lateinit var applicationContext: ApplicationContext lateinit var applicationContext: ApplicationContext
@Autowired
lateinit var httpMessageFactory: HttpMessageFactory
/** /**
* 定义切面的方法 * 定义切面的方法
*/ */
@@ -74,7 +77,7 @@ abstract class AppAspect {
gson.toJson(point.args) gson.toJson(point.args)
}" }"
) )
return HttpMessage(Status.Error, message) return this.httpMessageFactory.create(Status.Error, message)
} }
} }

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
/** /**

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
import java.lang.reflect.Method import java.lang.reflect.Method

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
/** /**

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2014 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
import java.util.* import java.util.*

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
import kotlin.reflect.KClass import kotlin.reflect.KClass

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
/** /**

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2014 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
import com.synebula.gaea.exception.NoticeUserException import com.synebula.gaea.exception.NoticeUserException

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2013 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
import java.lang.reflect.Method import java.lang.reflect.Method

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2013 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
/** /**

View File

@@ -1,16 +1,4 @@
/*
* Copyright (C) 2014 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.synebula.gaea.bus package com.synebula.gaea.bus
import com.synebula.gaea.data.message.messageTopic import com.synebula.gaea.data.message.messageTopic

View File

@@ -7,8 +7,68 @@ package com.synebula.gaea.data.cache
* @version 0.0.1 * @version 0.0.1
* @since 2016年8月15日 下午4:53:19 * @since 2016年8月15日 下午4:53:19
*/ */
interface ICache { interface ICache<K, V> {
fun add(key: String, value: CacheEntity)
operator fun get(key: String): CacheEntity? /**
* 添加一条缓存记录
*
* @param key 缓存key
* @param value 需要缓存的值
*/
fun add(key: K, value: V)
/**
* 添加多条缓存记录
*
* @param entries 需要添加的多条缓存记录Map
*/
fun add(entries: Map<K, V>)
/**
* 尝试获取缓存记录值
*
* @param key 缓存key
*/
operator fun get(key: K): V?
/**
* 获取给出列表key中存在的缓存元素
*
* @param keys 尝试获取的key列表
*/
fun get(keys: Iterable<K>): Map<K, V>
/**
* 清除所有缓存记录
*/
fun clear()
/**
* 清除给出key的缓存
*
* @param key 需要清楚的缓存key
*/
fun remove(key: K)
/**
* 清除给出列表key的缓存
*
* @param keys 需要清楚的缓存key列表
*/
fun remove(keys: Iterable<K>)
/**
* 判断key是否存在
*
* @param key 需要判断的key
*/
fun exists(key: K): Boolean
/**
* 判断keys是否存在
*
* @param keys 需要判断的缓存key列表
*/
fun exists(keys: Iterable<K>): Map<K, Boolean>
} }

View File

@@ -3,28 +3,24 @@ package com.synebula.gaea.data.message
import com.synebula.gaea.data.serialization.json.IJsonSerializer import com.synebula.gaea.data.serialization.json.IJsonSerializer
class HttpMessage() : DataMessage<Any>() { class HttpMessage(private var serializer: IJsonSerializer) : DataMessage<Any>() {
var serializer: IJsonSerializer? = null
constructor(data: Any, serializer: IJsonSerializer? = null) : this() { constructor(data: Any, serializer: IJsonSerializer) : this(serializer) {
this.data = data this.data = data
this.serializer = serializer
} }
constructor(status: Int, message: String, serializer: IJsonSerializer? = null) : this() { constructor(status: Int, message: String, serializer: IJsonSerializer) : this(serializer) {
this.status = status this.status = status
this.message = message this.message = message
this.serializer = serializer
} }
constructor(status: Int, data: Any, message: String, serializer: IJsonSerializer? = null) : this( constructor(status: Int, data: Any, message: String, serializer: IJsonSerializer) : this(
status, status,
message, message,
serializer serializer
) { ) {
this.data = data this.data = data
this.serializer = serializer
} }
fun load(msg: DataMessage<*>) { fun load(msg: DataMessage<*>) {
@@ -34,6 +30,6 @@ class HttpMessage() : DataMessage<Any>() {
} }
override fun toString(): String { override fun toString(): String {
return serializer?.serialize(this) ?: super.toString() return serializer.serialize(this)
} }
} }

View File

@@ -0,0 +1,22 @@
package com.synebula.gaea.data.message
import com.synebula.gaea.data.serialization.json.IJsonSerializer
class HttpMessageFactory(private var serializer: IJsonSerializer) {
fun create(): HttpMessage {
return HttpMessage(serializer)
}
fun create(data: Any): HttpMessage {
return HttpMessage(data, serializer)
}
fun create(status: Int, message: String): HttpMessage {
return HttpMessage(status, message, serializer)
}
fun create(status: Int, data: Any, message: String): HttpMessage {
return HttpMessage(status, data, message, serializer)
}
}