(一)主要实现功能
① 实现修改session中的主体为当前对象
② 实现授权管理
③ 实现菜单栏从数据库中显现
(二)细节处理
(1)从数据库获取权限
1、将权限变为从数据库中获取的真实权限
在拦截代码中:
1)注入权限业务层(因为在shiro的xml中配置了bean )
2)从数据库中获取所有的权限
3)将获取的权限集合遍历,并将每一个权限存入自定义拦截代码中(注意此时需要通过字符串,拼接编号)
(2)session处理
- 登录成功后主体为用户
以前登录成功,传如session中的是username,现在传主体Employee对象,在开发中也大多传入的是对象
//解决身份认证(登录)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.获取令牌
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
//2.根据令牌,获取用户名
String username = token.getUsername();
Employee employee = employeeService.findByUsername(username);
//4.数据库中无法根据用户传入的用户名查询到对应的对象,即对象为null,则代表该用户不存在
if(employee==null){
//为null,代表用户名不存在,shior自动抛出UnknownAccountException
return null;
}
//在这里加盐值需一个ByteSource对象,而Shiro提供了一个ByteSource对象
ByteSource salt = ByteSource.Util.bytes("lzj");
/**登录验证的用户身份信息,传入数据,shiro会自动比较获取的密码与令牌中用户传入的密码
* 如果对应不上,则会自动抛出IncorrectCredentialsException异常
* 第一个参数:存入的是当前主体,shiro可以在项目的任何地方获取当前的主体(在开发中一般使用直接存放当前的主体)
* 第二个参数:存入的是当前用户的密码
* 第三个参数:存入的是盐值
* 第四个参数:存入的是自定义取值方法名(名字随意,只需保存唯一即可)
*/
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(employee,employee.getPassword(),salt,getName());
return authenticationInfo;
}
- shiro的主体
此处的当事人principle,可以随意,因为此用户是主体(登录之后存入的东西,shiro是可以在任何地方都可以获取)如果当前的主体是当前对象的话,就会报错,不能在权限验证时将eployee转为一个String=====》将此处修改为对象(开发的时候,大多数用的是对象。存对象)
修改前台,直接获取属性
- shiro的会话管理
sbject不仅仅是当前用户,还是一个容器,还装有session(会话管理)、principal(主体)等等
注意此时的session不是http内的session。
shiro会自动同步代理,转为http的session
很多地方会使用当前用户—》抽取公共类//存入上下文中的当前对象
public class UserContext {
/此处字符串内设置为userInSession,因为ajax不支持有"_"的变量定义方式/
private static final String USER_IN_SESSION = “userInSession”;
public static void setUser(Employee loginUser) {
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute(USER_IN_SESSION, loginUser);
}
public static Employee getUser() {
Subject subject = SecurityUtils.getSubject();
return (Employee) subject.getSession().getAttribute(USER_IN_SESSION);
}
}
````
(3)授权管理
权限是从数据库中获取,前台应该显示当前用户的所有权限
- PermissionRepository写完JPQL之后,业务层进行调用
JPQL关联原则: 1.不写on 2.关联对象的别名.属性
//根据用户拿到他对应的所有权限
@Query("select distinct p.sn from Employee e join e.roles r join r.permissions p where e.id = ?1")
Set<String> findPermsByUser(Long userId);
- AiSell 获取当前用户的所有权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Employee loginUser = UserContext.getUser();
//根据当前用户拿到对应的权限
Set<String> perms = permissionService.findPermsByUser(loginUser.getId());
//准备并返回AuthorizationInfo这个对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
(4)Ajax请求权限处理
shiro处理没有权限是跳转页面,而我们如果是ajax请求,我们希望是返回json数据 ajax请求会有一个请求头:X-Requested-With: XMLHttpRequest 需要自定义一个shiro的权限过滤器
- 自定义权限过滤器
//自定义shiro的过滤器,单独处理ajax请求
public class AiSellPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
//没有登录成功后的操作
this.saveRequestAndRedirectToLogin(request, response);
} else {
//登录成功后没有权限的操作
//1.转成http的请求与响应操作
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
//2.有MLHttpRequest对象,则为ajax请求
String xRequestedWith = httpRequest.getHeader("X-Requested-With");
if (xRequestedWith != null &&"XMLHttpRequest".equals(xRequestedWith)) {
//3.在这里就代表是ajax请求
//表示ajax请求 {"success":false,"message":"没有权限"}
httpResponse.setContentType("text/json; charset=UTF-8");
httpResponse.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;
}
}
- 让Spring管理自定义过滤器,并告诉shiro使用自定义过滤器,在applicationContext-shiro.xml内配置
<!--引入操作拦截或方形的路径和页面的javaBean-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMapBuilderMap"/>
<property name="filters">
<map>
<entry key="aisellPerm" value-ref="AiSellPermissionsAuthorizationFilter"></entry>
</map>
</property>
<!--自定义过滤器-->
<bean id="AiSellPermissionsAuthorizationFilter" class="cn.lzj.aisell.shiro.AiSellPermissionsAuthorizationFilter"/>
@Autowired
private IPermissionService permissionService;
public Map<String,String> createFilterChainDefinitionMap(){
...
//拿到所有权限
List<Permission> perms = permissionService.findAll();
//设置相应的权限
perms.forEach(p -> {
filterChainDefinitionMap.put(p.getUrl(), "aisellPerms["+p.getSn()+"]");
});
...
}
(5)菜单管理
- Menu
菜单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<>();
- 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);
}
- MenuServiceImpl
根据设计只能通过员工找到子菜单
需要通过子菜单拿到父菜单
判断这个父菜单是否已经存到集合中
如果这个菜单单没有存起来,放到集合中 把当前这个子菜单放到父菜单中去
@Override
public List<Menu> findMenus() {
//1.父菜单容器
List<Menu> parents = new ArrayList<>();
//2.获取当事人的对应的所有子菜单
Employee user = UserContext.getUser();
List<Menu> menus = menuRepository.findMenusByLoginUser(user.getId());
//3.遍历子菜单,获取父菜单,并且放入parents父容器中 --》此处只能使用for循环,不能使用for-each
for(int i=0;i<menus.size();i++){
//3.1 通过子菜单获取其对应的父菜单
Menu childrenMenu = menus.get(i);
Menu menuParent = childrenMenu.getParent();
//3.2 将父菜单放入父菜单容器中(避免重复,需要判断该父菜单在容器内是否存在)
if(!parents.contains(menuParent)){
parents.add(menuParent);
}
//4.将子菜单放入父菜单中
menuParent.getChildren().add(childrenMenu);
}
return parents;
}
- UtilController中返回值
@Autowired
private IMenuService menuService;
@RequestMapping("/loginUserMenu")
@ResponseBody
public List<Menu> loginUserMenu(){
return menuService.findLoginMenu();
}
- main.jsp修改路径
$('#menuTree').tree({
url:'/util/loginUserMenus',
- 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>