易用宝项目记录day6-login和权限管理
1.登录界面
位置:webapp/WEB-INF/views/login.jsp
主要的是js使用easyui的ajax发送post请求
<%--
User: xj
Date: 2019/7/10
Time: 16:26
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<meta name="renderer" content="webkit">
<title>登录</title>
<%@ include file="/WEB-INF/views/head.jsp" %>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/style.css">
<script type="text/javascript">
//判断当前页面是否是顶层页面
if (top != window) {
//window.top.location.href = "/login";
window.top.location.href = window.location.href;
}
//回车登录
$(document.documentElement).on("keyup", function (event) {
console.debug(event);
var keyCode = event.keyCode;
if (keyCode === 13) { // 捕获回车
submitForm(); // 提交表单
}
});
//提交form表单
function submitForm() {
$('#loginForm').form('submit', {
url: "/login",
onSubmit: function () {
return $(this).form('validate');
},
//注意:现在这个data是一个字符串:{success:true,msg:""}
success: function (data) {
var result = JSON.parse(data);
if (result.success) {
//如果登录成功,跳转到主页面
window.location.href = "/main";
} else {
//如果登录失败,给出错误提示
$("#msg").text(result.msg);
setTimeout(function () {
$("#msg").hide().text("");
}, 2000);
$("#msg").show();
//$.messager.alert('错误', result.msg);
}
}
});
}
</script>
</head>
<body>
<div class="bg"></div>
<div class="container">
<div class="line bouncein">
<div class="xs6 xm4 xs3-move xm4-move">
<div style="height:150px;"></div>
<div class="media media-y margin-big-bottom">
</div>
<form id="loginForm" action="/login" method="post" class="easyui-form">
<div class="panel loginbox">
<div class="text-center margin-big padding-big-top">
<h1>欢迎来到易用宝</h1>
</div>
<div class="panel-body" style="padding:30px; padding-bottom:10px; padding-top:10px;">
<div class="form-group">
<div class="field field-icon-right">
<input type="text" class="input input-big" name="username" id="username"
placeholder="登录账号"/>
<span class="icon icon-user margin-small"></span>
</div>
</div>
<div class="form-group">
<div class="field field-icon-right">
<input type="password" class="input input-big" name="password" id="password"
placeholder="登录密码"/>
<span class="icon icon-key margin-small"></span>
</div>
</div>
<div class="form-group">
<div class="field">
<input type="text" class="input input-big" name="captcha" placeholder="填写右侧的验证码"/>
<img src="/images/captcha"
alt="" width="100" height="32" class="passcode" style="height:43px;cursor:pointer;"
onClick="this.src=this.src+'?'">
</div>
</div>
</div>
<div class="form-group">
<div class="field field-icon-right">
<h3 id="msg" style="text-align: center;color: red"></h3>
</div>
</div>
<div style="padding:30px;">
<input type="button" id="button" class="button button-block bg-main text-big input-big"
onclick="submitForm();" value="登录">
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
2.LoginController功能
准备两个/login,一个get用于跳转页面,一个post用于登录
package cn.xiaji.web.controller;
//encoding: utf-8
import cn.xiaji.common.JsonResult;
import com.wf.captcha.Captcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.utils.CaptchaUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import sun.security.util.Password;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.util.Random;
/**
* @author: xj
* @contact: xiaruji520@gmail.com
* @file: LoginController.java
*/
/*
*/
@Controller
public class LoginController {
private Font getFont() {
Random random = new Random();
Font font[] = new Font[6];
font[0] = new Font("Ravie", Font.PLAIN, 32);
font[1] = new Font("Antique Olive Compact", Font.PLAIN, 32);
font[2] = new Font("Forte", Font.PLAIN, 32);
font[3] = new Font("Wide Latin", Font.PLAIN, 32);
font[4] = new Font("Gill Sans Ultra Bold", Font.PLAIN, 32);
font[5] = new Font("Verdana", Font.PLAIN, 32);
return font[random.nextInt(6)];
}
//验证码
@RequestMapping("/images/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
/*
https://github.com/whvcse/EasyCaptcha
*/
// 设置请求头为输出图片类型
CaptchaUtil.setHeader(response);
// 三个参数分别为宽、高、位数
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
// 设置字体
specCaptcha.setFont(getFont());
// 设置类型,纯数字、纯字母、字母数字混合
specCaptcha.setCharType(Captcha.TYPE_DEFAULT);
// 验证码存入session
request.getSession().setAttribute("captcha", specCaptcha.text().toLowerCase());
// 生成的验证码
String code = specCaptcha.text();
System.out.println(code);
// 输出图片流
specCaptcha.out(response.getOutputStream());
}
/*@PostMapping("/login")
public JsonResult login(String username, String password, String code){
// 获取session中的验证码
String sessionCode = request.getSession().getAttribute("captcha");
// 判断验证码
if (code==null || !sessionCode.equals(code.trim().toLowerCase())) {
return JsonResult.error("验证码不正确");
}
}*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String index() {
return "login";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public JsonResult login(HttpServletRequest request, String username, String password, String captcha) {
// 获取session中的验证码
String code = (String) request.getSession().getAttribute("captcha");
//判断验证码是否正确
if (code.equals(captcha.toLowerCase())) {
System.out.println("验证码成功!" + username + "-----" + password);
//1.拿到当前用户
Subject subject = SecurityUtils.getSubject();
//2.如果没有登录就让它登录
if (!subject.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
return new JsonResult();
} catch (UnknownAccountException e) {
System.out.println("用户名或者密码错误!");
e.printStackTrace();
return new JsonResult(false, "用户名或者密码错误!");
} catch (IncorrectCredentialsException e) {
System.out.println("用户名或者密码错误!");
e.printStackTrace();
return new JsonResult(false, "用户名或者密码错误!");
} catch (AuthenticationException e) {
System.out.println("未知错误!");
e.printStackTrace();
return new JsonResult(false, "未知错误!");
}
}
return new JsonResult(false, "用户名或者密码错误!");
} else {
return new JsonResult(false, "验证码错误!");
}
}
//登出:注销功能
@RequestMapping("/logout")
public String logout(){
//拿到主体完成登出
Subject subject = SecurityUtils.getSubject();
subject.logout();
//跳回到登录页面
return "redirect:/login";
}
}
3.密码安全问题(MD5+循环加盐)
准备一个util
package cn.xiaji.common;
//encoding: utf-8
import 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();
}
}
在EmployeeServiceImpl 添加用户密码
package cn.xiaji.service.Impl;
//encoding: utf-8
import cn.xiaji.common.MD5Utils;
import cn.xiaji.domain.Employee;
import cn.xiaji.repository.EmployeeRepository;
import cn.xiaji.service.IEmployeeService;
import com.hazelcast.util.MD5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author: xj
* @contact: xiaruji520@gmail.com
* @file: EmployeeServiceImpl.java
*/
/*
*/
@Service
public class EmployeeServiceImpl extends BaseServiceImpl<Employee, Long> implements IEmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
//验证用户名是否存在
@Override
public Boolean checkUsername(String username) {
return !(employeeRepository.getCountByUsername(username) > 0);
}
@Override
public Employee findByUsername(String username) {
return employeeRepository.findByUsername(username);
}
//添加或更新 密码加密
@Override
@Transactional
public void save(Employee employee) {
if (employee.getId() == null) {
employee.setPassword(MD5Utils.createMD5Pwd(employee.getPassword()));
}
super.save(employee);
}
}
applicationContext-shiro.xml也要注意(编码方式与次数)
<!-- 等会需要改成自己的Realm -->
<bean id="jdbcRealm" class="cn.xiaji.web.shiro.AISellRealm">
<!--密码的匹配器-->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<property name="hashIterations" value="1000"/>
</bean>
</property>
</bean>
FilterChainDefinitionMapFactory静态资源放行
package cn.xiaji.web.shiro;
//encoding: utf-8
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author: xj
* @contact: xiaruji520@gmail.com
* @file: FilterChainDefinitionMapFactory.java
*/
/*
*/
public class FilterChainDefinitionMapFactory {
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]");
//设置拦截所有
maps.put("/**", "authc");
return maps;
}
}
4.权限管理-角色管理
-
先在domain层建立好多对多关系
-
role.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>易用宝,Role管理</title> <%@include file="/WEB-INF/views/head.jsp" %> <script src="/js/model/role.js"></script> </head> <body> <%--pagination:分页--%> <table id="roleGrid" class="easyui-datagrid" data-options="url:'/role/page', fitColumns:true, singleSelect:false, pagination:true, <%--onDblClickCell:onDblClickCell,--%> toolbar:'#gridTools', onRowContextMenu:showMenu"> <thead> <tr> <th data-options="field:'id',width:100">id</th> <th data-options="field:'name',width:100">名称</th> <th data-options="field:'sn',width:100">编码</th> <th data-options="field:'permissions',width:100,formatter:permsFormat">权限</th> </tr> </thead> </table> <%--右键支持增删改--%> <div id="gridMenu" class="easyui-menu" style="width:120px;"> <div data-options="iconCls:'icon-add'" data-method="add">添加</div> <div data-options="iconCls:'icon-edit'" data-method="update">修改</div> <div data-options="iconCls:'icon-remove'" data-method="del">删除</div> <div data-options="iconCls:'icon-remove'" data-method="delMore">批量删除</div> </div> <%--grid顶部工具栏--%> <div id="gridTools" style="padding:5px;height:auto"> <%--功能条--%> <div style="margin-bottom:5px"> <a href="#" data-method="add" class="easyui-linkbutton" iconCls="icon-add" plain="true">添加</a> <a href="#" data-method="update" class="easyui-linkbutton" iconCls="icon-edit" plain="true">修改</a> <a href="#" data-method="del" class="easyui-linkbutton" iconCls="icon-remove" plain="true">删除</a> <a href="#" data-method="delMore" class="easyui-linkbutton" iconCls="icon-remove" plain="true">批量删除</a> </div> <%--查询条--%> <form id="searchForm"> 名称:<input name="name" class="easyui-textbox" style="width:80px"> <a href="#" data-method="search" class="easyui-linkbutton" iconCls="icon-search">查询</a> </form> </div> <%--添加与修改的表单对话框--%> <div id="editDialog" class="easyui-dialog" title="功能编辑" style="width:950px;" data-options="iconCls:'icon-save',resizable:true,modal:true,closed:true"> <form id="editForm" method="post"> <input id="roleId" type="hidden" name="id"/> <table cellpadding="5" style="width: 100%"> <tr> <td> 名称: <input class="easyui-validatebox" type="text" name="name" data-options="required:true"/>     编码: <input class="easyui-validatebox" type="text" name="sn" data-options="required:true"/> </td> </tr> <tr> <td> <div id="cc" class="easyui-layout" style="width:99%;height:400px;"> <div data-options="region:'west'" style="width:440px;"> <table id="userPermissionGrid"></table> </div> <div data-options="region:'center'"> <table id="allPermissionGrid"></table> </div> </div> </td> </tr> </table> </form> <div style="text-align:center;padding:5px"> <a href="javascript:void(0)" class="easyui-linkbutton" data-method="save">提交</a> <a href="javascript:void(0)" class="easyui-linkbutton" data-method="closeDialog">关闭</a> </div> </div> </body> </html>
-
role.js
//右键支持增删改 function showMenu(e, rowIndex, rowData) { //选中这个行 $("#roleGrid").datagrid("selectRow", rowIndex); //第0个位置的面板不支持相应功能 e.preventDefault(); $('#gridMenu').menu('show', { left: e.pageX, top: e.pageY }); } //对权限进行相应的格式化 function permsFormat(val) { var permsStr = ""; for (let o of val) { permsStr += o.name + " "; } return permsStr; } $(function () { //获取常用组件(分页/查询条) var roleGrid = $("#roleGrid"); var searchForm = $("#searchForm"); var editDialog = $("#editDialog"); var editForm = $("#editForm"); //绑定事件 $("*[data-method]").on("click", function () { var method = $(this).data("method"); //动态调用 window.xiaji[method](); }); xiaji = { //查询 search() { //serializeObject:可以拿到form中的所有数据,封装成json对象 var params = searchForm.serializeObject(); roleGrid.datagrid("load", params); }, //关闭窗口 closeDialog() { editDialog.dialog("close"); }, //添加 add() { //让密码框显示 $("*[data-edit]").show(); $("*[data-edit] input").validatebox("enable"); //修改弹出框Title editDialog.dialog('setTitle', "添加"); //清空form中的数据 editForm.form("clear"); //清空左边grid的数据 loadData:加载本地数据,旧的行将被移除 userPermissionGrid.datagrid("loadData",[]); //打开弹出框(居中) editDialog.dialog("center").dialog("open"); }, //保存 save() { var url = "/role/save"; //获到id的值 var roleId = $("#roleId").val(); if (roleId) { url = "/role/update?cmd=_upd_"; } //easyui的form editForm.form('submit', { //提交路径 url: url, //提交前的操作 param这个参数加的属性都会向后台提示 onSubmit: function (param) { //拿到role(左边的grid)中的所有权限 var rows = userPermissionGrid.datagrid("getRows"); //遍历它,拼接出相应的结构 for(var i=0;i<rows.length;i++){ var row = rows[i]; param[`permissions[${i}].id`] = row.id; } // 做一些检查 return $(this).form('validate'); }, //data : {success:true/false,msg:xxx} success: function (data) { var result = JSON.parse(data); if (result.success) { roleGrid.datagrid("reload"); } else { $.messager.alert('错误', `失败了,失败原因::${result.msg}`, "error"); } //关闭弹出框 xiaji.closeDialog(); } }); }, //修改 update() { //获取到选中的那一行数据 var row = roleGrid.datagrid("getSelected"); //如果没有选中,给出提示后面的代码就不再执行 if (!row) { $.messager.alert('警告', '请选中再修改!', 'warning'); return; } //清空form中的数据 editForm.form("clear"); //让密码框失效且隐藏起来 $("*[data-edit]").hide(); $("*[data-edit] input").validatebox("disable"); //修改弹出框Title editDialog.dialog('setTitle', "修改"); //把结果进行回显 editForm.form("load", row); //打开弹出框(居中) editDialog.dialog("center").dialog("open"); //回显左边grid的数据 //获取当前选中的行的所有权限 //必需要拷备一个数组 不然会直接修改原数据 var permissions = [...row.permissions]; userPermissionGrid.datagrid("loadData",permissions); }, //删除 del() { //获取选中数据 var row = roleGrid.datagrid('getSelected'); //如果没有选中给提示 选中就是否确定删除 if (!row) { $.messager.alert('警告', '请选中再删除!', 'warning'); return; } else { $.messager.confirm('确认', '您确认想要删除记录吗?', function (r) { if (r) { //选是 确定删除 $.get("/role/delete", {id: row.id}, function (result) { if (result.success) { roleGrid.datagrid("reload"); } else { $.messager.alert('错误', `失败了,失败原因:${result.msg}`, "error"); } }) } }); } }, //批量删除 delMore() { //获取选中数据 var rows = roleGrid.datagrid('getSelections'); //如果没有选中给提示 选中就是否确定删除 if (rows.length == 0) { $.messager.alert('警告', '请选中再删除!', 'warning'); return; } else { $.messager.confirm('确认', '您确认想要删除记录吗?', function (r) { if (r) { //定义变量值 for (var i = 0; i < rows.length; i++) { //选是 确定删除 $.get("/role/delete", {id: rows[i].id}, function (result) { if (result.success) { roleGrid.datagrid("reload"); } else { $.messager.alert('错误', `失败了,失败原因:${result.msg}`, "error"); } }) } } }); } }, //添加权限 addPermission(index, row) {// index:点的行 row:这行的数据 //获取左边grid的所有数据 var rows = userPermissionGrid.datagrid("getRows"); //循环所有的行,如果重复传,就不执行后面代码 for (var o of rows) { if (o.id == row.id) { $.messager.show({ title: '提示', msg: '已添加,请勿重复操作!', timeout: 2000, showType: 'slide', style: { right: '', top: document.body.scrollTop + document.documentElement.scrollTop, bottom: '' } }); return; } } userPermissionGrid.datagrid("appendRow", row); }, //删除权限 removePermission(index, row) { userPermissionGrid.datagrid("deleteRow", index); } }; //单独获取两个rid var userPermissionGrid = $('#userPermissionGrid'); var allPermissionGrid = $('#allPermissionGrid'); //创建当前角色对应的权限(左)grid userPermissionGrid.datagrid({ fit: true, fitColumns: true, singleSelect: true, columns: [[ {field: 'name', title: '名称', width: 100}, {field: 'sn', title: '权限', width: 100}, {field: 'url', title: '资源', width: 100} ]], onDblClickRow: xiaji.removePermission }); //创建当前角色对应的权限(右)grid allPermissionGrid.datagrid({ fit: true, url: '/permission/page', fitColumns: true, singleSelect: true, pagination: true, columns: [[ {field: 'name', title: '名称', width: 100}, {field: 'sn', title: '权限', width: 100}, {field: 'url', title: '资源', width: 100} ]], onDblClickRow: xiaji.addPermission }); //按键扩展支持 $(document).bind('keydown', 'del', xiaji.del); $(document).bind('keydown', 'Shift+1', xiaji.add); $(document).bind('keydown', 'Shift+2', xiaji.update); $(document).bind('keydown', 'Shift+3', xiaji.delMore); });