显示导航

当前登录用户的自定义租户解析器

365bet地区了解如何创建自定义租户解析器并使用365bet地区多租户功能根据当前登录用户或JWT切换租户

s塞尔吉奥·德尔阿莫

365bet地区版本 3.3.8

365bet地区培训

365bet地区培训由创建并积极维护365bet地区框架的人们开发和交付

入门

GORM多租户支持包括多个内置租户解析器365bet地区在本指南中,您将创建自己的租户解析器,以基于365bet地区 rest api应用程序中的登录用户切换租户。Spring Security REST插件.

您将需要什么

要完成本指南,您将需要以下内容

  • 花些时间在你手上

  • 体面的文本编辑器或IDE

  • 安装了JDK或更高版本JAVA首页适当配置

如何完成指南

要开始,请执行以下操作

要么

365bet地区指南存储库包含两个文件夹

  • 初始初始项目通常是一个简单的365bet地区应用程序,其中包含一些其他代码,可以帮助您快速入门

  • 完成一个完整的示例它是按照指南中介绍的步骤进行操作并将这些更改应用于文档的结果。初始

要完成指南,请转到初始

  • 光盘进入grails指南grails自定义安全性租户解析器初始

并按照下一节中的说明进行操作

您可以直接前往完成的例子如果你光盘进入grails指南grails自定义安全租户解析器已完成

编写申请

本指南使用多租户标识符模式来了解更多信息单数据库多租户区分符列指南

创建域类

365bet地区应用程序域演示计划groovy
演示进口 grails gorm多租户

 计划 实施多租户 { (1)
    标题用户名静态的映射tenantId那么: '用户名' (2)
    }
}
1 实行多租户将这个领域类别视为多租户的特征
2 将租户标识符设置为名为的列用户名

创建一个界面PlanService这是一个GORM数据服务.

365bet地区应用程序服务演示PlanDataService groovy
演示进口 grails gorm多租户CurrentTenant
进口 grails gorm services服务
进口 常规转换CompileStatic

静态编译
服务计划(1)
当前租户 (2)
接口plandataservi通过清单findAll计划保存标题虚空deleteByTitle标题
1 用注释它grails gorm services服务服务适用于域类的注释
2 解决此类的上下文中的当前租户

控制者

创建一个与先前定义的结合使用的简单控制器PlanService服务

365bet地区应用程序控制器演示PlanController groovy
演示进口 常规转换CompileStatic

静态编译
 计划控制器PlanDataService planDataService定义 指数() {
        [planListplanDataService findAll

我们渲染计划在的帮助下将实例作为JSONJSON视图

grails应用程式意见计划索引gson
进口 演示计划模型可迭代的planList json tmpl计划'计划'planList
grails应用程序视图计划gson地图
进口 演示计划模型计划计划json标题计划标题

连线安全

创建安全域类您可以使用Spring Security Core创建这些类。快速入门.

grails应用程序域演示用户groovy
演示进口 常规转换EqualsAndHashCode
进口 常规转换ToString
进口 grails编译器365bet地区CompileStatic

365bet地区CompileStatic
EqualsAndHashCode包括'用户名')
ToString包括'用户名'includeNames真正includePackage)
 用户 实施 可序列化 {

    私人的 静态的 最后 serialVersionUID1

    用户名密码布尔值已启用真正
    布尔值accountExpired布尔值帐户被锁定布尔值密码已过期<角色getAuthorities UserRole findAllByUser这个)  清单角色 <角色>
    }

    静态的约束密码可为空: , 空白: , 密码: 真正用户名可为空: , 空白: , 独特: 真正
    }

    静态的映射密码: '密码'
    }
}
grails应用程序域演示角色groovy
演示进口 常规转换EqualsAndHashCode
进口 常规转换ToString
进口 grails编译器365bet地区CompileStatic

