2.职位权限设置
2.1 权限crud
2.1.1 编写dao
编写权限表的pojo
public class Privilege implements Serializable {
private static final long serialVersionUID = 7258292710314981583L;
private Long id;
private String name;
private String uri;
public Privilege() {
}
public Privilege(Long id, String name, String uri) {
this.id = id;
this.name = name;
this.uri = uri;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
@Override
public String toString() {
return "Privilege{" +
"id=" + id +
", name='" + name + '\'' +
", uri='" + uri + '\'' +
'}';
}
}
编写dao接口
public interface PrivilegeDao {
/**
* 查询所有权限
* @return
*/
List<Privilege> findAllPrivilege();
/**
* 查询一个职位所拥有的权限
* @param id
* @return
*/
List<Privilege> findPrivilegeByJobId(Long id);
/**
* 根据员工电话查询一个员工所拥有的权限
* @param phone
* @return
*/
List<Privilege> findPrivilegeByEmployeePhone(String phone);
编写到实现
public class PrivilegeDaoImpl implements PrivilegeDao {
@Override
public List<Privilege> findAllPrivilege() {
String sql="select id,name,uri from privilege";
List<Privilege> privileges = (List<Privilege>) JdbcTemplete.query(sql, new BeanListHandler(Privilege.class));
return privileges;
}
@Override
public List<Privilege> findPrivilegeByJobId(Long id) {
String sql="select p.id,p.name,p.uri from privilege p,job_privilege jp where p.id=jp.privilege_id and jp.job_id=?";
List<Privilege> privileges = (List<Privilege>) JdbcTemplete.query(sql, new BeanListHandler(Privilege.class),id);
return privileges;
}
@Override
public List<Privilege> findPrivilegeByEmployeePhone(String phone) {
String sql="select p.id,p.name,p.uri from employee e,job_privilege jp,privilege p where e.job_id=jp.job_id and jp.privilege_id=p.id and e.phone=?";
List<Privilege> privileges = (List<Privilege>) JdbcTemplete.query(sql, new BeanListHandler(Privilege.class),phone);
return privileges;
}
}
2.1.2 编写service
public interface PrivilegeService {
/**
* 查询所有权限
* @return
*/
List<Privilege> findAllPrivilege();
/**
* 查询一个职位所拥有的权限
* @param id
* @return
*/
List<Privilege> findPrivilegeByJobId(Long id);
/**
* 根据员工id查询一个员工所拥有的权限
* @param phone
* @return
*/
List<Privilege> findPrivilegeByEmployeePhone(String phone);
编写实现
public class PrivilegeServiceImpl implements PrivilegeService {
private PrivilegeDao privilegeDao=new PrivilegeDaoImpl();
@Override
public List<Privilege> findAllPrivilege() {
return privilegeDao.findAllPrivilege();
}
@Override
public List<Privilege> findPrivilegeByJobId(Long id) {
return privilegeDao.findPrivilegeByJobId(id);
}
@Override
public List<Privilege> findPrivilegeByEmployeePhone(String phone) {
return privilegeDao.findPrivilegeByEmployeePhone(phone);
}
}
2.1.3 编写servlet
@WebServlet(name = "PrivilegeController",urlPatterns = "/privilege")
public class PrivilegeController extends BaseServlet {
//初始化service
PrivilegeService privilegeService=new PrivilegeServiceImpl();
public Result list(HttpServletRequest req, HttpServletResponse resp) {
List<Privilege> privileges = privilegeService.findAllPrivilege();
return ResultUtils.buildSuccess(privileges);
}
public Result find(HttpServletRequest req, HttpServletResponse resp) {
String jobId=req.getParameter("jobId");
List<Privilege> privileges = privilegeService.findPrivilegeByJobId(Long.valueOf(jobId));
return ResultUtils.buildSuccess(privileges);
}
}
2.2 添加职位
2.2.1 修改dao
//1,2,3
@Override
public void insertJob(Job job,List<Long> privilegeIds) {
//1.插入职位基本数据
String sql="insert into job(name,description,created)"//
+"values(?,?,?)";
Integer jobId= JdbcTemplete.insert(sql,job.getName(),job.getDescription(),job.getCreated());
System.out.println("-------------------------"+jobId+"---------------------------");
//2.在中间表插入职位的拥有的权限数据
if(privilegeIds!=null){
for(Long privilegeId:privilegeIds){
sql="insert into job_privilege(job_id,privilege_id) values(?,?)";
JdbcTemplete.update(sql,jobId,privilegeId);
}
}
}
2.2.2 修改service
接口
public void insertJob(Job job, List<Long> privilegeIds);
实现类
public void insertJob(Job job,List<Long> privilegeIds) {
job.setCreated(new Date());
jobDao.insertJob(job, privilegeIds);
}
2.2.3 修改servlet
public Result add(HttpServletRequest req, HttpServletResponse resp) {
Job job = WebUtils.requestToBean(req, Job.class);
//校验
if(!ValidatePojo.validate(job)){
return ResultUtils.buildFail(500000,ValidatePojo.msg);
}
> //String 1,2,3
> //解析权限id
> String privilege= req.getParameter("privilegeIds");
> List<Long> ids = null;
> if(privilege!=null&&!privilege.trim().equals("")){
> String[] privilegeIds = privilege.split(",");
> ids = new ArrayList<>();
> for (String privilegeId : privilegeIds) {
> ids.add(Long.valueOf(privilegeId));
> }
> }
//2.调用service处理业务 传递javabean
jobService.insertJob(job,ids);
//3.反馈前端处理结果
return ResultUtils.buildSuccess();
}
2.2.4 接口测试
2.2.5 添加职位页面的权限复选框
页面添加html
<div class="layui-form-item">
<label class="layui-form-label">权限操作</label>
<div id="privilegeCheckbox" class="layui-input-block">
</div>
</div>
添加js
$.ajax({
type: "get",
dataType:"json",
url: "http://www.j236.com/crm/privilege?method=list",
success: function(res){
var data=res.data;
$.each(data,function(index,privilege){
$("#privilegeCheckbox").append("<input type=\"checkbox\" name=\"privilegeIds\" title=\""+privilege.name+"\" value=\""+privilege.id+"\">");
});
//重新渲染权限
layui.form.render("checkbox");
}
});
2.2.6 修改添加职位js
//监听提交
form.on('submit(add)', function (data) {
//获取表单提交的数据
let filed=data.field;
//获取KindEditor的内容
editor.sync();
filed.description=$("#editor_id").val();
//获取多选框的id装进数组
var quotation = new Array();
//获取被选中的多选框
$("input:checkbox[name='privilegeIds']:checked").each(function(){
//把被选中的多选框进行遍历,取出每一个选中权限的id,放入数组中
quotation.push($(this).val());
});
var str = ""; //1,2,3,
for (var i = 0; i < quotation.length; i++) {
str=str+quotation[i]+",";
}
//去掉最后一个逗号
str=str.substring(0,str.length-1);
//覆盖复选框的数据
data.field.privilegeIds = str ;
$.ajax({
type: "POST",
url: "http://www.j236.com/crm/job?method=add",
data: filed ,
success: function(obj){
if(obj.code==0){
layer.alert("添加成功!")
//3秒过后关闭
setTimeout(function () {
var iframeIndex = parent.layer.getFrameIndex(window.name);
parent.layer.close(iframeIndex);
//修改成功后刷新父界面
window.parent.location.reload()
},3000);
}else{
layer.alert(obj.msg)
}
}
});
return false;
});
});
2.3 修改职位
2.3.1 修改dao
需求:
编写接口
/**
* 修改职位
* @param job 职位基本信息
* @param privilegeIds 需要修改的权限id集合
*/
public void updateJob(Job job,List<Long> privilegeIds);
编写实现
@Override
public void updateJob(Job job,List<Long> privilegeIds) {
String sql="update job set name=?,description=? where id=?";
JdbcTemplete.update(sql,job.getName(),job.getDescription(),job.getId());
//首先删除该职位所有的权限
sql="delete from job_privilege where job_id=?";
JdbcTemplete.update(sql,job.getId());
//2.在中间表插入职位的拥有的权限数据
if(privilegeIds!=null){
//更新权限
for(Long privilegeId:privilegeIds){
sql="insert into job_privilege(job_id,privilege_id) values(?,?)";
JdbcTemplete.update(sql,job.getId(),privilegeId);
}
}
}
2.3.2 修改service
编写接口
public void updateJob(Job job,List<Long> privilegeIds);
编写实现
@Override
public void updateJob(Job job,List<Long> privilegeIds) {
jobDao.updateJob(job,privilegeIds);
}
2.3.3 修改servlet
public Result update(HttpServletRequest req, HttpServletResponse resp) {
//1.接收页面的数据,把数据封装到javabean
Job job = WebUtils.requestToBean(req, Job.class);
> //String 1,2,3
> //解析权限id
> String privilege= req.getParameter("privilegeIds");
> List<Long> ids = null;
> if(privilege!=null&&!privilege.trim().equals("")){
> String[] privilegeIds = privilege.split(",");
> ids = new ArrayList<>();
> for (String privilegeId : privilegeIds) {
> ids.add(Long.valueOf(privilegeId));
> }
> }
//2.调用service处理业务 传递javabean
jobService.updateJob(job,ids);
//3.反馈前端处理结果
return ResultUtils.buildSuccess();
}
2.3.4 接口测试
2.3.5 前端回显职位信息
页面添加
<div class="layui-form-item">
<label class="layui-form-label">权限操作</label>
<div id="privilegeCheckbox" class="layui-input-block">
</div>
</div>
js添加回显操作
$.ajax({
type: "get",
dataType:"json",
url: "http://www.j236.com/crm/privilege?method=list",
success: function(res){
var data=res.data;
$.each(data,function(index,privilege){
$("#privilegeCheckbox").append("<input type=\"checkbox\" name=\"privilegeIds\" title=\""+privilege.name+"\" value=\""+privilege.id+"\">");
});
//重新渲染权限
layui.form.render("checkbox");
}
});
//回显复选框
$.ajax({
type: "get",
dataType:"json",
url: "http://www.j236.com/crm/privilege?method=find&jobId="+id,
success: function(res){
var data=res.data;
$.each(data,function(index,privilege){
//1 2
//添加选中属性 查找1,2对应的多选框,选中操作
$("#privilegeCheckbox").find("input:checkbox[value='"+privilege.id+"']").attr("checked",true);
});
//重新渲染权限
layui.form.render("checkbox");
}
});
1.6.3 基于url拦截
基于url拦截是企业中常用的权限管理方法,实现思路是:将系统操作的每个url配置在权限表中,将权限对应到角色,将角色分配给用户,用户访问系统功能通过Filter进行过虑,过虑器获取到用户访问的url,只要访问的url是用户分配角色中的url则放行继续访问。
根据用户的电话号码查询一个用户拥有的权限
编写过滤器
@WebFilter(filterName="PermissionFilter",value = "/*")
public class PermissionFilter implements Filter {
private PrivilegeService privilegeService=new PrivilegeServiceImpl();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request= (HttpServletRequest) req;
HttpServletResponse response= (HttpServletResponse) res;
//登录相关接口放行
//获取客户端请求的资源路径
String uri = request.getRequestURI();
System.out.println(uri+"-------------");
//不拦截的资源
if(uri.contains("/crm/login")){
//直接访问 放行
chain.doFilter(request,response);
return;
}
//必须要认证的资源
//先从请求参数获取token
String token=request.getParameter("token");
if(token==null||token.trim().equals("")){
token=request.getHeader("token");
if(token==null||token.trim().equals("")) {
Result result = new Result(50003, "用户没有登录,不允许访问!", null, null);
ResponseUtils.responseJson(JsonUtils.objectToJson(result), response);
return;
}
}
//校验jwt令牌是否合法,是否过期
if(!TokenUtils.verify(token)){
//token不合法
Result result = new Result(50003, "用户没有登录,不允许访问!", null, null);
ResponseUtils.responseJson(JsonUtils.objectToJson(result), response);
return;
}
//后面在servlet的业务代码中需要获取当前用户的登录信息?
//解密载荷的信息
DecodedJWT jwt = JWT.decode(token);
Map<String, Claim> claims = jwt.getClaims();//获取载荷信息 用户登录的信息
//绑定到当前线程
UserThreadLocal.setUser(claims);
//权限代码
> //权限鉴定
> //获取用户访问的uri
>
> //判断该uri是否需要拦截 准备一个集合 配置一些不需要拦截的uri
>
> //判断当前访问的uri是否在用户的权限中 如果在 放行 不再就拦截
>
> //查询用户对应的权限
> String phone=claims.get("phone").asString();
> List<Privilege> privilegeList = privilegeService.findPrivilegeByEmployeePhone(phone);
>
> //默认没有通过
> boolean isOk=false;
>
> if(privilegeList!=null){
>
> for(Privilege privilege:privilegeList){
>
> if (uri.contains(privilege.getUri())) {
> isOk=true;
> break;
> }
>
> }
>
> }
>
> if(isOk==false){
> //权限鉴定没有通过
> Result result = new Result(50003, "用户没有对应的权限,不允许访问!", null, null);
> ResponseUtils.responseJson(JsonUtils.objectToJson(result), response);
> return;
> }
//放行
chain.doFilter(request,response);
//移除当前线程绑定的用户信息
UserThreadLocal.removeUser();
}
@Override
public void destroy() {
}
}
1.1 什么是权限管理
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
1.2 用户身份认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
关键对象
Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
1.3 授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
授权可简单理解为who对what(which)进行How操作:
Who,即主体(Subject),主体需要访问系统中的资源。
What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为01的商品为资源实例,编号为001的商品信息也属于资源实例。
How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
权限分为粗颗粒和细颗粒,粗颗粒权限是指对资源类型的权限,细颗粒权限是对资源实例的权限。
wyy具有对商品列表的操作权限 粗粒度的权限
wyy具有对手机商品列表操作的权限
主体、资源、权限关系如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-THtiLI5Z-1607347915987)(day58.assets/wps3.png)]
1.4 权限模型
主体(账号、密码)
资源(资源名称、访问地址)
权限(权限名称、资源id)
角色(角色名称)
角色和权限关系(角色id、权限id)
主体和角色关系(主体id、角色id)
通常企业开发中将资源和权限表合并为一张权限表,如下:
资源(资源名称、访问地址)
权限(权限名称、资源id)
合并为:
权限(权限名称、资源名称、资源访问地址)
上图常被称为权限管理的通用模型,不过企业在开发中根据系统自身的特点还会对上图进行修改,但是用户、角色、权限、用户角色关系、角色权限关系是需要去理解的。
1.5 权限分配和控制
1.5.1 权限分配
对主体分配权限,主体只允许在权限范围内对资源进行操作,比如:对u01用户分配商品修改权限,u01用户只能对商品进行修改。
权限分配的数据通常需要持久化,根据上边的数据模型创建表并将用户的权限信息存储在数据库中。
用户拥有了权限即可操作权限范围内的资源,系统不知道主体是否具有访问权限需要对用户的访问进行控制。
1.5.2 角色的访问控制
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
上图中的判断逻辑代码可以理解为:
if(主体.hasRole(“总经理角色id”)){
查询工资
}
缺点:以角色进行访问控制粒度较粗,如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断主体的角色是否是总经理或部门经理”,系统可扩展性差。
修改代码如下:
if(主体.hasRole(“总经理角色id”) || 主体.hasRole(“部门经理角色id”)){
查询工资
}
1.5.3 基于资源的访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制,比如:主体必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:
上图中的判断逻辑代码可以理解为:
if(主体.hasPermission(“查询工资权限标识”)){
查询工资
}
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也只需要将“查询工资信息权限”添加到“部门经理角色”的权限列表中,判断逻辑不用修改,系统可扩展性强。
1.6 权限解决方案
1.6.1 什么是组细粒度权限
对资源类型的管理称为粗颗粒度权限管理,即只控制到菜单、按钮、方法,粗粒度的例子比如:用户具有用户管理的权限,具有导出订单明细的权限。对资源实例的控制称为细颗粒度权限管理,即控制到数据级别的权限,比如:用户只允许修改本部门的员工信息,用户只允许导出自己创建的订单明细。
1.6.2 如何实现粗颗粒度和细颗粒度
对于粗颗粒度的权限管理可以很容易做系统架构级别的功能,即系统功能操作使用统一的粗颗粒度的权限管理。
对于细颗粒度的权限管理不建议做成系统架构级别的功能,因为对数据级别的控制是系统的业务需求,随着业务需求的变更业务功能变化的可能性很大,建议对数据级别的权限控制在业务层个性化开发,比如:用户只允许修改自己创建的商品信息可以在service接口添加校验实现,service接口需要传入当前操作人的标识,与商品信息创建人标识对比,不一致则不允许修改商品信息。
1.6.3 基于url拦截
基于url拦截是企业中常用的权限管理方法,实现思路是:将系统操作的每个url配置在权限表中,将权限对应到角色,将角色分配给用户,用户访问系统功能通过Filter进行过虑,过虑器获取到用户访问的url,只要访问的url是用户分配角色中的url则放行继续访问。
根据用户的电话号码查询一个用户拥有的权限
@WebFilter(filterName="PermissionFilter",value = "/*")
public class PermissionFilter implements Filter {
private PrivilegeService privilegeService=new PrivilegeServiceImpl();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request= (HttpServletRequest) req;
HttpServletResponse response= (HttpServletResponse) res;
//登录相关接口放行
//获取客户端请求的资源路径
String uri = request.getRequestURI();
System.out.println(uri+"-------------");
//不拦截的资源
if(uri.contains("/crm/login")){
//直接访问 放行
chain.doFilter(request,response);
return;
}
//必须要认证的资源
//先从请求参数获取token
String token=request.getParameter("token");
if(token==null||token.trim().equals("")){
token=request.getHeader("token");
if(token==null||token.trim().equals("")) {
Result result = new Result(50003, "用户没有登录,不允许访问!", null, null);
ResponseUtils.responseJson(JsonUtils.objectToJson(result), response);
return;
}
}
//校验jwt令牌是否合法,是否过期
if(!TokenUtils.verify(token)){
//token不合法
Result result = new Result(50003, "用户没有登录,不允许访问!", null, null);
ResponseUtils.responseJson(JsonUtils.objectToJson(result), response);
return;
}
//后面在servlet的业务代码中需要获取当前用户的登录信息?
//解密载荷的信息
DecodedJWT jwt = JWT.decode(token);
Map<String, Claim> claims = jwt.getClaims();//获取载荷信息 用户登录的信息
//绑定到当前线程
UserThreadLocal.setUser(claims);
//权限代码
> //权限鉴定
> //获取用户访问的uri
>
> //判断该uri是否需要拦截 准备一个集合 配置一些不需要拦截的uri
>
> //判断当前访问的uri是否在用户的权限中 如果在 放行 不再就拦截
>
> //查询用户对应的权限
> String phone=claims.get("phone").asString();
> List<Privilege> privilegeList = privilegeService.findPrivilegeByEmployeePhone(phone);
>
> //默认没有通过
> boolean isOk=false;
>
> if(privilegeList!=null){
>
> for(Privilege privilege:privilegeList){
>
> if (uri.contains(privilege.getUri())) {
> isOk=true;
> break;
> }
>
> }
>
> }
>
> if(isOk==false){
> //权限鉴定没有通过
> Result result = new Result(50003, "用户没有对应的权限,不允许访问!", null, null);
> ResponseUtils.responseJson(JsonUtils.objectToJson(result), response);
> return;
> }
//放行
chain.doFilter(request,response);
//移除当前线程绑定的用户信息
UserThreadLocal.removeUser();
}
@Override
public void destroy() {
}
}