springBoot+springSecurity 数据库动态管理用户、角色、权限(二)
序:
本文使用springboot+mybatis+SpringSecurity 实现数据库动态的管理用户、角色、权限管理
本文细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义滤器,代替原有的FilterSecurityInterceptor过滤器,
并分别实现AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。
spring security的简单原理:
使用众多的拦截器对url拦截,以此来管理权限。但是这么多拦截器,笔者不可能对其一一来讲,主要讲里面核心流程的两个。
首先,权限管理离不开登陆验证的,所以登陆验证拦截器AuthenticationProcessingFilter要讲;
还有就是对访问的资源管理吧,所以资源管理拦截器AbstractSecurityInterceptor要讲;
但拦截器里面的实现需要一些组件来实现,所以就有了AuthenticationManager、accessDecisionManager等组件来支撑。
现在先大概过一遍整个流程,用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。
访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。
重要
本文设计和代码是基于 上一篇博客(请点击)
springboot+mybatis+SpringSecurity 实现用户角色数据库管理
进行修改。
本文目录:
1:数据库表设计
2:权限表的业务
3:springSecurity 配置修改
4:修改home.html 文件
5:修改HomeController.java 文件
6:测试检验
目录结构如下:
1:数据库表设计
由于本文增加了权限表所以本文的数据库表为5个分别是: 用户表、角色表、权限表、用户角色中间表、角色权限中间表
初始化数据
- 注意:Sys_permission 表的url通配符为两颗星,比如说 /user下的所有url,应该写成 /user/**;
- 权限的名字可以随意起名
insert into SYS_USER (id,username, password) values (1,'admin', 'admin');
insert into SYS_USER (id,username, password) values (2,'abel', 'abel'); insert into SYS_ROLE(id,name) values(1,'ROLE_ADMIN');
insert into SYS_ROLE(id,name) values(2,'ROLE_USER'); insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(1,1);
insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(2,2); BEGIN;
INSERT INTO `Sys_permission` VALUES ('1', 'ROLE_HOME', 'home', '/', null), ('2', 'ROLE_ADMIN', 'ABel', '/admin', null);
COMMIT; BEGIN;
INSERT INTO `Sys_permission_role` VALUES ('1', '1', '1'), ('2', '1', '2'), ('3', '2', '1');
COMMIT;
2:权限表的业务代码
2.1 java bean
Permission.java
package com.us.example.domain; /**
* Created by yangyibo on 17/1/20.
*/
public class Permission { private int id;
//权限名称
private String name; //权限描述
private String descritpion; //授权链接
private String url; //父节点id
private int pid; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getDescritpion() {
return descritpion;
} public void setDescritpion(String descritpion) {
this.descritpion = descritpion;
} public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
} public int getPid() {
return pid;
} public void setPid(int pid) {
this.pid = pid;
}
}
2.2 dao 层
在 com.us.example.dao 包下新建PermissionDao.java 文件。
PermissionDao.java
package com.us.example.dao;
import com.us.example.config.MyBatisRepository;
import com.us.example.domain.Permission;
import java.util.List; /**
* Created by yangyibo on 17/1/20.
*/
public interface PermissionDao {
public List<Permission> findAll();
public List<Permission> findByAdminUserId(int userId);
}
在src/resource/mapper目录下新建对应的mapper.xml 文件
PermissionDaoMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.us.example.dao.PermissionDao">
<select id="findAll" resultType="com.us.example.domain.Permission"> SELECT * from Sys_permission ;
</select> <select id="findByAdminUserId" parameterType="int" resultType="com.us.example.domain.Permission">
select p.*
from Sys_User u
LEFT JOIN sys_role_user sru on u.id= sru.Sys_User_id
LEFT JOIN Sys_Role r on sru.Sys_Role_id=r.id
LEFT JOIN Sys_permission_role spr on spr.role_id=r.id
LEFT JOIN Sys_permission p on p.id =spr.permission_id
where u.id=#{userId}
</select>
</mapper>
3:springSecurity 配置修改
3.1 修改 WebSecurityConfig.java
修改com.us.example.config包下的 WebSecurityConfig.java 文件如下:
package com.us.example.config; import com.us.example.service.CustomUserService;
import com.us.example.service.MyFilterSecurityInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; /**
* Created by yangyibo on 17/1/18.
*/ @Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor; @Bean
UserDetailsService customUserService(){ //注册UserDetailsService 的bean
return new CustomUserService();
} @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()); //user Details Service验证 } @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated() //任何请求,登录后可以访问
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.permitAll() //登录页面用户任意访问
.and()
.logout().permitAll(); //注销行为任意访问
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
}
3.2 修改CustomUserService
修改CustomUserService.java 内容如下:
package com.us.example.service; import com.us.example.dao.PermissionDao;
import com.us.example.dao.UserDao;
import com.us.example.domain.Permission;
import com.us.example.domain.SysRole;
import com.us.example.domain.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import java.util.ArrayList;
import java.util.List; /**
* Created by yangyibo on 17/1/18.
*/
@Service
public class CustomUserService implements UserDetailsService { //自定义UserDetailsService 接口 @Autowired
UserDao userDao;
@Autowired
PermissionDao permissionDao; public UserDetails loadUserByUsername(String username) {
SysUser user = userDao.findByUserName(username);
if (user != null) {
List<Permission> permissions = permissionDao.findByAdminUserId(user.getId());
List<GrantedAuthority> grantedAuthorities = new ArrayList <>();
for (Permission permission : permissions) {
if (permission != null && permission.getName()!=null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());
//1:此处将权限信息添加到 GrantedAuthority 对象中,在后面进行全权限验证时会使用GrantedAuthority 对象。
grantedAuthorities.add(grantedAuthority);
}
}
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
} else {
throw new UsernameNotFoundException("admin: " + username + " do not exist!");
}
} }
3.3 新增MyAccessDecisionManager
在com.us.example.service 包下新增
MyAccessDecisionManager.java 文件
package com.us.example.service; import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service; import java.util.Collection;
import java.util.Iterator; /**
* Created by yangyibo on 17/1/19.
*/
@Service
public class MyAccessDecisionManager implements AccessDecisionManager { // decide 方法是判定是否拥有权限的决策方法,
//authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
//object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
//configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(null== configAttributes || configAttributes.size() <=0) {
return;
}
ConfigAttribute c;
String needRole;
for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
c = iter.next();
needRole = c.getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
if(needRole.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("no right");
} @Override
public boolean supports(ConfigAttribute attribute) {
return true;
} @Override
public boolean supports(Class<?> clazz) {
return true;
}
}
3.4 新增 MyFilterSecurityInterceptor
在com.us.example.service 包下新增
MyFilterSecurityInterceptor.java 文件
package com.us.example.service;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service; import java.io.IOException; /**
* Created by yangyibo on 17/1/19.
*/
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { @Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource; @Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
} @Override
public void init(FilterConfig filterConfig) throws ServletException { } @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
} public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
} @Override
public void destroy() { } @Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
} @Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
3.5 新增 MyInvocationSecurityMetadataSourceService
在com.us.example.service 包下新增MyInvocationSecurityMetadataSourceService.java文件
package com.us.example.service; import com.us.example.dao.PermissionDao;
import com.us.example.domain.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest;
import java.util.*; /**
* Created by yangyibo on 17/1/19.
*/
@Service
public class MyInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource { @Autowired
private PermissionDao permissionDao; private HashMap<String, Collection<ConfigAttribute>> map =null; /**
* 加载权限表中所有权限
*/
public void loadResourceDefine(){
map = new HashMap<>();
Collection<ConfigAttribute> array;
ConfigAttribute cfg;
List<Permission> permissions = permissionDao.findAll();
for(Permission permission : permissions) {
array = new ArrayList<>();
cfg = new SecurityConfig(permission.getName());
//此处只添加了用户的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。
array.add(cfg);
//用权限的getUrl() 作为map的key,用ConfigAttribute的集合作为 value,
map.put(permission.getUrl(), array);
} } //此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
if(map ==null) loadResourceDefine();
//object 中包含用户请求的request 信息
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
AntPathRequestMatcher matcher;
String resUrl;
for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
resUrl = iter.next();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
return map.get(resUrl);
}
}
return null;
} @Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
} @Override
public boolean supports(Class<?> clazz) {
return true;
}
}
4:修改home.html 文件
修改src/resources/templates目录下 的home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8"/>
<title sec:authentication="name"></title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
<style type="text/css">
body {
padding-top: 50px;
}
.starter-template {
padding: 40px 15px;
text-align: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Spring Security演示</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a th:href="@{/}"> 首页 </a></li>
<li><a th:href="@{/admin}"> admin </a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav> <div class="container"> <div class="starter-template">
<h1 th:text="${msg.title}"></h1> <p class="bg-primary" th:text="${msg.content}"></p> <div sec:authorize="hasRole('ROLE_HOME')"> <!-- 用户类型为ROLE_ADMIN 显示 -->
<p class="bg-info" th:text="${msg.etraInfo}"></p>
</div>
<div sec:authorize="hasRole('ROLE_ADMIN')"> <!-- 用户类型为ROLE_ADMIN 显示 -->
<p class="bg-info">恭喜您,您有 ROLE_ADMIN 权限 </p>
</div> <form th:action="@{/logout}" method="post">
<input type="submit" class="btn btn-primary" value="注销"/>
</form>
</div> </div> </body>
</html>
5:修改HomeController.java 文件
package com.us.example.controller; import com.us.example.domain.Msg;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; /**
* Created by yangyibo on 17/1/18.
*/
@Controller
public class HomeController { @RequestMapping("/")
public String index(Model model){
Msg msg = new Msg("测试标题","测试内容","欢迎来到HOME页面,您拥有 ROLE_HOME 权限");
model.addAttribute("msg", msg);
return "home";
}
@RequestMapping("/admin")
@ResponseBody
public String hello(){
return "hello admin";
}
}
6.测试检验
启动访问 http://localhost:8080/ 到登录页面
- 由于数据库的配置 admin 用户拥有 访问 home和admin 页面的权限。
- abel 用户只有访问 home 的权限
- 1
- 2
- 3
使用admin 登录
点击 admin 按钮 会反回结果 “hello admin“
使用abel 用户登录 点击 点击 admin 按钮 页面会报403
源码地址:https://github.com/527515025/springBoot
参考资料:
http://www.tuicool.com/articles/jq6fuur#c-23220
http://blog.csdn.net/u012367513/article/details/38866465
springBoot+springSecurity 数据库动态管理用户、角色、权限(二)的更多相关文章
- springBoot+springSecurity 数据库动态管理用户、角色、权限
使用spring Security3的四种方法概述 那么在Spring Security3的使用中,有4种方法: 一种是全部利用配置文件,将用户.权限.资源(url)硬编码在xml文件中,已经实现过, ...
- SpringBoot学习- 10、设计用户角色权限表
SpringBoot学习足迹 前几节已经基本了解了SpringBoot框架常用的技术,其他的消息队列,定时器等技术暂时用不到,真正项目中如果基于微信系,阿里系开发的话,还要了解平台专用的技术知识,学习 ...
- [.Net MVC] 用户角色权限管理_使用CLK.AspNet.Identity
项目:后台管理平台 意义:一个完整的管理平台需要提供用户注册.登录等功能,以及认证和授权功能. 一.为何使用CLK.AspNet.Identity 首先简要说明所采取的权限控制方式.这里采用了基于角色 ...
- ASP.NET MVC+EF框架+EasyUI实现权限管理系列(21)-用户角色权限基本的实现说明
原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(21)-用户角色权限基本的实现说明 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇) (1):框 ...
- java权限管理与用户角色权限设计
java权限管理与用户角色权限设计 实现业务系统中的用户权限管理 B/S系统中的权限比C/S中的更显的重要,C/S系统因为具有特殊的客户端,所以访问用户的权限检测可以通过客户端实现或通过客户端+服务器 ...
- Asp.Net MVC+BootStrap+EF6.0实现简单的用户角色权限管理
这是本人第一次写,写的不好的地方还忘包含.写这个的主要原因是想通过这个来学习下EF的CodeFirst模式,本来也想用AngularJs来玩玩的,但是自己只会普通的绑定,对指令这些不是很熟悉,所以就基 ...
- Asp.Net MVC+BootStrap+EF6.0实现简单的用户角色权限管理10
今天把用户的菜单显示和页面的按钮显示都做好了,下面先来个效果图 接下来说下我实现的方法: 首先我在每个方法前面都加了这个属性, /// <summary> /// 表示当前Action请求 ...
- spring-boot-plus V1.4.0发布 集成用户角色权限部门管理
RBAC用户角色权限 用户角色权限部门管理核心接口介绍 Shiro权限配置
- spring security中动态更新用户的权限
在程序的执行过程中,有时有这么一种需求,需要动态的更新某些角色的权限或某些人对应的权限,当前在线的用户拥有这个角色或拥有这个权限时,在不退出系统的情况下,需要动态的改变的他所拥有的权限. 需求:张三 ...
随机推荐
- pgsql物理复制(pgsql 备库的搭建以及角色互换,提升)
结构图如下: Postgresql早在9.0版本开始支持物理复制,也称为流复制,通过从实例级复制出一个与主库一模一样的备库.流复制同步方式有同步,异步两种,如果主节点和备节点不是很忙,通常异步模式下备 ...
- Jquery复习(四)之text()、html()、val()
三个简单实用的用于 DOM 操作的 jQuery 方法: text() - 设置或返回所选元素的文本内容 html() - 设置或返回所选元素的内容(包括 HTML 标记) val() - 设置或返回 ...
- npm学习(二)之如何防止权限错误
如何防止权限错误 如果您在尝试全局安装包时看到EACCES错误,请阅读本章.如果更改安装npm的目录,通常可以避免此错误.要做到这一点,要么使用版本管理器重新安装npm(推荐)或手动更改npm的默认目 ...
- 安装linux mint后要做20件事
Linux Mint 17 Qiana Cinnamon Linux Mint 17已经发布,定名为Qiana.Mint是Linux最佳发行版之一,它定位于桌面用户,关注可用性和简洁.它携带了风格迥异 ...
- GDAL支持中文路径和Shp文件中文属性写入
在使用GDAL的过程中,为了支持中文,比需手动进行中文路径的设置,同时特别是在对Shp的属性进行中文输入的时候,都必须进行必要的设定. 为了支持中文路径,在注册了驱动之后,加上第三句就可以了.必须设置 ...
- install - 复制文件并设置属性
SYNOPSIS[总览] install [options] [-s] [--strip] source dest install [options] [-s] [--strip] source... ...
- jumpserver部署1.0版本
A. jumpserver概述 跳板机概述: 跳板机就是一台服务器,开发或运维人员在维护过程中首先要统一登录到这台服务器,然后再登录到目标设备进行维护和操作: 跳板机缺点:没有实现对运维人员操作行为的 ...
- Node.js之querystring模块
querystring从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析.querystring模块只提供4个方法,在我看来,这4个方法是相对应的. 这4个方法分别是querystr ...
- 对ECMAScript的研究-----------引用
ECMAScript 新特性与标准提案 一:ES 模块 第一个要介绍的 ES 模块,由于历史上 JavaScript 没有提供模块系统,在远古时期我们常用多个 script 标签将代码进行人工隔离.但 ...
- Linux基础教程 linux无密码ssh登录设置
概述 在一些常用设备之间ssh, scp,不用输入密码可以节省不少时间. 生成密钥 先看本地是否有密钥,如果有,则不用生成,否则会影响到以前打通的设备. 复制代码代码如下: 没有则用 ssh-ke ...