易用宝项目记录day7-权限与菜单

易用宝项目记录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>
上一篇:02-CSS基础与进阶-day7_2018-09-07-21-27-32


下一篇:Python入门day7