User management scaffolding, integration: User password login, mobile login, OAuth2 login(Based on JustAuth), jwt, validate code(image, sms, sliderCode), RBAC, Support multi-tenancy, SLF4J-MDC, sign etc...
User management scaffolding
UMS is a non-intrusive, highly decoupled from business, customizable user management scaffolding.
User management scaffolding, integration: User password login, mobile login, OAuth2 login(Based on JustAuth),
one click login, Support multi-tenancy, jwt , validate code(image, sms, sliderCode), RBAC, SLF4J-MDC, signed etc…
UMS feature list
:Module | Function |
---|---|
commons | common component module |
ums | Integrated commons/core/vc/mdc/oauth/rbac/jwt module |
core | Username password login/Mobile login and automatic registration/signed/Simplify session、remember me、csrf cors etc configuration/session redis cache/Return json or html data according to the set response method (JSON and REDIRECT)/JWT/mdc model |
vc | validate code(image, SMS, slider) verification function, integrated mdc model |
mdc | Support log link tracking function based on SLF4J MDC mechanism |
oauth | OAuth2 login by JustAuth, one-click login, integrated jwt/mdc model |
rbac | RBAC-based access control, supports multi-tenancy, integrated mdc model |
jwt | JWT function, integrated mdc model |
dependencies | UMS Dependencies |
demo | basic-example/basic-detail-example/permission-example/quickStart/session-detail-example/validate-codi-example/justAuth-security-oauth2-example/tenant-example/jwt-example |
demo | demo function |
---|---|
basic-example | Basic function: the simplest configuration/one-click login |
basic-detail-example | Detailed configuration of basic functions: anonymous/session simple configuration/rememberMe/csrf/cors/login routing/signed |
permission-example | RBAC-based permission function settings |
quickStart | quick start example |
multi-tenancy-example | multi tenant registration and login example |
justAuth-security-oauth2-example | Detailed example of third-party authorized login, MDC log link tracking configuration |
session-detail-example | Session and session cache detailed configuration |
validate-code-example | Basic functions: verification code (including slider verification code), mobile login configuration |
jwt-example | JWT function example |
maven
:
<dependency>
<groupId>top.dcenter</groupId>
<artifactId>ums-spring-boot-starter</artifactId>
<version>[2.2.0,)</version>
</dependency>
TODO List
:Quick Start
:user service: Must implemente
RBAC-based access control: Support multi-tenancy
推荐通过实现 AbstractUriAuthorizeService 来实现此接口
AbstractUriAuthorizeService: 必须实现(Must implemente)
注意:
1. 推荐实现 AbstractUriAuthorizeService 同时实现 UpdateCacheOfRolesResourcesService 更新与缓存权限服务, 有助于提高授权服务性能.
2. 对传入的 Authentication 的 authorities 硬性要求:
// 此 authorities 可以包含: [ROLE_A, ROLE_B, ROLE_xxx TENANT_110110, SCOPE_read, SCOPE_write, SCOPE_xxx]
// authorities 要求:
// 1. 角色数量 >= 0
// 2. SCOPE 数量 >= 0
// 3. 多租户数量 1 或 0
// 4. 角色数量 + SCOPE 数量 >= 1
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
3. 此框架默认实现 hasPermission(Authentication, HttpServletRequest)
方法访问权限控制, 通过 UriAuthoritiesPermissionEvaluator
实现, 使用此接口的前提条件是: 应用使用的是 restful 风格的 API;
如果不是 restful 风格的 API, 请使用 hasPermission(Authentication, String, String)
接口的访问权限控制,
此接口使用注解的方式 @PerAuthorize("hasPermission('/users', 'list')")
来实现, 使用注解需先开启 @EnableGlobalMethodSecurity(prePostEnabled = true)
注解.
UpdateCacheOfRolesResourcesService:
建议:
1. 基于 角色 的权限控制: 实现所有角色 uri(资源) 的权限 Map(roleAuthority, map(uri, Set(permission))) 的更新与缓存本机内存.
2. 基于 SCOPE 的权限控制: 情况复杂一点, 但 SCOPE 类型比较少, 也还可以像 1 的方式实现缓存本机内存与更新.
3. 基于 多租户 的权限控制: 情况比较复杂, 租户很少的情况下, 也还可以全部缓存在本机内存, 通常情况下全部缓存内存不现实, 只能借助于类似 redis 等的内存缓存.
注意:
1. 在添加资源时, 通过PermissionType.getPermission() 来规范的权限格式, 因为要支持 restful 风格的 Api,
在授权时需要对 HttpMethod 与对应的权限进行匹配判断
2. 如果实现了 UpdateCacheOfRolesResourcesService 接口, 未实现 RolePermissionsService 接口,
修改或添加基于"角色/多租户/SCOPE "的资源权限时一定要调用 UpdateCacheOfRolesResourcesService 对应的方法, 有两种方式: 一种发布事件, 另一种是直接调用对应服务;
// 1. 推荐用发布事件(异步执行)
applicationContext.publishEvent(new UpdateRolesResourcesEvent(true, UpdateRoleResourcesDto);
// 2. 直接调用服务
// 角色权限资源
UpdateCacheOfRolesResourcesService.updateAuthoritiesByRoleId(roleId, resourceClass, resourceIds);
// 多租户的角色权限资源
UpdateCacheOfRolesResourcesService.updateAuthoritiesByRoleIdOfTenant(tenantId, roleId, resourceClass, resourceIds);
// SCOPE 的角色权限资源
UpdateCacheOfRolesResourcesService.updateAuthoritiesByScopeId(scopeId, roleId, resourceClass, resourceIds);
// 角色组权限资源
UpdateCacheOfRolesResourcesService.updateRolesByGroupId(groupId, roleIds);
// 多租户的角色组权限资源
UpdateCacheOfRolesResourcesService.updateRolesByGroupIdOfTenant(tenantId, groupId, roleIds);
3. 实现此 RolePermissionsService 接口, 不需要执行上两种方法的操作, 已通过 AOP 方式实现发布 UpdateRolesResourcesEvent 事件.
4. 注意: RolePermissionsServiceAspect 切面生效前提, 事务的 Order
的值必须 大于 1, 如果是默认事务(优先级为 Integer.MAX_VALUE
)不必关心这个值, 如果是自定义事务, 且设置了 Order 的值, 那么值**必须 大于 1**.
时序图
- 权限更新及权限缓存实时更新时序图
验证码(ValidateCode)
短信验证码(SMS validate code): 默认空实现
图片验证码(image validate code): 已实现缓存功能, 支持定时刷新缓存功能, 可以自定义缓存验证码图片的输出路径与缓存数量
滑块验证码(Slider validate code): 已实现缓存功能, 支持定时刷新缓存功能, 可以自定义缓存验证码图片的输出路径与缓存数量, 支持自定义源图片路径与模板图片路径(源图片与模板图片参考
validate-code-example)
自定义验证码(customize validate code):
OAuth2
对 OAuth2 流程中的 state 进行自定义编解码. 可以传递必要的信息
用户需要时实现
, 对第三方授权登录流程中的 state 进行自定义编解码. 可以传递必要的信息,获取第三方用户信息的接口
第三方授权登录用户的注册, 绑定, 更新第三方用户信息与 accessToken 信息的接口
ConnectionService: 第三方授权登录用户的注册, 绑定, 更新第三方用户信息与 accessToken 信息的接口, 一般不需要用户实现.
除非想自定义获取第三方用户信息的逻辑, 实现此接口注入 IOC 容器即可替代.
注意: 要关闭应用启动时自动创建内置的 auth_token
与 user_connection
表设置属性 ums.repository.enableStartUpInitializeTable = false
.
UsersConnectionRepository: 第三方授权登录的第三方用户信息增删改查, 绑定与解绑及查询是否绑定与解绑接口, 一般不需要用户实现.
除非想自定义获取第三方用户信息的逻辑, 实现此接口注入 IOC 容器即可替代.
UsersConnectionTokenRepository: 第三方授权登录用户 accessToken 信息表增删改查接口, 一般不需要用户实现.
除非想自定义获取第三方用户信息的逻辑, 实现此接口注入 IOC 容器即可替代.
取消 OAuth2 的内置数据库说明
一. 同时取消第三方登录的 user_connection 与 auth_token 表
1. 属性配置
ums:
oauth:
# 是否支持内置的第三方登录用户表(user_connection) 和 auth_token 表. 默认: true.
# 注意: 如果为 false, 则必须重新实现 ConnectionService 接口.
enable-user-connection-and-auth-token-table: false
2. 必须重新实现
top.dcenter.ums.security.core.api.oauth.signup.ConnectionService
接口二. 取消第三方登录 auth_token 表
1. 属性配置
ums:
oauth:
# 是否支持内置的第三方登录 token 表(auth_token). 默认: true.
enable-auth-token-table: false
自定义 OAuth2 Login 扩展接口: 内置两个自定义 providerId(ums.oauth.customize 与 ums.oauth.gitlabPrivate)
AuthGitlabPrivateSource: 抽象类, 实现此自定义的 AuthGitlabPrivateSource 且注入 ioc 容器的同时, 必须实现 AuthCustomizeRequest , 会自动集成进 OAuth2 Login 逻辑流程中, 只需要像 JustAuth 默认实现的第三方登录一样, 配置相应的属性(ums.oauth.gitlabPrivate.[clientId|clientSecret]等属性)即可.
AuthCustomizeSource: 抽象类, 实现此自定义的 AuthCustomizeSource 且注入 ioc 容器的同时, 必须实现 AuthCustomizeRequest , 会自动集成进 OAuth2 Login 逻辑流程中, 只需要像 JustAuth 默认实现的第三方登录一样, 配置相应的属性(ums.oauth.customize.[clientId|clientSecret]等属性)即可.
AuthCustomizeRequest: 抽象类, 实现此自定义的 AuthCustomizeRequest 同时, 必须实现 AuthCustomizeSource 或 AuthGitlabPrivateSource 且注入 ioc 容器, 会自动集成进 OAuth2 Login 逻辑流程中, 只需要像 JustAuth 默认实现的第三方登录一样, 配置相应的属性(ums.oauth.customize.[clientId|clientSecret]等属性)即可.
自定义用户登录成功与失败处理器时, 请继承下面两个抽象类; 方便 ums 自动注入登录功能.
BaseAuthenticationSuccessHandler: 认证成功处理器
BaseAuthenticationFailureHandler: 认证失败处理器
多租户系统(Multi-tenant)
多租户上下文存储器. 实现此接口并注入 IOC 容器后, 会自动注入 UMS 默认实现的注册/登录/授权组件, 要实现 ums 框架具有多租户功能, 必须实现此接口并注入 IOC 容器.
功能:
1. tenantIdHandle(HttpServletRequest, String)
从注册用户入口或登录用户入口提取 tenantId
及进行必要的逻辑处理(如: tenantId
存入 ThreadLocal
, 或存入 session
, 或存入 redis
缓存等).
2. getTenantId()
方便后续用户注册、登录、授权的数据处理(如:sql
添加 tenantId
的条件,注册用户添加 TENANT_tenantId
权限,根据 tenantId
获取角色的权限数据).
3. getTenantId(Authentication)
默认实现方法, 用户已登录的情况下, 获取租户 ID, 直接从 authority
中解析获取.
注意:
1. 多租户系统中, 在未登录时需要用到 tenantId 的接口, 如: UserCache.getUserFromCache(String)/UmsUserDetailsService
等接口,
可通过 getTenantId()
来获取 tenantId
. 登录用户可以通过 Authentication
来获取 tenantId
.
2. UMS 默认的登录与注册逻辑中, 都内置了 TenantContextHolder.tenantIdHandle(HttpServletRequest, String)
逻辑,
用户在实现 UserCache/UmsUserDetailsService
等接口中需要 tenantId
时, 调用 TenantContextHolder.getTenantId()
方法即可.
3. 如果自定义的注册或登录逻辑, 需要自己先调用 TenantContextHolder.tenantIdHandle(HttpServletRequest, String)
逻辑, 再在
实现 UserCache/UmsUserDetailsService
等接口中需要 tenantId
时, 调用 TenantContextHolder.getTenantId()
方法即可.
任务处理器接口(job)
JWT 接口请看 jwt-example.
一键登录(运营商)接口(one-click login)
必须实现
此接口, 根据 accessToken 从服务商获取用户手机号.功能(Features) | 模块(model) | demo模块—简单配置(Simple Configuration) | demo模块—详细配置(detail Configuration) |
---|---|---|---|
1. 基本功能 | core | basic-example | |
2. 登录路由功能 | core | basic-detail-example | |
3. session | core | session-detail-example | |
4. remember-me | core | basic-detail-example | |
5. csrf | core | basic-detail-example | |
6. anonymous | core | basic-detail-example | |
7. 验证码 | core | validate-code-example | |
8. 手机登录 | core | basic-detail-example | |
9. 第三方登录 | core | basic-detail-example | |
10. 给第三方登录时用的数据库表 user_connection 与 auth_token 添加 redis cache | core | basic-detail-example | |
11. 签到 | core | basic-detail-example | |
12. 基于 RBAC 的访问权限控制功能 | core | permission-example | |
13. 线程池配置 | core | justAuth-security-oauth2-example | |
14. 基于 SLF4J MDC 机制的日志链路追踪配置 | core | justAuth-security-oauth2-example |
注意事项(NOTE)
:如果是已存在的应用:
HttpSecurityAware
top.dcenter.security.core.api.config.HttpSecurityAware
与 spring cloud: 2020.0.0 和 spring 2.4.x 集成时, 因配置文件的加载方式发送变化, 当使用 spring.factories 加载此类时,
会有如下错误提示: Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one .
// 第一种方案: 使用 spring.factories 加载此类, 再添加下面空的 WebSecurityConfigurerAdapter 配置类,
// 阻止 spring 自动加载方式默认的 WebSecurityConfigurerAdapter 配置.
// 适合引入了 top.dcenter:ums-core-spring-boot-starter 或 top.dcenter:ums-spring-boot-starter 模块
@Configuration
public class WebSecurityAutoConfigurer extends WebSecurityConfigurerAdapter { }
// 第二种方案: 不使用 spring.factories 加载此类, 直接注册此类到 IOC 容器.
// 适合所有模块.
@Configuration
public class WebSecurityAutoConfigurer {
@Bean
public SecurityCoreAutoConfigurer securityCoreAutoConfigurer() {
return new SecurityCoreAutoConfigurer();
}
}
SMS > CUSTOMIZE > SELECTION > TRACK > SLIDER > IMAGE
// 示例
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// Auth2Jackson2Module 为此项目实现的反序列化配置
objectMapper.registerModules(new CoreJackson2Module(), new WebJackson2Module(), new Auth2Jackson2Module());
jackson2JsonRedisSerializer.setObjectMapper(om);
UserDetails
的默认实现 User
已实现反序列化器, 如果是开发者自定义的子类, 需开发者自己实现反序列化器.时序图 |
---|
csrf |
获取验证码逻辑 |
图片验证码逻辑 |
logout |
第三方授权登录 |
rememberMe |
核心配置逻辑 |
登录路由 |
session |
手机登录 |
授权逻辑时序图 |
过时:第三方绑定与解绑 |
过时:第三方授权登录 |
过时:第三方授权登录注册 |
pattern
中添加 %X{MDC_TRACE_ID}
即可.
<!-- 控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH
ss.SSS} %-5level ${PID:- } --- [%thread] %X{MDC_TRACE_ID} %logger[%L] - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 只有这个日志权限才能看,sql语句 -->
<level>DEBUG</level>
</filter>
</appender>
SLF4J MDC
机制实现日志链路追踪 id 的类型: 可通过属性 ums.mdc.type 定义:当 ums.mdc.type = CUSTOMIZE_ID 时 需要实现接口 MdcIdGeneratorMdcIdGenerator 并注入 IOC 容器.
# 基于 SLF4J MDC 机制实现日志链路追踪 id 的类型, 默认为 uuid. 当需要自定义 id 时, type = MdcIdType.CUSTOMIZE_ID, 再实现 MdcIdGenerator.getMdcId() 方法, 注入 IOC 容器即可.
ums:
mdc:
type: UUID/THREAD_ID/SESSION_ID/CUSTOMIZE_ID
MDC.getCopyOfContextMap()
方法获取 MDC context
, 子线程在执行操作前先调用MDC.setContextMap(context)
方法将父线程的 MDC context
设置到子线程中. ThreadPoolTaskExecutor 的配置请参考 ScheduleAutoConfiguration.
final Logger log = LoggerFactory.getLogger(this.getClass());
// 获取父线程 MDC 中的内容
final Map<String, String> context = MDC.getCopyOfContextMap();
final Runnable r = () -> {
log.info("testMDC");
System.out.println("...");
};
new Thread(() -> {
// 将父线程的 MDC context 设置到子线程中
MDC.setContextMap(context);
r.run();
}, "testMDC").start();