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), Support multi-tenancy, jwt , validate code(image, sms, sliderCode), RBAC, SLF4J-MDC, signed etc...
一、UMS feature list:
 
- validate code(image, SMS, slider) verification function.
- Mobile login function, automatic registration after login, Support multi-tenancy.
- Support all third-party authorized logins supported by JustAuth, after login, automatically register or bind or create temporary users(TemporaryUser). 
  - Support timing refresh accessToken, support distributed timing tasks.
- Support the caching function of user table and token table by OAuth2 login.
- Support third-party binding and unbinding and query interfaces.
 
- Access control function, support multi tenancy.
- Simplify session、remember me、csrf cors etc configuration.
- Return json or html data according to the set response method (JSON and REDIRECT).
- signed function.
- Support log link tracking function based on SLF4J MDC mechanism.
- JWT creation, verification, refresh, concurrent access problems caused by jwt invalid, and blacklist functions.
Module function
| 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, 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 | demo function | 
|---|---|
| basic-example | Basic function: the simplest configuration | 
| 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 | 
微信群:UMS 添加微信(z56133)备注(UMS)
二、maven:
 
<dependency>
    <groupId>top.dcenter</groupId>
    <artifactId>ums-spring-boot-starter</artifactId>
    <version>[2.2.0,)</version>
</dependency> 
三、TODO List:
 
- 1. microservices, OAuth2 authenticate server
四、Quick Start:
 
- example: quickStart
- Gitee | Github
五、Interface instructions:
The interface that needs to be implemented when the corresponding function is present:
-  user service: Must implemente
-  RBAC-based access control: Support multi-tenancy -  UriAuthorizeService: 推荐通过实现 AbstractUriAuthorizeService 来实现此接口
-  AbstractUriAuthorizeService: 必须实现(Must implemente)-  uri(资源) 访问权限控制服务接口抽象类, 定义了基于(角色/多租户/SCOPE)的访问权限控制逻辑. 实现 AbstractUriAuthorizeService 抽象类并注入 IOC 容器即可替换 DefaultUriAuthorizeService. 
-  注意: 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: -  用于更新并缓存基于(角色/多租户/SCOPE)角色的权限的服务接口, 每次更新角色的 uri(资源)权限时,需要调用此接口, 推荐实现此 RolePermissionsService 接口, 会自动通过 AOP 方式实现发布 UpdateRolesResourcesEvent 事件, 从而调用 UpdateCacheOfRolesResourcesService 对应的方法. 
-  建议: 1. 基于 角色 的权限控制: 实现所有角色 uri(资源) 的权限 Map(role, map(uri, Set(permission))) 的更新与缓存本机内存. 2. 基于 SCOPE 的权限控制: 情况复杂一点, 但 SCOPE 类型比较少, 也还可以像 1 的方式实现缓存本机内存与更新. 3. 基于 多租户 的权限控制: 情况比较复杂, 租户很少的情况下, 也还可以全部缓存在本机内存, 通常情况下全部缓存内存不现实, 只能借助于类似 redis 等的内存缓存. 
 
-  
-   
    -  更新与查询基于(角色/多租户/SCOPE)的角色资源服务接口. 主要用于给角色添加权限的操作. 
-  注意: 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.
 
-  
-  时序图 
 
-  
-  短信验证码(SMS validate code): 默认空实现
-  图片验证码(image validate code): 已实现缓存功能, 支持定时刷新缓存功能, 可以自定义缓存验证码图片的输出路径与缓存数量 
-  滑块验证码(Slider validate code): 已实现缓存功能, 支持定时刷新缓存功能, 可以自定义缓存验证码图片的输出路径与缓存数量, 支持自定义源图片路径与模板图片路径(源图片与模板图片参考validate-code-example) 
-  自定义验证码(customize validate code): 
-  Auth2StateCoder: 用户需要时实现, 对第三方授权登录流程中的 state 进行自定义编解码. 可以传递必要的信息, 如: 第三方登录成功的跳转地址等 注意此接口的两个方法必须同时实现对应的编解码逻辑, 实现此接口后注入 IOC 容器即可, 如有前端向后端获取 authorizeUrl 时向后端传递额外参数 且用作注册时的信息, 需配合 UmsUserDetailsService.registerUser(AuthUser, String, String, String) 方法实现.
-  Auth2UserService: 获取第三方用户信息的接口, 一般不需要用户实现, 除非想自定义获取第三方用户信息的逻辑, 实现此接口注入 IOC 容器即可替代. 
-  UsersConnectionRepository: 第三方授权登录的第三方用户信息增删改查, 绑定与解绑及查询是否绑定与解绑接口, 一般不需要用户实现. 除非想自定义获取第三方用户信息的逻辑, 实现此接口注入 IOC 容器即可替代. 
-  UsersConnectionTokenRepository: 第三方授权登录用户 accessToken 信息表增删改查接口, 一般不需要用户实现. 除非想自定义获取第三方用户信息的逻辑, 实现此接口注入 IOC 容器即可替代. 
-  ConnectionService: 第三方授权登录用户的注册, 绑定, 更新第三方用户信息与 accessToken 信息的接口, 一般不需要用户实现. 除非想自定义获取第三方用户信息的逻辑, 实现此接口注入 IOC 容器即可替代. - 注意: 要替换内置 auth_token与user_connection表的实现逻辑, 实现此接口注入 IOC 容器即可, 且设置属性ums.repository. enableStartUpInitializeTable = 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]等属性)即可. 
 
