项目练习-day05(登录、注销,角色权限的CRUD)

1. 准备密码加密的工具类

把关键属性设置为常量,便于以后根据需求不同修改,也方便后面的使用可以直接调用!

public class MD5Utils {
    //算法名字
    public static final String ALGORITHMNAME = "MD5";
    //加盐
    public static final Object SALT = "java";
    //迭代次数
    public static final int HASHITERATIONS = 10;
    public static String createPassword(String source){
        SimpleHash simpleHash = new SimpleHash(ALGORITHMNAME,source,SALT,HASHITERATIONS);
        return simpleHash.toHex();
    }
}

2. 登录

  1. 登录界面

    使用ajax发送登录请求到后台,携带登录信息!

  2. LoginController

    根据请求方式不同,可以执行不同的方法!符合RESTful风格 !

    后台接收请求,先获取当前用户,根据前台传过来的参数封装用户名密码令牌,此时shiro拿到这个令牌去自定义的realm中获取权限数据。在realm中先根据当前用户拿到用户名,然后去数据库查询用户名是否存在,如果不存在,直接抛出异常,如果存在,获取到数据库中的用户名对应的密码,然后对前台传过来的密码进行加密(自定义realm中的凭证匹配器设置算法和迭代次数),加密后与数据库中获取到的密码进行比对,如果不一致抛出异常,控制器应该捕获不同的异常,传递到前台!

传统风格 Resultful风格
url method url method
增加 /addEmployee?name=xx POST /employee PUT
删除 /deleteEmployeel?id=1 GET /employee/1 DELETE
修改 /updateEmployee?id=1 POST /employee/1 POST
获取 /getEmployee?id=1 GET /employee/1 GET
查询 /listEmployee GET /employee GET

在下面的代码中,因为配置了拦截器(只对/login资源进行放行),为什么还能跳转到登录界面?

测试结果:试图解析器不会被拦截,可以通过它直接转发到禁止访问的页面,但是第一次请求有效,后面的资源都会被拦截,都是同一次请求!

这里不能用重定向,如果使用了,会无限重定向,因为发送一次请求,重定向到其他资源,然后被拦截,再跳到登录界面,就会无线死循环!/login--->其他资源(拦截)--->/login

@Controller
public class LoginController {
    //如果是get请求,跳到登录页面
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String index(){
        return "login";
    }
    //如果是post请求提交参数,执行登录
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    @ResponseBody
    public JsonResult login(String username, String password){
        //1.获取当前用户
        Subject subject = SecurityUtils.getSubject();
        //2.判断当前用户是否登录
        if(!subject.isAuthenticated()){
            try {
                //3.创建用户名和密码令牌
                UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
                //4.登录
                subject.login(usernamePasswordToken);
            } catch (UnknownAccountException e) {
                System.out.println("账户名错误");
                return new JsonResult("用户名错误",false);
            }catch (IncorrectCredentialsException e){
                System.out.println("密码错误");
                return new JsonResult("用户名或者密码错误",false);
            }catch (AuthenticationException e){
                System.out.println("其他异常");
                return new JsonResult("神秘错误",false);
            }
        }
        return new JsonResult();
    }

自定义realm中的登录验证

/**
     * 登录验证
     * @param authenticationToken
     * @return 如果返回的是null表示用户不存在
     * @throws AuthenticationException
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取用户名密码令牌(前台传过来的)
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)authenticationToken;
        //获取用户名
        String username = usernamePasswordToken.getUsername();
        Employee employee = employeeService.findByUsername(username);
        //判断用户是否存在,不存在直接返回null,表示用户不存在。
        if(employee==null){
            return null;
        }
        //存在去数据库查询密码
        /** SimpleAuthenticationInfo:AuthenticationInfo的子类
         * 参数1:principal:主体,需要保存的对象,登录成功后在任何地方都能使用到的对象
         * 参数2:credentials:密码,从数据库查询出来的
         * 参数3:realmName:realm名称!
         */
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(employee,employee.getPassword(),getName());
        //加盐设置,需要传入一个ByteSource对象,用自带的工具类转换
        ByteSource bytes = ByteSource.Util.bytes(MD5Utils.SALT);
        simpleAuthenticationInfo.setCredentialsSalt(bytes);
        return simpleAuthenticationInfo;
    }

对前台返回消息封装

public class JsonResult {
    private String msg;
    private boolean success = true;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public JsonResult() {
    }

    public JsonResult(String msg, boolean success) {
        this.msg = msg;
        this.success = success;
    }
}

2. 注销

点击注销按钮,发送请求到后台,后台接收请求,执行注销操作!

注销后,因为拦截器的原因,只能访问可以不用登录的页面!

@RequestMapping("/logout")
    public String logOut(){
        //1.获取当前用户
        Subject subject = SecurityUtils.getSubject();
        //2.注销
        subject.logout();
        //3.无论返回什么都可以,因为已经登出了,由于拦截器的作用,只能访问可以不用登录的页面,用重定向,不用转发,不走视图解析器
        return "redirect:/";
    }

