-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Description
对以下问题有疑问:
我看了下 SaInterceptor 的实现,以及一些校验注解,有一些几点问题:
SaInterceptor 里是先校验注解,后校验 run 理的逻辑。在我的一个场景中,我需要校验登录和 apikey 二选一认证即可,认证后我需要初始化上下文,如:登录后存数据到 session ,登录和apikey 的话我会存数据到 ThreadLocal 中。
这种情况分2步,一个是实现认证的二选一,一个是实现上下文数据的存放。
用注解实现,但存在一个问题:在这我需要同时使用 @SaCheckLogin 和 @SaCheckApiKey 注解,否则无法二选一,因为注解先行校验。那我后续还需要用代码逻辑实现拦截其他所有请求(因为我不可能每个接口都加 @SaCheckLogin),但我需要部分接口加 @SaCheckApiKey(明确指出哪些接口可开放),但为了避免重复校验,以及部分是通过 apikey 认证的,我需要做一些逻辑判断,但最大的问题在于我的判断【**无法基于明确的条件,只能用异常捕获打逻辑补丁】。**如下代码:
// 测试:只有通过登录校验,或者提供了正确的 ApiKey,才可以进入方法
@RequestMapping("/test")
@SaCheckOr(login = @SaCheckLogin, append = { SaCheckApiKey.class })
@SaCheckApiKey
public SaResult test() {
// ...
return SaResult.ok();
}package com.idataway.pro.core.modules.security.config
import cn.dev33.satoken.annotation.SaCheckLogin
import cn.dev33.satoken.annotation.SaCheckOr
import cn.dev33.satoken.apikey.annotation.SaCheckApiKey
import cn.dev33.satoken.apikey.exception.ApiKeyException
import cn.dev33.satoken.apikey.model.ApiKeyModel
import cn.dev33.satoken.apikey.template.SaApiKeyUtil
import cn.dev33.satoken.interceptor.SaInterceptor
import cn.dev33.satoken.stp.StpUtil
import com.idataway.pro.core.entity.User
import com.idataway.pro.core.entity.query.QMenu.Alias.permission
import com.idataway.pro.core.modules.security.LoggedUser
import com.idataway.pro.core.support.config.TenantContext
import com.idataway.pro.core.support.config.WebContext
import com.idataway.pro.core.support.config.WebContext.Companion.LOGGED_USER
import org.springframework.context.annotation.Configuration
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import java.util.UUID
/**
* @author zhangpeng
*/
@Configuration
class SaTokenConfigurer : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
// 注册Sa-Token的路由拦截器
registry.addInterceptor(SaInterceptor { handler: Any? ->
// 由于注解是先进行校验,因此如果想实现:手动登录以及 apikey 认证二选一的效果,需在接口如下配置:
// @SaCheckOr(login = [SaCheckLogin()], append = [SaCheckApiKey::class])
// @SaCheckApiKey
val handlerMethod = handler as? HandlerMethod
val apikey = handlerMethod?.method?.isAnnotationPresent(SaCheckApiKey::class.java)
// 未配置 SaCheckApiKey 的接口,执行登录校验。
if (apikey == null || !apikey) {
StpUtil.checkLogin()
}
else {
try {
// 初始化基于 apiKey 的登录状态的相关上下文信息。
val userId = SaApiKeyUtil.currentApiKey().loginId as UUID
val user = User.find.byId(userId)!!
// TODO permissions 和 scope 待完善
WebContext.loggedUser(LoggedUser(user, listOf()))
TenantContext.setTenant(WebContext.loggedUsername())
} catch (e: ApiKeyException) {
// 由于注解已经进行过 apiKey 的校验,因此这里直接忽略异常,因为有可能其通过手动登录。
}
}
// 如果用户已登录,初始化相关上下文信息。
if (StpUtil.isLogin()) {
WebContext.loggedUser(StpUtil.getSession().getModel(LOGGED_USER, LoggedUser::class.java))
TenantContext.setTenant(WebContext.loggedUsername())
}
}).addPathPatterns("/**")
.excludePathPatterns(
listOf(
"/login",
"/error",
"/v3/api-docs",
"/public/**"
)
)
}
}还有一个问题:像 @SaCheckLogin @SaCheckApiKey 这种注解,属于同一场景的注解,不存在说需要同时做两种认证,像 SaCheckOr 这种就不应该用于这类。认证场景应该是多选一,权限范围则是存在【且】和【或】。我觉得最好将【认证】和【权限】分开。像认证类,天然就应该过一道即可。而权限范围这类(角色,权限值,scope)才存在布尔逻辑。我也无法直接将注解校验关闭,因像权限类一般用注解是比较好的。
像 spring security 框架我没记错的话,应该是:认证逻辑用代码扩展,没提供注解,并且过了一个认证点即可,权限值和角色,这类需要细粒度控制的才支持注解。这个框架设计较为一体化,内部流程处理的比较好,但的确不容易上手以及扩展。sa-token 则更偏向于util ,流程需要自己组装,但这种安全框架,一旦组装不好,就容易出现翻车,很容易就被绕开。
初步的话我觉得做以下调整会比较好:
- SaCheckOr 不用于认证,只用于权限。
- 可以自定义多个认证点,但认证多选一即可。
- SaCheckApiKey 注解,将 key 的认证和 scope 权限分开。
- isAnnotation 明确指定忽略的注解,或不提供,万一被当成临时工具,临时改了,没还原就凉了。
< 备注:请尽量详细描述问题所在 >