365bet地区CompileStatic
EqualsAndHashCode包括'权威')
ToString包括'权威'includeNames真正includePackage)
 角色 实施 可序列化 {

        私人的 静态的 最后 serialVersionUID1

        权威静态的约束权限可为空: , 空白: , 独特: 真正
        }

        静态的映射缓存真正
        }
}

建立课程UserPasswordEncoderListener处理用户的密码编码

它使用听众注释以听同步地GORM活动.

src主要groovy演示UserPasswordEncoderListener groovy
演示进口 grails插件springsecurity SpringSecurityService
进口 组织grails数据存储区映射引擎事件AbstractPersistenceEvent
进口 组织grails数据存储区映射引擎事件PreInsertEvent
进口 组织grails数据存储区映射引擎事件PreUpdateEvent
进口 org springframework bean工厂注释自动装配
进口 grails事件注释gorm侦听器
进口 常规转换CompileStatic

静态编译
 UserPasswordEncoderListener {

    自动接线SpringSecurityService springSecurityService听众用户虚空onPreInsertEvent PreInsertEvent事件encodePasswordForEvent事件听众用户虚空onPreUpdateEvent PreUpdateEvent事件encodePasswordForEvent事件私人的 虚空encodePasswordForEvent AbstractPersistenceEvent事件如果事件entityObject实例用户用户u事件entityObject用户如果u密码事件实例PreInsertEvent事件实例PreUpdateEvent u isDirty'密码'事件getEntityAccess setProperty'密码'encodePassword u密码私人的 encodePassword密码springSecurityService密码编码器springSecurityService encodingPassword密码密码

将先前的侦听器定义为Bean

grails app conf春季资源groovy
进口 演示UserPasswordEncoderListenerbean用户PasswordEncoder侦听器用户PasswordEncoder侦听器

在以下位置配置Spring Security Coreyml应用程序

grails应用配置会议应用yml

:
    插入:
        弹簧安全:
            securityConfigType: InterceptUrlMap
            userLookup:
                userDomainClassName: 演示用户
                AuthorityJoinClassName: 演示UserRole
            权威:
                班级名称: 演示角色
            filterChain:
                chainMap:
                    - 无状态链
                        : /**
                        过滤器: JOINED FILTERS异常TranslationFilter authenticationProcessingFilter securityContextPersistenceFilter RememberMeAuthenticationFilter
            InterceptUrlMap:
                -
                    : /
                    访问:
                        - 角色村
                -
                    : 错误
                    访问:
                        - allowAll
                -
                    : 消防登录
                    访问:
                        - 角色一致
                -
                    : API验证
                    访问:
                        - 角色一致
                -
                    : oauth访问令牌
                    访问:
                        - 角色一致
                -
                    : 计划
                    访问:
                        - 角色村

创建几个GORM数据服务与涉及我们的应用程序证券化的域类一起工作

grails应用服务演示RoleDataService groovy
演示进口 grails gorm services服务
进口 常规转换CompileStatic

静态编译
服务(角色)
接口RoleDataService虚空删除可序列化ID虚空通过授权删除权威角色findByAuthority权威角色saveByAuthority权威
grails应用服务演示UserDataService groovy
演示进口 grails gorm services服务
进口 常规转换CompileStatic

静态编译
服务用户接口UserDataService用户保存用户名密码虚空删除可序列化id用户findByUsername用户名
grails应用程序服务演示UserRoleDataService groovy
演示进口 grails gorm服务查询
进口 grails gorm services服务
进口 常规转换CompileStatic

服务用户角色静态编译
接口UserRoleDataService询问("""选择$用户用户名来自${UserRole userRole}内部联接${用户用户user角色用户}内部联接${角色角色用户角色角色}哪里$角色权威$权威""")
    清单<findAllUsernameByAuthority权限UserRole保存用户用户角色角色虚空删除用户用户角色角色虚空deleteByUser用户用户

创建服务以处理具有特定角色的用户创建

grails应用程序服务演示UserService groovy
演示进口 grails gorm Transactions事务性
进口 常规转换CompileStatic

静态编译
 用户服务RoleDataService roleDataService UserRoleDataService userRoleDataService UserDataService userDataService交易性用户保存用户名密码权威角色角色roleDataService findByAuthority权限如果角色角色roleDataService saveByAuthority授权用户用户useruserDataService保存用户名密码userRoleDataService保存用户角色user

自定义租户解析器

下表包含所有租户解析器GORM附带的可直接使用的实现。软件包名称已与组织grails数据存储区映射g为了简洁

那么 描述

多租户解析器固定租户解析器

解决固定的租户ID

o g d m多租户解析器SystemProperty租户解析器

通过名为的系统属性解析租户IDgorm tenantId

多租户Web SubDomain租户解析器

通过DNS解析子域中的租户ID

多租户Web Cookie租户解析器

从名为HTTP的HTTP cookie中解析当前租户gorm tenantId默认情况下

多租户Web会话租户解析器

使用属性从HTTP会话解析当前租户gorm tenantId默认情况下

但是,您可以轻松地创建自定义的租户解析器,以便创建一个实现org grails数据存储区映射多租户TenantResolver.

下一节将向您展示如何创建两个自定义租户解析器

JWT的租户解析器

365bet地区365bet地区 Spring Security REST插件允许您使用存在于其中的JWT令牌对应用程序进行身份验证授权书标头

示例向计划在中包含一个JWT令牌授权书标头

请求重复curl http本地主机计划H授权承载eyJhbGciOiJIUzI NiJ H接受应用程序json

我们可以创建自定义租户解析程序,该程序提取JWT令牌重新水化并提取用户名

src main groovy演示CurrentUserByJwtTenantResolver groovy
演示进口 365bet地区 Gorm DetachedCriteria
进口 grails插件springsecurity rest令牌存储TokenStorageService
进口 常规转换CompileStatic
进口 组织grails数据存储区映射多租户AllTenantsResolver
进口 org grails数据存储区映射多租户TenantResolver
进口 组织grails数据存储区映射多租户异常TenantNotFoundException
进口 org springframework bean工厂注释自动装配
进口 org springframework安全核心用户详细信息UserDetails
进口 org springframework网络上下文请求RequestAttributes
进口 org springframework网络上下文请求RequestContextHolder
进口 org springframework Web上下文请求ServletWebRequest

进口 javax servlet http HttpServletRequest

静态编译
 CurrentUserByJwtTenantResolver 实施AllTenantsResolver(1)

    上市 静态的 最后 标题名称'授权书'
    上市 静态的 最后 标头值前缀'承载者'

    headerName标题名称标头值前缀HEADER VALUE PREFIX自动接线令牌存储服务tokenStorageService覆写
    可序列化resolveTenantIdentifier抛出TenantNotFoundException RequestAttributes requestAttributes RequestContextHolder getRequestAttributes如果requestAttributes实例ServletWebRequest HttpServletRequest httpServletRequest ServletWebRequest requestAttributes getRequest令牌httpServletRequest getHeader headerName toLowerCase如果代币 TenantNotFoundException"无法从HTTP标头解析租户${headerName}")
            }

            如果令牌startsWith headerValuePreffix令牌令牌子字符串headerValuePreffix长度UserDetails userDetails tokenStorageService loadUserByToken令牌用户名userDetails用户名如果用户名返回用户名 TenantNotFoundException"无法从HTTP标头解析租户${headerName}")
        }
         TenantNotFoundException"无法在网络请求之外解析租户")
    }

    覆写
    可迭代的<可序列化resolveTenantIdsDetachedCriteria用户不同'用户名'清单
1 使用基于鉴别器的多租户时,您可能需要实施AllTenantsResolver如果想随时迭代所有可用的租户,请在Tenant Resolver实现中使用接口AllTenantsResolver扩展了TenantResolver创建自己的租户解析器实现org grails数据存储区映射多租户TenantResolver.

将自定义租户解析器定义为bean

grails app conf春季资源groovy
进口 演示CurrentUserByJwtTenantResolver豆currentUserByJwtTenantResolver CurrentUserByJwtTenantResolver

创建一个集成测试以测试自定义的Tenant Resolver

src集成测试groovy演示CurrentUserByJwtTenantResolverSpec groovy
演示进口 grails测试mixin集成集成
进口 组织grails数据存储区映射多租户异常TenantNotFoundException
进口 org springframework bean工厂注释自动装配
进口 org springframework模拟网络MockHttpServletRequest
进口 org springframework网络上下文请求RequestContextHolder
进口 org springframework Web上下文请求ServletWebRequest
进口 spock lang规格
进口 spock lang忽略

忽略( { 系统盖滕'特拉维斯')  布尔值 } )
积分
 CurrentUserByJwtTenantResolverSpec 延伸规格自动接线CurrentUserByJwtTenantResolver currentUserTenantResolver虚空 "测试HttpHeader解析程序在Web请求之外引发异常"() {
        什么时候currentUserTenantResolver resolveTenantIdentifier然后:
        定义e抛出TenantNotFoundException e消息"无法在网络请求之外解析租户"
    }

    虚空 "测试未找到租户ID"() {
        设定:
        定义请求MockHttpServletRequest"得到", ""RequestContextHolder setRequestAttributesServletWebRequest请求什么时候currentUserTenantResolver resolveTenantIdentifier然后:
        定义e抛出TenantNotFoundException e消息"无法从HTTP标头解析租户${CurrentUserByJwtTenantResolver标题名称}"

        清理RequestContextHolder setRequestAttributes空值)
    }

    虚空 "存在请求时,测试HttpHeader值是租户ID"() {

        设定MockHttpServletRequest请求MockHttpServletRequest"得到", "")
        wt'''\
eyJhbGciOiJiuzI NiJ eyJwcmluY lwYWwiOiJINHNJQUFBQUFBQUFBSlZTdjA VVFSUitleHhDZ xF\
d tunun V Zksdtv C Rkibzgtiehznz OSE VDZKSZ Rtvmrksijj R\
nRcL FPTUZMVFV th BhMelVNjFlZlB OSt TjZRV XZ RthPRnRHRW MFNvMEdaR FNUmlsQn\
ZoZW GdTBjVG Dc J QVd UkJLNVBVSUdBUVVYRURoNnhMZDdoTmNsVlVsdHJiMkhrNmwwRGM b tONHd\
iaHFlNG MTJlTXNkYVlOXC DWlRVd ZjS pLM RGSThpblN WDBHcXBtd EOFRwTWxqT vMjBcL Vo\
ElJEU udUxURDBERlV QzB WmpEQmM ZXBTVldnZGZEdzJtenVoS cxMGRVWmpHZmNXbkwzVDVLbTg Yj\
l YmVwS FbjJJVnFOd ZvVUhmUFBUVDBQT dpbHBKU M M NiRXVsT hZYndvc RmM wvbXk K RrMzZy\
QWtDZHZMajduM wrWkFINlB NWNQaTJLRGlJSDAwUFdTMWk bTVHYnFaTDVyVUd XC QdjQ ZGVqaTczM\
k VHNFYVwvK Z K o emZOOVJaMW uSERuUjdhRWRIdVZQMDNrU wy RUN lRaTlzaWpTVFNDOUtPWXh\
SlVwaWlsczFXZzc ZG EXC UnBiK ZodWhiSDVsWVlmM UWRtMUkrRUdSMnk c pKcld WDkrK BQZ\
zJSOGlXWVhSRHBjNVV MlRKYWlScDIwMG wK BaaWErbmUwWElRWVArZ JPZUdRVkZBTUFBQT Iiwic\
ViijoidmVjdG yIiwicm sZXMiOlsiUk MRV WSU​​xMQUlOll sImlhdCI MTUwNDc MTEyNX cf rGNrNolchQ QyMsPB fwzYGiihBkRF KU soxc'''请求addHeader'授权书', "承载者$wt"RequestContextHolder setRequestAttributesServletWebRequest请求什么时候:
        定义tenantId currentUserTenantResolver resolveTenantIdentifier然后tenantId"向量"

        清理RequestContextHolder setRequestAttributes空值)
    }
}

接下来将配置我们的应用程序以使用此自定义租户解析器

通过修改配置多租户yml应用程序.

grails应用配置会议应用yml

    蓝色的:
        多租户:
            模式: 判别器(1)
            tenantResolverClass: 演示CurrentUserByJwtTenantResolver(2)
        反应堆:
            是否将GORM事件转换为Reactor事件
            由于性能原因,默认情况下禁用
            大事记: 
1 将多租户模式定义为判别器
2 使用自定义类设置Tenant Resolver类,您将在本指南的后面部分编写该类

租户解析器(按用户)

另外,您可能希望结合使用由Spring Security REST保护的无状态端点和由Spring Security Core保护的有状态端点的组合。您将有兴趣通过当前登录的用户创建自定义解析器

src main groovy演示CurrentUserTenantResolver groovy
演示进口 365bet地区 Gorm DetachedCriteria
进口 grails插件springsecurity SpringSecurityService
进口 常规转换CompileDynamic
进口 常规转换CompileStatic
进口 组织grails数据存储区映射多租户AllTenantsResolver
进口 org grails数据存储区映射多租户TenantResolver
进口 组织grails数据存储区映射多租户异常TenantNotFoundException
进口 org springframework bean工厂注释自动装配
进口 org springframework上下文注释懒惰
进口 org springframework安全核心用户详细信息UserDetails

静态编译
 CurrentUserTenantResolver 实施AllTenantsResolver(1)

    自动接线
    SpringSecurityService springSecurityService覆写
    可序列化resolveTenantIdentifier抛出TenantNotFoundException用户名已记录如果用户名返回用户名 TenantNotFoundException"Spring Security Principal无法解决租户")
    }

    loggingUsername如果springSecurityService主体实例  ) {
            返回springSecurityService主体如果springSecurityService主体实例用户详细信息返回UserDetails springSecurityService主体用户名空值
    }

    覆写
    可迭代的<可序列化resolveTenantIdsDetachedCriteria用户不同'用户名'清单
1 使用基于鉴别器的多租户时,您可能需要实施AllTenantsResolver如果想随时迭代所有可用的租户,请在Tenant Resolver实现中使用接口AllTenantsResolver扩展了TenantResolver创建自己的租户解析器实现org grails数据存储区映射多租户TenantResolver.

将自定义租户解析器定义为bean

grails app conf春季资源groovy
进口 演示CurrentUserTenantResolverbeans currentUser租户解析器currentUser租户解析器

创建一个集成测试以测试自定义的Tenant Resolver

src集成测试groovy演示CurrentUserTenantResolverSpec groovy
演示进口 grails测试mixin集成集成
进口 组织grails数据存储区映射多租户异常TenantNotFoundException
进口 org springframework bean工厂注释自动装配
进口 org springframework安全认证UsernamePasswordAuthenticationToken
进口 org springframework安全核心GrantedAuthority
进口 org springframework安全核心授权AuthorityUtils
进口 org springframework安全核心上下文SecurityContextHolder
进口 org springframework测试注释回滚
进口 spock lang规格

积分
 CurrentUserTenantResolverSpec 延伸规范UserDataService userDataService RoleDataService roleDataService UserRoleDataService userRoleDataService自动接线CurrentUserTenantResolver currentUserTenantResolver虚空 "如果未登录,则“测试当前用户”会引发TenantNotFoundException"() {
        什么时候currentUserTenantResolver resolveTenantIdentifier然后:
        定义e抛出TenantNotFoundException e消息"Spring Security Principal无法解决租户"
    }

    虚空 "测试当前登录的用户已解决"() {
        给定:
        角色角色roleDataService saveByAuthority'角色用户'用户用户userDataService保存'管理员', '管理员'userRoleDataService保存用户角色什么时候loginAs'管理员', '角色用户')
        可序列化用户名currentUserTenantResolver resolveTenantIdentifier然后用户名用户名什么时候: "验证AllTenantsResolver resolveTenantIds"
        可迭代的<可序列化tenantIds使用NewSession演示的用户tenantIds currentUserTenantResolver resolveTenantIds然后tenantIds到列表大小1tenantIds toList得到0) == '管理员'

        清理userRoleDataService删除用户角色roleDataService删除角色userDataService删除用户ID虚空loginAs用户名权限用户用户userDataService findByUsername用户名如果用户必须通过管理员身份验证才能创建ACL
            清单AuthorityList AuthorityUtils createAuthorityList授权SecurityContextHolder上下文认证UsernamePasswordAuthenticationToken用户名用户名用户密码授权列表

接下来将配置我们的应用程序以使用此自定义租户解析器

通过修改配置多租户yml应用程序.

grails应用配置会议应用yml

    蓝色的:
        多租户:
            模式: 判别器(1)
            tenantResolverClass: 演示CurrentUserTenantResolver(2)
        反应堆:
            是否将GORM事件转换为Reactor事件
            由于性能原因,默认情况下禁用
            大事记: 
1 将多租户模式定义为判别器
2 使用自定义类设置Tenant Resolver类,您将在本指南的后面部分编写该类

功能测试

创建一个功能测试,以验证我们可以使用API​​切换用户

src集成测试groovy演示PlanControllerSpec groovy
演示进口 grails gorm多租户租户
进口 grails插件rest client RestBuilder
进口 grails测试mixin集成集成
进口 spock lang规格
进口 spock lang忽略

忽略( { 系统盖滕'特拉维斯')  布尔值 } )
积分
 PlanControllerSpec 延伸规范PlanDataService planDataService UserDataService userDataService UserRoleDataService userRoleDataService UserService userService RoleDataService roleDataService RestBuilder restRestBuilder的accessTokenp定义休息休息"HTTP本地主机${服务器端口}消防登录"接受'应用程序json'内容类型'应用程序json'json用户名u密码p如果状态200 ) {
            返回响应json访问令牌空值
    }

    定义 "检索当前登录用户的计划"() {
        给定用户向量userService保存'向量', '秘密', '角色村'用户gru userService保存'起重机', '秘密', '角色村'带有ID的租户"起重机") { (1)planDataService保存'偷月亮'带有ID的租户"向量"planDataService保存'偷金字塔')
        }

        什么时候: '用gru登录'
        gruAccessToken accessToken'起重机', '秘密')

        然后gruAccessToken什么时候:
        定义休息休息"HTTP本地主机${服务器端口}计划"接受'应用程序json'标头'授权书', "承载者${gruAccessToken}")
        }

        然后状态200响应JSON toString'标题偷月球'

        什么时候: '用矢量登录'
        vectorAccessToken accessToken'向量', '秘密')

        然后vectorAccessToken什么时候休息休息"HTTP本地主机${服务器端口}计划"接受'应用程序json'标头'授权书', "承载者${vectorAccessToken}")
        }

        然后状态200响应JSON toString'偷金字塔'

        清理带有ID的租户"起重机") { (1)planDataService deleteByTitle'偷月亮'带有ID的租户"向量"planDataService deleteByTitle'偷金字塔'userRoleDataService deleteByUser gru userDataService删除gru id userRoleDataService deleteByUser向量userDataService删除向量ID roleDataService deleteByAuthority'角色村')
    }
}
1 您可以使用withId方法使用特定的租户ID

运行测试

运行测试

grailsw grails测试应用程序grails打开测试报告

要么

gradlew检查打开构建报告测试索引html

帮助365bet地区

OCI赞助了本指南的创建OCI提供了几种365bet地区服务:

免费咨询

OCI 365bet地区团队包括365bet地区联合创始人Jeff Scott Brown和Graeme Rocher检查我们的365bet地区课程并向发展和维护365bet地区的工程师学习

Grails OCI团队