diff --git a/spring-boot-demo-rbac-security/README.md b/spring-boot-demo-rbac-security/README.md
new file mode 100644
index 0000000..89f66a0
--- /dev/null
+++ b/spring-boot-demo-rbac-security/README.md
@@ -0,0 +1,566 @@
+# spring-boot-demo-rbac-security
+
+> 此 demo 主要演示了 Spring Boot 项目如何集成 Spring Security 完成权限拦截操作。本 demo 为基于**前后端分离**的后端权限管理部分,不同于其他博客里使用的模板技术。
+
+## 1. 主要功能
+
+- [x] 基于 `RBAC` 权限模型设计,详情参考数据库表结构设计 [`security.sql`](./sql/security.sql)
+- [x] 支持**动态权限管理**,详情参考 [`RbacAuthorityService.java`](./src/main/java/com/xkcoding/rbac/security/config/RbacAuthorityService.java)
+- [x] **登录 / 登出**部分均使用自定义 Controller 实现,未使用 `Spring Security` 内部实现部分,适用于前后端分离项目,详情参考 [`SecurityConfig.java`](./src/main/java/com/xkcoding/rbac/security/config/SecurityConfig.java) 和 [`AuthController.java`](./src/main/java/com/xkcoding/rbac/security/config/AuthController.java)
+- [x] 持久化技术使用 `spring-data-jpa` 完成
+- [x] 使用 `JWT` 实现安全验证,同时引入 `Redis` 解决 `JWT` 无法手动设置过期的弊端,并且保证同一用户在同一时间仅支持同一设备登录,不同设备登录会将,详情参考 [`JwtUtil.java`](./src/main/java/com/xkcoding/rbac/security/config/JwtUtil.java)
+- [ ] 在线人数统计
+- [ ] 手动踢出用户
+
+## 2. 运行
+
+### 2.1. 环境
+
+1. JDK 1.8 以上
+2. Maven 3.5 以上
+3. Mysql 5.7 以上
+4. Redis
+
+### 2.2. 运行方式
+
+1. 新建一个名为 `spring-boot-demo` 的数据库,字符集设置为 `utf-8`,如果数据库名不是 `spring-boot-demo` 需要在 `application.yml` 中修改 `spring.datasource.url`
+2. 使用 [`security.sql`](./sql/security.sql) 这个 SQL 文件,创建数据库表和初始化RBAC数据
+3. 运行 `SpringBootDemoRbacSecurityApplication`
+4. enjoy ~ :kissing_smiling_eyes:
+
+## 3. 部分关键代码
+
+### 3.1. pom.xml
+
+```xml
+
+
+ * JWT 工具类 + *
+ * + * @package: com.xkcoding.rbac.security.util + * @description: JWT 工具类 + * @author: yangkai.shen + * @date: Created in 2018-12-07 13:42 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@EnableConfigurationProperties(JwtConfig.class) +@Configuration +@Slf4j +public class JwtUtil { + @Autowired + private JwtConfig jwtConfig; + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + /** + * 创建JWT + * + * @param rememberMe 记住我 + * @param id 用户id + * @param subject 用户名 + * @param roles 用户角色 + * @param authorities 用户权限 + * @return JWT + */ + public String createJWT(Boolean rememberMe, Long id, String subject, List+ * Security 配置 + *
+ * + * @package: com.xkcoding.rbac.security.config + * @description: Security 配置 + * @author: yangkai.shen + * @date: Created in 2018-12-07 16:46 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + @Autowired + private AccessDeniedHandler accessDeniedHandler; + + @Autowired + private CustomUserDetailsService customUserDetailsService; + + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @Bean + public BCryptPasswordEncoder encoder() { + return new BCryptPasswordEncoder(); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(customUserDetailsService) + .passwordEncoder(encoder()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.cors() + + // 关闭 CSRF + .and() + .csrf() + .disable() + + // 登录行为由自己实现,参考 AuthController#login + .formLogin() + .disable() + .httpBasic() + .disable() + + // 认证请求 + .authorizeRequests() + // 放行 /api/auth/** 的所有请求,参见 AuthController + .antMatchers("/**/api/auth/**") + .permitAll() + .anyRequest() + .authenticated() + // RBAC 动态 url 认证 + .anyRequest() + .access("@rbacAuthorityService.hasPermission(request,authentication)") + + // 登出行为由自己实现,参考 AuthController#logout + .and() + .logout().disable() + + // Session 管理 + .sessionManagement() + // 因为使用了JWT,所以这里不管理Session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + // 异常处理 + .and() + .exceptionHandling() + .accessDeniedHandler(accessDeniedHandler); + + // 添加自定义 JWT 过滤器 + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + } +} +``` + +### 3.4. RbacAuthorityService.java + +> 路由动态鉴权类,主要功能:根据当前请求路径与该用户可访问的资源做匹配,通过则可以访问,否则,不允许访问 + +```java +/** + *+ * 动态路由认证 + *
+ * + * @package: com.xkcoding.rbac.security.config + * @description: 动态路由认证 + * @author: yangkai.shen + * @date: Created in 2018-12-10 17:17 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Component +public class RbacAuthorityService { + @Autowired + private RoleDao roleDao; + + @Autowired + private PermissionDao permissionDao; + + public boolean hasPermission(HttpServletRequest request, Authentication authentication) { + Object userInfo = authentication.getPrincipal(); + boolean hasPermission = false; + + if (userInfo instanceof UserDetails) { + UserPrincipal principal = (UserPrincipal) userInfo; + Long userId = principal.getId(); + + List+ * Jwt 认证过滤器 + *
+ * + * @package: com.xkcoding.rbac.security.config + * @description: Jwt 认证过滤器 + * @author: yangkai.shen + * @date: Created in 2018-12-10 15:15 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Component +@Slf4j +public class JwtAuthenticationFilter extends OncePerRequestFilter { + @Autowired + private CustomUserDetailsService customUserDetailsService; + + @Autowired + private JwtUtil jwtUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + if (antPathMatcher.match("/**/api/auth/**", request.getRequestURI())) { + filterChain.doFilter(request, response); + } else { + String jwt = jwtUtil.getJwtFromRequest(request); + + if (StrUtil.isNotBlank(jwt)) { + try { + String username = jwtUtil.getUsernameFromJWT(jwt); + + UserDetails userDetails = customUserDetailsService.loadUserByUsername(username); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext() + .setAuthentication(authentication); + filterChain.doFilter(request, response); + } catch (SecurityException e) { + ResponseUtil.renderJson(response, e); + } + } else { + ResponseUtil.renderJson(response, Status.UNAUTHORIZED, null); + } + } + } + +} +``` + +### 3.6. CustomUserDetailsService.java + +> 实现 `UserDetailsService` 接口,主要功能:根据用户名查询用户信息 + +```java +/** + *+ * 自定义UserDetails查询 + *
+ * + * @package: com.xkcoding.rbac.security.service + * @description: 自定义UserDetails查询 + * @author: yangkai.shen + * @date: Created in 2018-12-10 10:29 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Service +public class CustomUserDetailsService implements UserDetailsService { + @Autowired + private UserDao userDao; + + @Autowired + private RoleDao roleDao; + + @Autowired + private PermissionDao permissionDao; + + @Override + public UserDetails loadUserByUsername(String usernameOrEmailOrPhone) throws UsernameNotFoundException { + User user = userDao.findByUsernameOrEmailOrPhone(usernameOrEmailOrPhone, usernameOrEmailOrPhone, usernameOrEmailOrPhone) + .orElseThrow(() -> new UsernameNotFoundException("未找到用户信息 : " + usernameOrEmailOrPhone)); + List