一、权限判断
- 获取到所有权限进行判断,应该从数据库中获取
public class FilterChainDefinitionMapFactory {
@Autowired
private IPermissionService iPermissionService;
public LinkedHashMap<String,String> builderFilterChainDefinitionMap(){
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
//anon 不需要登陆就可以访问 authc需要登陆才可以访问 per[xx:xx]需要授权才可以
linkedHashMap.put("/s/login.jsp", "anon");
linkedHashMap.put("/login", "anon");
linkedHashMap.put("*.js","anon");
linkedHashMap.put("*.css","anon");
linkedHashMap.put("/css/**","anon");
linkedHashMap.put("/js/**","anon");
linkedHashMap.put("/easyui/**","anon");
linkedHashMap.put("/images/**","anon");
//linkedHashMap.put("/dept/index", "perms[dept:index]");
//linkedHashMap.put("/employee/index", "perms[employee:index]");
List<Permission> list = iPermissionService.findAll();
System.out.println(list);
list.forEach(e->
linkedHashMap.put(e.getUrl(), "permsFilter["+e.getSn()+"]")
);
linkedHashMap.put("/**", "authc");
return linkedHashMap;
}
}
- 怎么拿到登录用户
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//先拿用户传过来的令牌
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
//拿到令牌的用户
String username = token.getUsername();
Employee loginUser = iEmployeeService.findByUsername(username);
if (loginUser==null){
return null;
}
//加盐验证
//要想使用,还需要在xml配置加密次数,和加密算法。。才可以正确解密验证
ByteSource salt = ByteSource.Util.bytes("itsource");
System.out.println(loginUser.getPassword()+"=====================测试=======================");
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(loginUser,loginUser.getPassword(),salt,getName());
return simpleAuthenticationInfo;
}
修改页面展示:
主页面的用户现在变成了这个样子
修改代码:
<shiro:user>
欢迎[ <shiro:principal property="username" />]登录,<a href="${pageContext.request.contextPath}/logout">退出</a>
</shiro:user>
二、完成了登陆验证,将用户存入session
- 为了以后方便,创建一个工具类专门存取用户
/**
* 该工具可以将当前主体对象存入到session中去
* */
public class UserContext {
public static final String LOGINUSER = "loginUser";
public static void setUser(){
/*先得到全局的授权主体*/
Subject subject = SecurityUtils.getSubject();
//得到session
Session session = subject.getSession();
/*将该主题对象存入到session*/
Employee loginUser = (Employee)subject.getPrincipal();
session.setAttribute(LOGINUSER, loginUser);
}
public static Employee getUser(){
/*先拿到主体employ对象*/
Subject subject = SecurityUtils.getSubject();
//拿到session
Session session = subject.getSession();
//拿到session中的对象
Employee employee = (Employee)session.getAttribute(LOGINUSER);
return employee;
}
}
- LoginController:登录成功后把用户放到Session中
@RequestMapping(value="/login",method = RequestMethod.POST)
@ResponseBody
public SuccessBoolean login(String username,String password){
System.out.println("===========================jll");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
if (!subject.isAuthenticated()){
try {
subject.login(token);
} catch (UnknownAccountException e) {
System.out.println("用户名不存在");
return new SuccessBoolean(false,"用户名不存在");
}
catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
e.printStackTrace();
return new SuccessBoolean(false,"用户名不存在或者密码错误");
}
catch (AuthenticationException e) {
System.out.println("未知错误");
e.printStackTrace();
return new SuccessBoolean(false,"未知错误");
}
}
//使用工具存入session中
UserContext.setUser();
//如果登陆验证失败,要访问首页还是会被拦截下来
return new SuccessBoolean();
}
三、根据用户id拿到权限
- 完成权限的判断
其它完成权限,就是通过当前登录用户拿到所有权限,然后去判断当前用户是否有咱们当前访问的这个路径的权限!
PermissionRepository
@Query("select p.sn from Employee o join o.roles r join r.permissions p where o.id=?1")
Set<String> findPermissionSnByUserID(Long id);
IPermissionService
在这里插入代码片
PermissionServiceImpl
@Override
public Set<String> findPermissionSnByUserID(Long id) {
Set<String> permissions = permissionRepository.findPermissionSnByUserID(id);
return permissions;
}
- 自定义Realm:进入权限判断
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//拿到主角。。登陆方法验证传过来的是什么 这里就是什么。。
Employee loginUser = (Employee)principalCollection.getPrimaryPrincipal();
//核心管理器会调用doGetAuthorizationInfo方法得到这个用户的权限和角色
//设置到权限的对象并且返回
Set<String> permissions = iPermissionService.findPermissionSnByUserID(loginUser.getId());
//将权限改成从数据库中查找出来。。进行判断
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setStringPermissions(permissions);
//返回权限对象
return authorizationInfo;
}
四、测试,并且完成自定义过滤器
以上功能完成后,我们就可以测试咱们的用户是否有相应的权限!
测试
当没有删除权限时,删除报错
需要的结果是,点击删除,删除失败,后台返回没有权限的结果
原因,在请求是Ajax请求时,这个删除是Ajax,后台响应返回的就是一个没有权限时的我们自己设置的页面,他把整个全返回了,我们仅仅需要一个失败原因
想要的是json的结果字符串。。
咱们所有的请求可以分为两大类,一个是跳转页面,xxx/index 还有一类是Ajax请求期望返回的是{“success”:false,”message”:”没有权限”}
区分处理是否是Ajax请求,普通跳转页面的请求 ,就跳转没有权限的页面,如果是ajax请求返回{“success”:false,”message”:”没有权限”}
怎么判断Ajax 判断请求头里面是否有X-Requested-With
跳转页面请求头
Ajax请求多个一个请求头X-Requested-With XMLHttpRequest
* 所以自定义拦截器
因为shiro自带的拦截器不区分请求,所以不能达到需要的请求需要结果
/**
* 将自定义过滤器交给shiro,改变shiro的默认过滤器配置
* */
//继承PermissionsAuthorizationFilter ,覆写方法
public class AISellPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = this.getSubject(request, response);
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if (subject.getPrincipal() == null) {
this.saveRequestAndRedirectToLogin(request, response);
} else {
//根据请求确定是什么请求
String xRequestedWith = httpServletRequest.getHeader("X-Requested-With");
if (xRequestedWith != null &&"XMLHttpRequest".equals(xRequestedWith)) {
//3.在这里就代表是ajax请求
//表示ajax请求 {"success":false,"message":"没有权限"}
//返回结果
httpServletResponse.setContentType("text/json; charset=UTF-8");
httpServletResponse.getWriter().print("{\"success\":false,\"msg\":\"没有权限\"}");
}else {
String unauthorizedUrl = this.getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(401);
}
}
}
return false;
}
}
- 把过滤器交给shiro,配置applicationContext-shiro.xml
<!-- 5.shiro的真实过滤器(注:这个名称必需和web.xml的代表过滤器【DelegatingFilterProxy】名称一样) -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- 登录的url,如果没有登录,你访问的路径会跳到这个页面 -->
<property name="loginUrl" value="/login"/>
<!-- 登录成功的url,如果登录成功,会跳转到这个页面 -->
<property name="successUrl" value="/main"/>
<!-- 没有权限时跳转到这个位置 -->
<property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
<!-- 这个配置我们可以直接给一个map(动态的可以从代码中获取) -->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
<!-- 引用自定义的权限过滤器 -->
<!--引用自定义过滤器 在map,权限工厂配置路径-->
<property name="filters" >
<map>
<entry key="permsFilter" value-ref="aiSellPermsFilter"></entry>
</map>
</property>
<!-- 这个bean是帮助咱们获取相应的值:它会到一个工厂bean中通过对应的方法拿到相应的值 -->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapFactory" factory-method="builderFilterChainDefinitionMap"></bean>
<!-- 配置可以创建 -->
<bean id="filterChainDefinitionMapFactory" class="com.lirui.web.shiro.FilterChainDefinitionMapFactory">
</bean>
<bean id="aiSellPermsFilter" class="com.lirui.web.shiro.AISellPermissionsAuthorizationFilter" />
- 修改自动权限设置的工具类FilterChainDefinitionMapFactory
修改权限
linkedHashMap.put(e.getUrl(), “permsFilter[”+e.getSn()+"]")
public class FilterChainDefinitionMapFactory {
@Autowired
private IPermissionService iPermissionService;
public LinkedHashMap<String,String> builderFilterChainDefinitionMap(){
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
/*
/s/login.jsp = anon
/login = anon
/dept/index = perms[dept:index]
/employee/index = perms[employee:index]
/** = authc
* */
//anon 不需要登陆就可以访问 authc需要登陆才可以访问 per[xx:xx]需要授权才可以
linkedHashMap.put("/s/login.jsp", "anon");
linkedHashMap.put("/login", "anon");
linkedHashMap.put("*.js","anon");
linkedHashMap.put("*.css","anon");
linkedHashMap.put("/css/**","anon");
linkedHashMap.put("/js/**","anon");
linkedHashMap.put("/easyui/**","anon");
linkedHashMap.put("/images/**","anon");
//linkedHashMap.put("/dept/index", "perms[dept:index]");
//linkedHashMap.put("/employee/index", "perms[employee:index]");
List<Permission> list = iPermissionService.findAll();
System.out.println(list);
list.forEach(e->
linkedHashMap.put(e.getUrl(), "permsFilter["+e.getSn()+"]")
);
linkedHashMap.put("/**", "authc");
return linkedHashMap;
}
}
五、菜单读取
没有url的就是父菜单,根目录。。
用户->角色->权限->菜单
用户有哪些权限,就应该有对应的菜单
(这里咱们可以分析数据库理解设计)
用户拥有对应的权限就拥有对应的菜单(二级菜单),如果此菜单有父菜单(一级菜单)也同时拥有
- Domain设计
@Entity
@Table(name="menu")
public class Menu extends BaseDomain {
private String name;//菜单名称
private String url; //路径
private String icon; //图标
/**
* JsonIgnore:生成JSON的时候忽略这个属性
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="parent_id")
@JsonIgnore //这里生成json的时候要忽略,否则会造成功能相互调用
private Menu parent;
/**
* 还要配置一个一对多
* 这个字段不要交给JPA管理【到时候自己写代码管理】
* 数据库的menu表中就应该有一个children,而且还是List类型
* Transient:临时属性(JPA不管这个属性,和数据库没有关系)
*/
@Transient
private List<Menu> children = new ArrayList<>();
…
public String getText(){ //EasyUI的树需要一个text属性
return name;
}
}
因为前台的名字是叫Text
public String getText(){ //EasyUI的树需要一个text属性
return name;}
多个权限可以对应一个菜单
比如系统管理菜单下面可以有员工的管理等等
数据管理菜单也可以管理员工。。等等
所以权限和菜单是多对一关系
- Permission类
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="menu_id")
private Menu menu;
MenuRepository
员工和角色多对多,一个员工可能有多个角色
一个角色可能多个权限,每一个权限对应一个menu 每一个权限permission对象中就有一个menu对象 每一个menu对象都有一个parent字段,
一个人通过permission去查menu,查到的menu只有子菜单,
Permission 中的 menu 全是子菜单 只要有子菜单就一定有父菜单
比如一个user,有员工管理权力8,查询得到的是id为8的menu对象,这个menu一定有一个父菜单字段parent,6号
- MenuRepository
@Query("select p.sn from Employee o join o.roles r join r.permissions p where o.id=?1")
Set<String> findPermissionSnByUserID(Long id);
- IMenuService
List<Menu> findByUserId();
- MenuServiceImpl
@Override
public List<Menu> findByUserId() {
Employee loginUser = UserContext.getUser();
//拿到所有的菜单menu对象
List<Menu> menuList = menuRepository.findByUserId(loginUser.getId());
//准备一个父菜单容器 仅仅是存入子菜单返回给前台
List<Menu> parentList = new ArrayList<>();
//遍历查询的所以菜单
for (int i = 0;i<menuList.size();i++){
//得到单个的子菜单对象menu,里面有parent,有空的children集合没用,通过他得到父菜单
Menu menuChild = menuList.get(i);
//得到父菜单,这个父菜单的children集合就有用了!
//判断父菜单集合中是否有这个父菜单,没有就先放进父菜单容器再添加,有的话就直接添加子菜单
Menu menuParent = menuChild.getParent();
if (!parentList.contains(menuParent)){
//先添加到父容器集合
parentList.add(menuParent);
}
//添加子菜单到这个父菜单
/*因为当次循环的是这个子菜单,所以添加到的就一定是他自己的父菜单中*/
menuParent.getChildren().add(menuChild);
}
//返回父容器集合给前台 里面有子菜单。。
System.out.println(parentList.get(1).getChildren());
return parentList;
}
思路梳理
Menu中父菜单和子菜单,父菜单要找得到子菜单
设计思路。
两种思路都是只返回几个父对象,然后前台使用父对象的集合中的子对象,
前台接收父菜单,通过父菜单获取子菜单,形成菜单结构
1.如果父菜单和子菜单多对一,一对多。不行,为什么?
2.因为查询到了user的menu,的却可以直接拿到父菜单和父菜单对应的子菜单,然后得到父菜单,但是返回了父菜单,因为是双向,就可以直接找到所有子菜单了
那就相当于不验证直接返回所有菜单
2.所以,不能配置双向,父菜单中的集合我们自己控制,打上注解@transient
4 每一个menu对象都有一个childrenList集合在内部,直接查询出来是子菜单,是空的
查出user的所有menu的时候,findbyuserID方法是返回所有menu给前台
5.我们应该自己管理返回结果,应该返回那几个父菜单,将子菜单存进去。
6.所以service层覆写方法,现在用子菜单得到他的父菜单menu对象,然后准备一个要返回的父菜单,如果这个父菜单有了,就不存入,然后将这个子菜单存到这个父菜单中
返回给前台
六、 UtilController
用于返回treeMenu菜单
//拿到当前用户的菜单
@RequestMapping("/loginUserMenus")
@ResponseBody
public List<Menu> loginUserMenus(){
List<Menu> menuList = iMenuService.findByUserId();
return menuList;
}
修改jsp页面的url
- 查看返回数据时报错
原因
相互找,循环,让子菜单不要去找父菜单
只要是双向关系就要出问题
@JsonIgnore //这里生成json的时候要忽略,否则会造成功能相互调用
七、页面按钮权限控制
一个用户如果有查看员工的权限,但是它却没有删除的权限,我们连按钮都不应该让他看到!
- 功能实现
- 需要在头部引入
//引入shiro标签
<%@taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>
<shiro:hasPermission name="employee:save">
<a href="#" data-method="add" class="easyui-linkbutton" iconCls="icon-add" plain="true">添加</a>
</shiro:hasPermission>
<shiro:hasPermission name="employee:update">
<a href="#" data-method="edit" class="easyui-linkbutton" iconCls="icon-edit" plain="true">修改</a>
</shiro:hasPermission>
<shiro:hasPermission name="employee:delete">
<a href="#" data-method="del" class="easyui-linkbutton" iconCls="icon-remove" plain="true">删除</a>
</shiro:hasPermission>