一.权限管理
1.1 真实权限配置
1.1.1 拦截真实权限
public class ShiroFilterMapFactory {
@Autowired
private IPermissionService permissionService;
public Map<String,String> createMap(){
...
//从数据库中拿到所有权限
List<Permission> perms = permissionService.findAll();
//遍历所有权限,设置权限拦截
perms.forEach(p->{
//perms:权限拦截
map.put(p.getUrl(),"perms["+p.getSn()+"]");
});
...
}
}
1.1.2 当前用户赋权
PermissionRepository:根据用户id获取权限
/**
* 根据当前用户获取相应的权限(只拿权限中的sn)
* JPQL关连法则(2): 1.关连的是前面的对象别名.属性 2.自动消除迪卡尔积
* select o from Employee o join o.department
*/
@Query("select distinct p.sn from Employee e join e.roles r join r.permissions p where e.id =?1")
Set<String> findSnByUser(Long userid);
PermissionService:根据用户id获取权限
//根据当前登录用户拿到对应的权限
@Override
public Set<String> findSnByLoginUser() {
//从shiro中拿到主体(当前登录用户)
Subject subject = SecurityUtils.getSubject();
Employee loginUser = (Employee)subject.getPrincipal();
return permissionRepository.findSnByUser(loginUser.getId());
}
JpaRealm:获取权限
@Autowired
private IEmployeeService employeeService;
@Autowired
private IPermissionService permissionService;
//授权认证功能就写在这里面
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//从数据库中获取权限并放且放到授权对象中
Set<String> perms = permissionService.findSnByLoginUser();
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
二.ajax请求权限拦截
解决Ajax的跳转问题
2.1 自定义权限
写一个类,继承 PermissionsAuthorizationFilter
重写 onAccessDenied
/**
* 重写Shiro的权限过滤器(我们要让他支持Ajax操作)
*/
public class AiSellPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
//请求头的所有数据都封装到请求对象中
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
System.out.println("我是一个中国人!!!!!");
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
this.saveRequestAndRedirectToLogin(request, response);
} else {
//确定是否是Ajax请求,如果是, 就返回json数据,如果不是,执行原来的代码
//1.把请求和响应对象转到Http的方式(功能更强大一些)
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
//2.根据请求对象拿到请求头中 X-Requested-With:XMLHttpRequest
String xRequestedWith = req.getHeader("X-Requested-With");
if("XMLHttpRequest".equals(xRequestedWith)){
//3.Ajax请求的处理,返回一个json数据 {success:false,msg:"没有权限"}
//设置响应头为json
resp.setContentType("application/json;charset=UTF-8");
//通过一个响应流把json数据返回回去
resp.getWriter().print("{\"success\":false,\"msg\":\"没有权限\"}");
}else {
//获取到没有权限的地址
String unauthorizedUrl = this.getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
//如果有地址就跳转
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
//如果没有地址就报401错误
WebUtils.toHttp(response).sendError(401);
}
}
}
return false;
}
}
2.2 权限配置
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
...
<!--告诉Shiro我这里有一个自定义过滤器-->
<property name="filters">
<map>
<entry key="aisellPerms" value-ref="aiSellPermissionsAuthorizationFilter" />
</map>
</property>
</bean>
<!--配置我们的自定义权限过滤器-->
<bean id="aiSellPermissionsAuthorizationFilter"
class="cn.itsource.aisell.web.shiro.AiSellPermissionsAuthorizationFilter">
</bean>
2.3 权限使用
权限拦截的地方写上 aisellPerms 对应上一步的配置名称
public class ShiroFilterMapFactory {
@Autowired
private IPermissionService permissionService;
public Map<String,String> createMap(){
...
//遍历所有权限,设置权限拦截
perms.forEach(p->{
//perms:权限拦截
map.put(p.getUrl(),"aisellPerms["+p.getSn()+"]");
});
...
}
}
三.shiro标签认识
3.1 引入标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
3.2 拿到主体的标签
<shiro:principal property="username" />
3.3 权限判断的标签
<shiro:hasPermission name="employee:delete">
<a href="javascript:;" data-method="delete" class="easyui-linkbutton" data-options="iconCls:'icon-remove',plain:true">删除</a>
</shiro:hasPermission>
四.菜单管理
3.1 菜单对象
自己完成基本CRUD
/**
* 菜单
*/
@Entity
@Table(name = "menu")
public class Menu extends BaseDomain {
//菜单名称
private String name;
//菜单路径
private String url;
//图标
private String icon;
//parent_id
//配置一对多
@OneToMany
@JoinColumn(name = "parent_id")
private List<Menu> children = new ArrayList<>();
...
}
3.2 获取父菜单
3.2.1
MenuRepository拿到所有父菜单
/**
* 获取到所有父菜单
* @return
*/
@Query("select o from Menu o where o.url is null ")
List<Menu> findParentMenus();
3.2.2 service与controller的支持
service
@Override
public List<Menu> findParentMenus() {
return menuRepository.findParentMenus();
}
controller
/**
* 返回所有的父菜单
* @return
*/
@RequestMapping("/findParentMenus")
@ResponseBody
public List<Menu> findParentMenus() {
return menuService.findParentMenus();
}
3.2.3 主页面进行访问
/
/创建咱们的菜单树
$('#menuTree').tree({
url:'/menu/findParentMenus',
...
})
权限菜单
每个用户登录后是根据自己的权限查找到相应的菜单
1.1 Role的修改
@Transient:一对多不需要JPA管理,由我们自己添加数据
@JsonIgnore:双向关连不要生成JSON,会搞成死循环,根据业务需求放某一方放弃生成
@Entity
@Table(name = "menu")
public class Menu extends BaseDomain {
//菜单名称
private String name;
//菜单路径
private String url;
//图标
private String icon;
/**
* 配置多对一(性能,业务需求【需要通过子菜单拿到父菜单】)
* 让生成JSON的时候,不要去找父亲
*/
@ManyToOne
@JoinColumn(name = "parent_id")
@JsonIgnore
private Menu parent;
/**
* 不要配置单向一对多(性能不好)
* 你现在敢配置一对多嘛?配置了一对多,关系则由JPA来管理!
* 它会自动从1方去拿到所有多方!!!
* 1.这里是否需要儿子的List字段 -》 一定要的
* 2.这个儿子的List是否要JPA管理 -》 不可以
* 加上这个字段(属性),但是不交给JPA管理【咱们手动管理这个字段】
*/
//parent_id
//配置一对多 Transient:临时属性(JPA不会管它)
@Transient
private List<Menu> children = new ArrayList<>();
...
}
1.2 MenuRepository
根据员工获取到他对应的所有权限菜单(这里只有子菜单)
这条JPQL要求你的关系都是配置好的(与实体类匹配上)
/**
* 根据用户拿到对应的权限菜单
*/
@Query("select distinct m from Employee e join e.roles r join r.permissions p join p.menu m where e.id=?1")
List<Menu> findMenusByUser(Long userId);
1.3 MenuService
拿到当前登录用户然后进行查询
拼接成前台需要的效果(一定要记住你最后想要的是什么效果)
@Override
public List<Menu> findLoginUserMenus() {
//一.准备一个装父菜单的容器(List)
List<Menu> parentMenuList = new ArrayList<>();
//二.拿到并循环子菜单
//2.1 拿到当前登录用户
Subject subject = SecurityUtils.getSubject();
Employee loginUser = (Employee) subject.getPrincipal();
//2.2 根据用户拿到他的菜单 iter
List<Menu> menus = menuRepository.findMenusByUser(loginUser.getId());
for (Menu menu : menus) {
//三.开始设置数据结构
//3.1 根据子菜单拿父菜单
Menu parentMenu = menu.getParent();
//3.2 判断父菜单容器中是否有这个父菜单
if(!parentMenuList.contains(parentMenu)){
//如果没有,我们要把当前这个父菜单放到容器中
parentMenuList.add(parentMenu);
}
//3.3 把子菜单放到父菜单中
parentMenu.getChildren().add(menu);
}
return parentMenuList;
}
1.4 前台正常展示
@RequestMapping("/findParentMenus")
@ResponseBody
public List<Menu> findParentMenus() {
return menuService.findLoginUserMenus();
}