上一篇文章Filter(过滤器)常见应用(三)——权限管理系统(二)已经开发好了web层的一大部分,做的所有这些工作都是为了这一步——权限实现,是时候使用Filter实现URL级别的权限认证了。
权限管理系统的设计和分析
开发web层
权限实现
现在我们来编写一个过滤器来实现URL级别的权限认证,要在cn.itcast.web.filter包下创建一个Filter——SecurityFilter.java。
编写这个过滤器,还算是有一点麻烦,但是我们按照以下步骤慢慢来,相信我们自己一定能写出来。
- 检查用户是否已登录。
- 没登录,登录去。
- 得到用户想访问的资源。
- 得到访问该资源需要的权限。
- 判断用户是否有相应权限。
- 没有权限,则提示用户权限不足,联系管理员。
- 如果有,则则放行。
写出来的SecurityFilter过滤器,代码应该就是这样的:
public class SecurityFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
User user = (User) request.getSession().getAttribute("user");
if (user == null) {
request.setAttribute("message", "请先登录!!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
String uri = request.getRequestURI();
SecurityService service = new SecurityService();
Resource r = service.findResource(uri);
if (r == null) {
chain.doFilter(request, response);
return;
}
Privilege required_Privilege = r.getPrivilege();
List<Privilege> list = service.getUserAllPrivilege(user.getId());
if (!list.contains(required_Privilege)) {
request.setAttribute("message", "对不起,您没有权限,请联系管理员!!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
代码虽然写出来了,但有一些细节如若我们不注意,那么对这个过滤器的理解依然是不深刻的,这是要不得的。特来对这些细节详加说明。
- 细节一:在得到访问该资源需要的权限这一步中,我们根据URI查找出的资源,一定得进行非空检查。
因为对于你要访问的资源,如若我在权限管理系统里面没有说访问这个资源需要权限,也即这个资源不需要被权限系统控制(只有被权限系统控制的资源,在数据库表里面才有),则从数据库表里面查询出来的资源必然为null,如若为null,代表这个资源不受权限系统控制。 -
细节二:在判断用户是否有相应权限这一步中,用到了这样的代码:
if (!list.contains(required_Privilege)) {
blabla......
}
学过java基础的人应该知道List集合的contains方法内部调用的equals方法,我们自定义的Privilege对象没有重写equals方法(就连hashCode方法也一起重写算了,因为Eclipse会自动帮我们重写这两个方法),就这样比较是不行的。因此,应在Privilege类重写这两个方法。
public class Privilege {
private String id;
private String name;
private String description;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Privilege other = (Privilege) obj;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
得出结论:以后自定义的对象只要涉及到比较,一定要重写hashCode()和equals()这2个方法。
编写好这样的过滤器之后,就应在web.xml文件中配置它。
<filter>
<filter-name>SecurityFilter</filter-name>
<filter-class>cn.itcast.web.filter.SecurityFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SecurityFilter</filter-name>
<url-pattern>/manager/*</url-pattern>
</filter-mapping>
这样就可以拦截下manager映射目录下的所有请求,将请求都归在manager映射目录中,可以方便于我们的管理。
接下来,我们就在WebRoot根目录下新建一个用户登录的页面——login.jsp。
login.jsp页面的内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/UserServlet?method=login" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="text" name="password"><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
当用户输入用户名和密码之后,点击登录按钮,请求同样也应交给UserServlet,又由于请求URL后面的method参数的值是login,因此要把请求派发给login方法处理,这样UserServlet的代码就应该为:
public class UserServlet extends HttpServlet {
private SecurityService service = new SecurityService();
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String method = request.getParameter("method");
if ("getAll".equals(method)) {
getAll(request, response);
}
if ("addUI".equals(method)) {
addUI(request, response);
}
if ("add".equals(method)) {
add(request, response);
}
if ("forUpdateUserRoleUI".equals(method)) {
forUpdateUserRoleUI(request, response);
}
if ("updateRole".equals(method)) {
updateRole(request, response);
}
if ("login".equals(method)) {
login(request, response);
}
}
private void getAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<User> list = service.getAllUser();
request.setAttribute("list", list);
request.getRequestDispatcher("/security/listuser.jsp").forward(request, response);
}
private void addUI(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/security/adduser.jsp").forward(request, response);
}
private void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
User user = WebUtils.request2Bean(request, User.class);
user.setId(UUID.randomUUID().toString());
service.addUser(user);
request.setAttribute("message", "添加成功!!!");
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "添加失败!!!");
}
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
private void forUpdateUserRoleUI(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userid = request.getParameter("id");
User user = service.findUser(userid);
List<Role> list = service.getAllRole();
request.setAttribute("user", user);
request.setAttribute("list", list);
request.getRequestDispatcher("/security/updateUserRole.jsp").forward(request, response);
}
private void updateRole(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
String userid = request.getParameter("userid");
String[] rids = request.getParameterValues("rid");
service.updateUserRole(userid, rids);
request.setAttribute("message", "更新成功!!!");
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "更新失败!!!");
}
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
private void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = service.findUser(username, password);
if (user == null) {
request.setAttribute("message", "用户名或密码错误!!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
request.getSession().setAttribute("user", user);
response.sendRedirect("/day20/index.jsp");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
用户成功登录之后,就要跳转到网站首页上面去,前面我们就已经写过index.jsp页面了,现在只须修改一下即可。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="/itcast" prefix="itcast" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
欢迎您:${user.username } <a href="${pageContext.request.contextPath }/UserServlet?method=logout">注销</a>
<br/><br/>
<a href="${pageContext.request.contextPath }/login.jsp">登录</a>
<br/><br/>
<a href="/day20/manager/Servlet1">添加分类</a>
<a href="/day20/manager/Servlet2">删除分类</a>
<a href="/day20/manager/Servlet3">修改分类</a>
<a href="/day20/manager/Servlet4">查找分类</a>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
在修改后的index.jsp页面中我们又添加了一个用户注销的功能,当用户点击注销超链接时,请求同样也应交给UserServlet,又由于请求URL后面的method参数的值是logout,因此要把请求派发给logout方法处理,这样UserServlet的代码就应该为:
public class UserServlet extends HttpServlet {
private SecurityService service = new SecurityService();
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String method = request.getParameter("method");
if ("getAll".equals(method)) {
getAll(request, response);
}
if ("addUI".equals(method)) {
addUI(request, response);
}
if ("add".equals(method)) {
add(request, response);
}
if ("forUpdateUserRoleUI".equals(method)) {
forUpdateUserRoleUI(request, response);
}
if ("updateRole".equals(method)) {
updateRole(request, response);
}
if ("login".equals(method)) {
login(request, response);
}
if ("logout".equals(method)) {
logout(request, response);
}
}
private void getAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<User> list = service.getAllUser();
request.setAttribute("list", list);
request.getRequestDispatcher("/security/listuser.jsp").forward(request, response);
}
private void addUI(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/security/adduser.jsp").forward(request, response);
}
private void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
User user = WebUtils.request2Bean(request, User.class);
user.setId(UUID.randomUUID().toString());
service.addUser(user);
request.setAttribute("message", "添加成功!!!");
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "添加失败!!!");
}
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
private void forUpdateUserRoleUI(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userid = request.getParameter("id");
User user = service.findUser(userid);
List<Role> list = service.getAllRole();
request.setAttribute("user", user);
request.setAttribute("list", list);
request.getRequestDispatcher("/security/updateUserRole.jsp").forward(request, response);
}
private void updateRole(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
String userid = request.getParameter("userid");
String[] rids = request.getParameterValues("rid");
service.updateUserRole(userid, rids);
request.setAttribute("message", "更新成功!!!");
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "更新失败!!!");
}
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
private void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = service.findUser(username, password);
if (user == null) {
request.setAttribute("message", "用户名或密码错误!!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
request.getSession().setAttribute("user", user);
response.sendRedirect("/day20/index.jsp");
}
private void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.getSession().removeAttribute("user");
response.sendRedirect("/day20/index.jsp");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
连载三篇文章,不得不佩服自己了,终于一个简陋的权限管理系统出炉了,接受大家的检测。
权限实现的细节
即使我们做出了这样一个简陋的权限管理系统,但还有一些细节需要我们注意,我这就来娓娓道来。
第一,有些人在做权限管理系统时,权限管理系统比较简单,可能就没有角色这个概念,直接就说某个用户拥有某个权限,用户和权限还是多对多的关系,即一个用户可以拥有多个权限,一个权限可以属于多个用户。但这种设计只能做比较简单的权限管理系统,如果一复杂,就会很麻烦,麻烦在什么地方呢?试想一下,现在网站有10个用户,有100个权限值,现在我要给这10个用户都授予100个权限的话,仅在页面里面就要点死人,选中某个用户,给他勾100个权限……这样的话,就要勾1000次,如果使用正规一点的模型的话,就很简单,只需要把这100个权限授予到一个角色上,再把角色给这10个用户就OK了。
第二,我现在做的权限管理方案属于无侵入式的权限管理方案,即没有侵入到业务代码中去。
第三,我现在做的权限管理方案属于粗粒度的拦截方案,还有一种拦截方案,即动态代理+注解的拦截方案(细粒度的拦截,可以拦截到某个具体业务方法上)。
第四,用户要是登录了,若他有几个权限,就可以看到几个超链接,若他没有权限,那这几个超链接就看不见。这个就属于页面显示时候的处理了。我会写一个自定义标签,里面所有的超链接我都使用自定义标签套起来,我这个自定义标签会检查一下用户有没有权限,用户有权限,我就输出标签体,即把这个超链接输出,没有就不输出。
口说无凭,现在我们来写代码实现第四个细节。按照第四个细节的说法,应写一个自定义标签,所以我们在cn.itcast.web.tag包下创建一个类——PermissionTag.java。
该类须继承SimpleTagSupport,然后再重写doTag方法。
public class PermissionTag extends SimpleTagSupport {
private String value;
public void setValue(String value) {
this.value = value;
}
@Override
public void doTag() throws JspException, IOException {
PageContext pageContext = (PageContext) this.getJspContext();
HttpSession session = pageContext.getSession();
User user = (User) session.getAttribute("user");
if (user != null) {
SecurityService service = new SecurityService();
List<Privilege> privileges = service.getUserAllPrivilege(user.getId());
boolean b = false;
for (Privilege p : privileges) {
if (p.getName().equals(value)) {
b = true;
break;
}
}
if (b) {
this.getJspBody().invoke(null);
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
在得到用户所有权限的时候,注意我们是new出了这个业务对象,如果你不想new出这个业务对象的话,那也行,这时,用户登录进来了你就要把用户的所有权限找出来,即用户对象里面要有一个集合维护他所有的权限,但是我们不想改动以前的设计了,索性干脆就调用service去完成,但这样做有点不太好,即Web层和业务逻辑层绑定在一起了。
接下来我们就要在WEB-INF目录下新建一个itcast.tld文件,然后在itcast.tld文件中添加对该标签处理类的描述,如下:
itcast.tld文件的内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<description>JSTL 1.1 core library</description>
<display-name>JSTL core</display-name>
<tlib-version>1.1</tlib-version>
<short-name>c</short-name>
<uri>/itcast</uri>
<tag>
<name>permission</name>
<tag-class>cn.itcast.web.tag.PermissionTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
这个时候我们就可以在首页导入并使用自定义标签了。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="/itcast" prefix="itcast" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
欢迎您:${user.username } <a href="${pageContext.request.contextPath }/UserServlet?method=logout">注销</a>
<br/><br/>
<a href="${pageContext.request.contextPath }/login.jsp">登录</a>
<br/><br/>
<itcast:permission value="添加分类">
<a href="/day20/manager/Servlet1">添加分类</a>
</itcast:permission>
<itcast:permission value="删除分类">
<a href="/day20/manager/Servlet2">删除分类</a>
</itcast:permission>
<itcast:permission value="修改分类">
<a href="/day20/manager/Servlet3">修改分类</a>
</itcast:permission>
<itcast:permission value="查找分类">
<a href="/day20/manager/Servlet4">查找分类</a>
</itcast:permission>
<itcast:permission value="删除商品">
<a href="/day20/manager/Servlet5">删除商品</a>
</itcast:permission>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
提示:标签控制用户需要有添加分类的权限值,才可以看到超链接。
最后我多一嘴,有时候权限有可能是一棵树,Privilege类可能还需要这样一个属性private String module;
来记住权限属于哪个模块。关于这点,我理解不够,无法详细记录了。