3. 登录中的一些细节

  • 敲击回车就可以登录
 /*监听回车事件,敲击回车执行登录*/
        $(document.documentElement).on('keyup',function (e) {
           //监听按下的键的code值
            var code =  e.keyCode;
            //回车键的code是13,如果相等,执行提交
           if(code==13)
               submitForm();
        })
  • 页面嵌套优化

    使用ifram页面嵌套,有时会出现,页面展示在了其他地方

    // 检查自己是否是顶级页面
    if (top != window) {// 如果不是顶级
        //把子页面的地址,赋值给顶级页面显示
        window.top.location.href = window.location.href;
    }
    
    
  • 展示用户名和注销

    注销可以使用a标签,点击a标签发送注销请求!

    当前登录用户名的展示,从何而来?

    自定义realm时,我们返回凭证中包含了一个主体,这个主体principal相当于放在了上下文中,在任何地方都可以使用,把登录成功后的用户放在主体中,那么在任何地方都可以获取到这个用户,在jsp中通过标签的方式获取!

    //引入标签
    <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    
    //获取用户名
    
    欢迎 用户登录!点我注销

4. 配置员工和角色之间的关系

@JoinTable 表示中间表信息,name,中间表名字

joinColumns = @JoinColumn 表示当前类在中间表的外键名

inverseJoinColumns =@JoinColumn 表示另一个类在中间表的外键名

  @ManyToMany
    @JoinTable(name = "employee_role",
            joinColumns = @JoinColumn(name = "employee_id"),
            inverseJoinColumns =@JoinColumn(name = "role_id") )
    private List roles = new ArrayList<>();

5. 配置角色和权限之间的关系

   @ManyToMany
   @JoinTable(name = "role_permission",
           joinColumns = @JoinColumn(name = "role_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id"))
   private List permissions = new ArrayList<>();

6. 前台页面展示及角色对应权限的添加

  • 在角色展示页面中,权限信息展示不出来,因为一个角色获取到的权限是一个list集合,使用格式化的方式解决!
权限
//获取真实权限数据,此函数应该在页面加载之前完成!
function permissionFormat(permissions) {
   var p = "";
   for(var permission of permissions){
       //拼接每一个权限名字
       p+=permission.name+' ';
   }
   return p;
}
  • 点击添加按钮,弹出添加对话框,展示表单信息,因为添加的权限不止一个(下拉框、复选框展示效果都不好),添加左右两个布局,在布局中添加datagrid!
<%--弹出对话框--%>
名称: SN:
<%--展示所有权限--%>
名称 路径 编码
<%--展示当前用户拥有权限--%>
名称 路径 编码
  • 双击将选中的权限添加到用户中
//所有权限数据表格的双击事件,双击添加到用户权限的数据表格
$("#allPermission").datagrid({
    onDblClickRow:function (index,row) {
        console.debug(row);
        //添加之前获取用户权限是否已有该权限,如果有则不能添加
        //获取用户权限的所有行
        var rows = $("#userPermission").datagrid("getRows");
        //遍历数据,如果有相同的,则不能添加
        for (var r of rows){
            if(r.id==row.id){
                $.messager.alert('提示','已有该权限,请勿重复选择!');
                return;
            }
        }
        $("#userPermission").datagrid("appendRow",row)
    }
})
  • 双击将当前用户中已有的权限移除
//用户权限的双击事件,双击移除该行
$("#userPermission").datagrid({
    onDblClickRow:function (index,row) {
        $("#userPermission").datagrid("deleteRow",index)
    }
})
  • 每次点击添加弹出对话框,应该将用户数据表中的信息置空,解决方案,准备一个空数组,每次点击添加都执行reload方法,将空数组加载到当前数据表格,就置空了!
//清空数据表格,加载一个空数据来替换原来的数据
var newData = [];
 $("#userPermission").datagrid("loadData",newData)
  • 添加完成,点击提交,应该提交哪些数据,怎么提交?

    表单中的数据根据name很好提交,数据表中的权限数据是一个数组,他不属于表单数据,使用onSubmit提交额外的参数,提交权限的id就好!

            //在提交之前触发,可以提交额外的参数
                onSubmit: function(param){
                //获取用户权限的所有行
                var rows = $("#userPermission").datagrid("getRows");
                    //遍历数据,拼接参数
                    for (var i=0;i

后台的处理方式:permissions[0].id":"1" 小数点之前执行get方法,小数点后执行set方法,用当前的角色Role对象获取索引为0(第一个)的权限对象并执行setId方法,设置id为1,更新中间表信息!

7. 角色对应权限的修改

  • 点击修改按钮,弹出修改对话框,此时应该对当前数据进行回显,表单数据很好回显,数据表格中的权限信息回显方式:把当前行的权限信息复制到一个数组中,然后将这个数组中的数据重新加载到数据表格中!
          //数据表格的回显
            //复制一个数组
            var oldData = [...row.permissions];
            //重新加载数组里的数据
            $("#userPermission").datagrid("loadData",oldData)
  • 执行修改后台报 n to n的错误(修改了一个持久化对象的id值),当执行修改时,前台发送参数permissions[0].id":"1"到后台,后台先根据id去数据库查询获取了Role角色对象,后台的处理方式,用获取到的Role角色对象getPermissions

    返回的是一个持久化对象List集合,设置索引为0的perimission对象的id为1,此时如果之前的id不为1,就表示修改了一个持久化对象的id的值!解决方式:

      @ModelAttribute("editRole")
        public Role beforeUpdate(Long id,String _cmd){
            if(id!=null&&"update".equals(_cmd)){
                Role role = roleService.findOne(id);
                //对获取到的权限列表清空,防止报n to n的错误
                //role.setPermissions(null); 设置为null也可以
                role.getPermissions().clear();
                return role;
            }
            return null;
        }
    

你可能感兴趣的:(项目练习-day05(登录、注销,角色权限的CRUD))