易用宝项目记录day7-权限与菜单
1.登录成功后主体为用户
位置:AISellRealm 以前登录成功,传的是username,现在传主体Employee对象
/登录验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.拿令牌(拿对应的用户名)
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//2.根据用户名取拿密码
String username = token.getUsername();
Employee loginUser = employeeService.findByUsername(username);
if (loginUser == null) {
//返回null为:用户名错误
return null;
}
//获取到盐值
ByteSource salt = ByteSource.Util.bytes("xiaji");
//getName():只是随便起的名
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginUser, loginUser.getPassword(), salt, getName());
return authenticationInfo;
}
2.UserContext
shiro是存session中的,HttpSession也会有值
package cn.xiaji.common;
import cn.xiaji.domain.Employee;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
/*
操作用户的工具类
*/
public class UserContext {
public static final String LOGINUSER = "loginUser";
public static void putUser() {
Subject subject = SecurityUtils.getSubject();
//获取到session
Session session = subject.getSession();
//获取到相应的主体
Employee employee = (Employee) subject.getPrincipal();
//将当前登录用户放到session中
session.setAttribute(LOGINUSER, employee);
}
public static Employee getUser() {
Subject subject = SecurityUtils.getSubject();
//获取到session
Session session = subject.getSession();
return (Employee) session.getAttribute(LOGINUSER);
}
}
3.FilterChainDefinitionMapFactory
所有需要过滤的数据从数据库来取
package cn.xiaji.web.shiro;
//encoding: utf-8
import cn.xiaji.domain.Permission;
import cn.xiaji.service.IPermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author: xj
* @contact: xiaruji520@gmail.com
* @file: FilterChainDefinitionMapFactory.java
*/
/*
*/
public class FilterChainDefinitionMapFactory {
@Autowired
private IPermissionService permissionService;
public Map<String, String> builderFilterChainDefinitionMap() {
Map<String, String> maps = new LinkedHashMap<>();
//maps中加数据
//设置放行
maps.put("/s/*", "anon");
maps.put("/login", "anon");
maps.put("/easyui/**", "anon");
maps.put("/js/**", "anon");
maps.put("/json/**", "anon");
maps.put("/css/**", "anon");
maps.put("*.js", "anon");
maps.put("*.css", "anon");
maps.put("/images/**", "anon");
//设置权限拦截
/* maps.put("/employee/index", "perms[employee:index]");
maps.put("/dept/index", "perms[dept:index]");*/
//从数据库拿到数据,放到Map中
List<Permission> permissions = permissionService.findAll();
for (Permission permission : permissions) {
//资源
String url = permission.getUrl();
//权限
String sn = permission.getSn();
//把路径与资源放到拦截中去
//maps.put(url, "perms[" + sn + "]");
//改为自定义的
maps.put(url, "aiSellPerms[" + sn + "]");
}
//设置拦截所有
maps.put("/**", "authc");
return maps;
}
}xxxxxxxxxx package cn.xiaji.common;//encoding: utf-8import org.apache.shiro.crypto.hash.SimpleHash;/** * @author: xj * @contact: xiaruji520@gmail.com * @file: MD5Utils.java *//* */public class MD5Utils { //加盐 public static final String salt = "xxxx";//xxxx代表你的盐值 //加密次数 public static final int HASHITERATIONS = 1000;//加密循环次数 public static String createMD5Pwd(String str) { SimpleHash hash = new SimpleHash("MD5", str, salt, HASHITERATIONS); return hash.toHex(); }}
###4.PermissionRepository
JPQL关联原则: 1.不写on 2.关联对象的别名.属性
package cn.xiaji.repository;
import cn.xiaji.domain.Permission;
import org.springframework.data.jpa.repository.Query;
import java.util.Set;
public interface PermissionRepository extends BaseRepository<Permission, Long> {
//根据当前用户获取到他对应的所有权限
//jpql关联法制:1.不需要写one 2.关联前面对象的别名,属性
@Query("select p.sn from Employee o join o.roles r join r.permissions p where o.id =?1")
Set<String> findPermissionByUser(Long userId);
}
###5.JpaRealm
拿到当前登录用户,再获取它的权限
//进行授权判断(权限)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.拿到当前登录的用户
Employee employee = (Employee) principalCollection.getPrimaryPrincipal();
//2.根据登录用户拿到角色与权限
// Set<String> roles = getRoles(employee.getUsername());
//Set<String> permissions = getPermissions(employee.getUsername());
Set<String> permissions = permissionService.findPermissionByUser(employee.getId());
//3.创建返回的权限对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//把角色放到权限对象中去
//authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
6.Ajax请求的权限处理
shiro处理没有权限是跳转页面,而我们如果是ajax请求
我们希望是返回json数据 ajax请求会有一个请求头:X-Requested-With: XMLHttpRequest
需要自定义一个shiro的权限过滤器
package cn.xiaji.web.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/*
自定义权限,解决Ajax的问题
得告诉shiro(现在spring在管理) 使用自定义过滤器
*/
public class AISellPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
this.saveRequestAndRedirectToLogin(request, response);
} else {
//判断是不是Ajax请求,是就返回 {success:false,msg:"权限不够,请联系管理员!"}
//拿到Http的请求和响应
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//Ajax请求会发送:X-Requested-With,值是XMLHttpRequest
if ("XMLHttpRequest".equals(req.getHeader("X-Requested-With"))) {
//设置响应头
resp.setContentType("application/json;charset=UTF-8");
//代表这里是Ajax请求 ,响应 {success:false,msg:"权限不够,请联系管理员!"}
response.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;
}
}
7.applicationContext-shiro.xml
配置权限过滤器
<!--②自定义过滤器:把自定义过滤器加入到核心过滤器中-->
<property name="filters">
<map>
<entry key="aiSellPerms" value-ref="aiSellPerms"/>
</map>
</property>
</bean>
<!--实体工厂bean,把它返回值也变成一个bean-->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapFactory"
factory-method="builderFilterChainDefinitionMap"/>
<!-- 拿到生成map的工厂 -->
<bean id="filterChainDefinitionMapFactory" class="cn.xiaji.web.shiro.FilterChainDefinitionMapFactory"/>
<!--①自定义过滤器:配置你的自定义过滤器-->
<bean id="aiSellPerms" class="cn.xiaji.web.shiro.AISellPermissionsAuthorizationFilter"/>
8.修改过滤器配置
package cn.xiaji.web.shiro;
//encoding: utf-8
import cn.xiaji.domain.Permission;
import cn.xiaji.service.IPermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author: xj
* @contact: xiaruji520@gmail.com
* @file: FilterChainDefinitionMapFactory.java
*/
/*
*/
public class FilterChainDefinitionMapFactory {
@Autowired
private IPermissionService permissionService;
public Map<String, String> builderFilterChainDefinitionMap() {
Map<String, String> maps = new LinkedHashMap<>();
//maps中加数据
//设置放行
maps.put("/s/*", "anon");
maps.put("/login", "anon");
maps.put("/easyui/**", "anon");
maps.put("/js/**", "anon");
maps.put("/json/**", "anon");
maps.put("/css/**", "anon");
maps.put("*.js", "anon");
maps.put("*.css", "anon");
maps.put("/images/**", "anon");
//设置权限拦截
/* maps.put("/employee/index", "perms[employee:index]");
maps.put("/dept/index", "perms[dept:index]");*/
//从数据库拿到数据,放到Map中
List<Permission> permissions = permissionService.findAll();
for (Permission permission : permissions) {
//资源
String url = permission.getUrl();
//权限
String sn = permission.getSn();
//把路径与资源放到拦截中去
//maps.put(url, "perms[" + sn + "]");
//改为自定义的
maps.put(url, "aiSellPerms[" + sn + "]");
}
//设置拦截所有
maps.put("/**", "authc");
return maps;
}
}
9.菜单管理
菜单domain的自关连配置
需要配置双向,但是不能让JPA去管理一对多(我们自己管理:@Transient)
双向生成JSON会产生死循环,需要一边进行忽略:@JsonIgnore
//让它不再生成JSON
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
@JsonIgnore
private Menu parent;
// 临时属性 -> 这个字段JPA就不管它了
@Transient
private List<Menu> children = new ArrayList<>();
9.1 MenuRepository
public interface MenuRepository extends BaseRepository<Menu,Long>{
@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> findByUser(Long userId);
}
###9.2MenuService
根据设计只能通过员工找到子菜单
需要通过子菜单拿到父菜单
判断这个父菜单是否已经存到集合中
如果这个菜单单没有存起来,放到集合中 把当前这个子菜单放到父菜单中去
@Override
public List<Menu> findLoginMenu() {
//1.准备一个装父菜单的容器
List<Menu> parentMenus = new ArrayList<>();
//2.拿到当前登录用户的所有子菜单
Employee loginUser = UserContext.getUser();
List<Menu> children = menuRepository.findByUser(loginUser.getId());
//3.遍历子菜单,设置它们的关系
for (Menu child : children) {
//3.1 根据子菜单拿到它对应的父菜单
Menu parent = child.getParent();
//3.2 判断这个父菜单是否在容器中
if(!parentMenus.contains(parent)){
//3.3 如果不在,把父菜单放进去
parentMenus.add(parent);
}
//3.4 为这个父菜单添加对应的子菜单
parent.getChildren().add(child);
}
return parentMenus;
}
###9.3 UtilController中返回值
@Autowired
private IMenuService menuService;
@RequestMapping("/loginUserMenu")
@ResponseBody
public List<Menu> loginUserMenu(){
return menuService.findLoginMenu();
}
9.4 main.jsp修改路径
$('#menuTree').tree({
url:'/util/loginUserMenu',
...
shiro:hasPermission
没有这个权限,就不展示对应的按键
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
...
<shiro:hasPermission name="employee:delete">
<a href="javascript:;" data-method="delete" class="easyui-linkbutton" iconCls="icon-remove" plain="true">删除</a>
</shiro:hasPermission>