-  
-  自定义前后端认证 token 以及通信方式 -  BaseAuthenticationSuccessHandler: 认证成功处理器 
-  BaseAuthenticationFailureHandler: 认证失败处理器 
 
-  
-  如果应用为多租户系统, 且使用 RememberMe 功能: 默认的 RememberMeServices 不支持多租户, 则需要自定义实现 org.springframework.security.web .authentication.RememberMeServices接口, 使其支持多租户系统, 不过一般情况下多租户系统不使用 RememberMe 功能.
-   
  -  多租户上下文存储器. 实现此接口并注入 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()方法即可.
 
-  
-   
  - 任务处理器接口, 继承此接口并注入 IOC 容器, top.dcenter.ums.security.core.tasks.config.ScheduleAutoConfiguration 会自动注册到 ScheduledTaskRegistrar 中.
 
-  JWT 接口请看 jwt-example. 
六、Configurations:
| 功能(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):
 
1. HttpSecurity 配置问题:UMS 中的 HttpSecurityAware 配置与应用中的 HttpSecurity 配置冲突问题:
-  如果是新建应用添加 HttpSecurity 配置, 通过下面的接口即可: 
-  如果是已存在的应用: - 添加 HttpSecurity 配置, 通过下面的接口即可: HttpSecurityAware
- 已有的 HttpSecurity 配置, 让原有的 HttpSecurity 配置实现此接口进行配置: top.dcenter.security.core.api.config.HttpSecurityAware
 
- 添加 HttpSecurity 配置, 通过下面的接口即可: 
-  与 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();
       }
   } 
2. 在 ServletContext 中存储的属性:
- 属性名称: SecurityConstants.SERVLET_CONTEXT_PERMIT_ALL_SET_KEY
- 属性值: Set, 把权限类型为 PERMIT_ALL 的 Set 存储在 servletContext .
4. 验证码优先级(Verification code Priority):
- 同一个 uri 由多种验证码同时配置, 优先级如下: SMS > CUSTOMIZE > SELECTION > TRACK > SLIDER > IMAGE
5. Jackson 序列化与反序列化
- 添加一些 Authentication 与 UserDetails 子类的反序列化器, 以解决 redis 缓存不能反序列化此类型的问题, 具体配置 redis 反序列器的配置请看 RedisCacheAutoConfiguration.getJackson2JsonRedisSerializer() 方法.
// 示例
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// Auth2Jackson2Module 为此项目实现的反序列化配置     
objectMapper.registerModules(new CoreJackson2Module(), new WebJackson2Module(), new Auth2Jackson2Module());
jackson2JsonRedisSerializer.setObjectMapper(om); 
- 注意: UmsUserDetailsService 的注册用户方法返回的 UserDetails的默认实现User已实现反序列化器, 如果是开发者自定义的子类, 需开发者自己实现反序列化器.
八、Properties Configurations
九、参与贡献(Participate in contribution)
- Fork 本项目
- 新建 Feat_xxx 分支
- 提交代码
- 新建 Pull Request
十、Flow chart: 随着版本迭代会有出入
Login Flow chart
JWT Flow chart
Slider verification code Flow chart
十一、时序图(Sequence Diagram): 随着版本迭代会有出入
| 时序图 | 
|---|
| csrf | 
| 获取验证码逻辑 | 
| 图片验证码逻辑 | 
| logout | 
| 第三方授权登录 | 
| rememberMe | 
| 核心配置逻辑 | 
| 登录路由 | 
| session | 
| 手机登录 | 
| 授权逻辑时序图 | 
| 过时:第三方绑定与解绑 | 
| 过时:第三方授权登录 | 
| 过时:第三方授权登录注册 | 
十二、基于 SLF4J MDC 机制的日志链路追踪功能
- 使用此功能在日志配置文件中的 pattern中添加%X{MDC_TRACE_ID}即可.
<!-- 控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- 日志格式 -->
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm: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 定义:
# 基于 SLF4J MDC 机制实现日志链路追踪 id 的类型, 默认为 uuid. 当需要自定义 id 时, type = MdcIdType.CUSTOMIZE_ID, 再实现 MdcIdGenerator.getMdcId() 方法, 注入 IOC 容器即可.
ums:
  mdc:
    type: UUID/THREAD_ID/SESSION_ID/CUSTOMIZE_ID 
当 ums.mdc.type = CUSTOMIZE_ID 时 需要实现接口 MdcIdGeneratorMdcIdGenerator 并注入 IOC 容器.
- 多线程使用问题: 父线程新建子线程之前调用 MDC.getCopyOfContextMap()方法获取MDC context, 子线程在执行操作前先调用MDC.setContextMap(context)方法将父线程的MDC context设置到子线程中. ThreadPoolTaskExecutor 的配置请参考 ScheduleAutoConfiguration.
- 多线程传递 MDC context 简单示例:
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(); 
 JarCasting
 JarCasting







