之前写过一篇博客:拦截器实现基于Url的权限管理,文章中讲解了怎么用拦截器实现url权限认证,这仅仅是权限管理的一部分。今天这篇博客就来说说一个项目完整的权限认证流程。
1、 准备权限数据
服务器启动时,通过InitListener监听器,将数据库中的权限信息查询出来,并放到ServletContext中。其中权限信息分两类:顶层菜单(系统管理)和其他权限信息url。ServletContext中放一些共享数据。
public class InitListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
// 获取容器与相关的Service对象
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
PrivilegeService privilegeService = (PrivilegeService) ac.getBean("privilegeServiceImpl");
// 准备数据:topPrivilegeList
List<Privilege> topPrivilegeList = privilegeService.findTopList();
sce.getServletContext().setAttribute("topPrivilegeList", topPrivilegeList);
System.out.println("------------> 已准备数据 <------------");
// 准备数据:allPrivilegeUrls
Collection<String> allPrivilegeUrls = privilegeService.getAllPrivilegeUrls();
sce.getServletContext().setAttribute("allPrivilegeUrls", allPrivilegeUrls);
System.out.println("------------> 已准备数据allPrivilegeUrls <------------");
}
public void contextDestroyed(ServletContextEvent arg0) {
}
}
2、编写拦截器CheckPrivilegeInterceptor,拦截用户的所有请求
public class CheckPrivilegeInterceptor extends AbstractInterceptor {
public String intercept(ActionInvocation invocation) throws Exception {
// 获取信息
User user = (User) ActionContext.getContext().getSession().get("user"); // 当前登录用户
String namespace = invocation.getProxy().getNamespace();
String actionName = invocation.getProxy().getActionName();
String privUrl = namespace + actionName; // 对应的权限URL
// 如果未登录
if (user == null) {
if (privUrl.startsWith("/user_login")) { // "/user_loginUI", "/user_login"
// 如果是去登录,就放行
return invocation.invoke();
} else {
// 如果不是去登录,就转到登录页面
return "loginUI";
}
}
// 如果已登 录,就判断权限
else {
if (user.hasPrivilegeByUrl(privUrl)) {
// 如果有权限,就放行
return invocation.invoke();
} else {
// 如果没有权限,就转到提示页面
return "noPrivilegeError";
}
}
}
}
3、其中User实体提供两个权限判断方法
hasPrivilegeByName( ):判断本用户是否有指定名称的权限,主要查询用户拥有哪个菜单权限,从而显示到页面
hasPrivilegeByUrl( ):判断本用户是否有指定URL的权限,主要是界面的增删改查权限url
/**
* 判断本用户是否有指定名称的权限
* 主要查询用户拥有哪个菜单权限,从而显示到页面
* @param name,菜单名称
* @return
*/
public boolean hasPrivilegeByName(String name)
{
// 超级管理有所有的权限
if (isAdmin())
{
return true;
}
// 普通用户要判断是否含有这个权限
for (Role role : roles)
{
for (Privilege priv : role.getPrivileges())
{
if (priv.getName().equals(name))
{
return true;
}
}
}
return false;
}
------------------------华丽的分割线-------------------------
/*
* 判断本用户是否有指定URL的权限
*
* @param privUrl 主要是界面的增删改查权限url
* @return
*/
public boolean hasPrivilegeByUrl(String privUrl) {
// 超级管理有所有的权限
if (isAdmin()) {
return true;
}
// >> 去掉后面的参数
int pos = privUrl.indexOf("?");
if (pos > -1) {
privUrl = privUrl.substring(0, pos);
}
// >> 去掉UI后缀
if (privUrl.endsWith("UI")) {
privUrl = privUrl.substring(0, privUrl.length() - 2);
}
// 如果本URL不需要控制,则登录用户就可以使用
//allPrivilegeUrls是在监听器中添加的
Collection<String> allPrivilegeUrls = (Collection<String>) ActionContext.getContext().getApplication().get("allPrivilegeUrls");
if (!allPrivilegeUrls.contains(privUrl)) {
return true;
} else {
// 普通用户要判断是否含有这个权限
for (Role role : roles) {
for (Privilege priv : role.getPrivileges()) {
if (privUrl.equals(priv.getUrl())) {
return true;
}
}
}
return false;
}
}
4、 用户进行登录操作
首先进行登录校验,不成功给出提示;成功之后,将用户的信息放入到session中
/** 登录 */
public String login() throws Exception
{
User user = userService.findByLoginNameAndPassword(
model.getLoginName(), model.getPassword());
if (user == null)
{
addFieldError("login", "用户名或密码不正确!");
return "loginUI";
}
else
{
// 登录用户,将登陆用户放入session
ActionContext.getContext().getSession().put("user", user);
return "toIndex";
}
}
5、用户登录之后显示首页的菜单信息
页面显示菜单信息,需要调用session.user.hasPrivilegeByName(name)方法判断是否有权限显示,其中topPrivilegeList是在初始化监听器时放入的顶级菜单信息。
<ul id="MenuUl">
<%-- 显示一级菜单,在初始化时用Listener添加到ServletContextEvent中 --%>
<s:iterator value="#application.topPrivilegeList">
<s:if test="#session.user.hasPrivilegeByName(name)">
<li class="level1">
<div onClick="menuClick(this);" class="level1Style">
<img src="style/images/MenuIcon/${id}.gif" class="Icon" />
${name}
</div>
<ul style="" class="MenuLevel2" id="aa">
<%-- 显示二级菜单 --%>
<s:iterator value="children">
<s:if test="#session.user.hasPrivilegeByName(name)">
<li class="level2">
<div class="level2Style">
<img src="style/images/MenuIcon/menu_arrow_single.gif" />
<a target="right" href="${pageContext.request.contextPath}${url}.action"> ${name}</a>
</div>
</li>
</s:if>
</s:iterator>
</ul>
</li>
</s:if>
</s:iterator>
</ul>
6、用户点击某个页面
用户点击某个页面,权限判断需要有两步,一是页面显示权限,而是url访问权限。
①修改Struts2的页面显示类AnchorTag ,添加判断:如果没有权限的话,就不显示该标签,让用户根本看不到。比如添加按钮等。
/*
* $Id: AnchorTag.java 768855 2009-04-27 02:09:35Z wesw $
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.views.jsp.ui;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import org.apache.struts2.components.Anchor;
import org.apache.struts2.components.Component;
import cn.itcast.oa.domain.User;
import com.opensymphony.xwork2.util.ValueStack;
/**
* @see Anchor
*/
public class AnchorTag extends AbstractClosingTag {
private static final long serialVersionUID = -1034616578492431113L;
protected String href;
protected String includeParams;
protected String scheme;
protected String action;
protected String namespace;
protected String method;
protected String encode;
protected String includeContext;
protected String escapeAmp;
protected String portletMode;
protected String windowState;
protected String portletUrlType;
protected String anchor;
protected String forceAddSchemeHostAndPort;
@Override
public int doEndTag() throws JspException {
// 当前登录用户
User user = (User) pageContext.getSession().getAttribute("user");
// 当前准备显示的链接对应的权限URL
// >> 在开头加上'/'
String privUrl = "/" + action;
if (user.hasPrivilegeByUrl(privUrl)) {
return super.doEndTag(); // 正常的生成并显示超链接 标签,并继续执行页面中后面的代码
} else {
return EVAL_PAGE; // 不生成与显示超链接 标签,只是继续执行页面中后面的代码
}
}
public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
return new Anchor(stack, req, res);
}
protected void populateParams() {
super.populateParams();
Anchor tag = (Anchor) component;
tag.setHref(href);
tag.setIncludeParams(includeParams);
tag.setScheme(scheme);
tag.setValue(value);
tag.setMethod(method);
tag.setNamespace(namespace);
tag.setAction(action);
tag.setPortletMode(portletMode);
tag.setPortletUrlType(portletUrlType);
tag.setWindowState(windowState);
tag.setAnchor(anchor);
if (encode != null) {
tag.setEncode(Boolean.valueOf(encode).booleanValue());
}
if (includeContext != null) {
tag.setIncludeContext(Boolean.valueOf(includeContext).booleanValue());
}
if (escapeAmp != null) {
tag.setEscapeAmp(Boolean.valueOf(escapeAmp).booleanValue());
}
if (forceAddSchemeHostAndPort != null) {
tag.setForceAddSchemeHostAndPort(Boolean.valueOf(forceAddSchemeHostAndPort).booleanValue());
}
}
public void setHref(String href) {
this.href = href;
}
public void setEncode(String encode) {
this.encode = encode;
}
public void setIncludeContext(String includeContext) {
this.includeContext = includeContext;
}
public void setEscapeAmp(String escapeAmp) {
this.escapeAmp = escapeAmp;
}
public void setIncludeParams(String name) {
includeParams = name;
}
public void setAction(String action) {
this.action = action;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public void setMethod(String method) {
this.method = method;
}
public void setScheme(String scheme) {
this.scheme = scheme;
}
public void setValue(String value) {
this.value = value;
}
public void setPortletMode(String portletMode) {
this.portletMode = portletMode;
}
public void setPortletUrlType(String portletUrlType) {
this.portletUrlType = portletUrlType;
}
public void setWindowState(String windowState) {
this.windowState = windowState;
}
public void setAnchor(String anchor) {
this.anchor = anchor;
}
public void setForceAddSchemeHostAndPort(String forceAddSchemeHostAndPort) {
this.forceAddSchemeHostAndPort = forceAddSchemeHostAndPort;
}
}
② 拦截用户输入的url,用户可能直接在浏览器栏输入添加的url,这时候也要拦截并判断是否有权限访问,没有的话跳转到无权访问界面。
判断方法就在第二步的CheckPrivilegeInterceptor 拦截器中
// 如果未登录
if (user == null) {
if (privUrl.startsWith("/user_login")) { // "/user_loginUI", "/user_login"
// 如果是去登录,就放行
return invocation.invoke();
} else {
// 如果不是去登录,就转到登录页面
return "loginUI";
}
}
// 如果已登 录,就判断权限
else {
if (user.hasPrivilegeByUrl(privUrl)) {
// 如果有权限,就放行
return invocation.invoke();
} else {
// 如果没有权限,就转到提示页面
return "noPrivilegeError";
}
}
小结
基于URL的权限验证的整个流程大概也就这样了,梳理完感觉清晰很多。不过这种基于URL拦截的方式实现权限认证也太繁琐了,在上面也看到了,需要咱们完成很多工作,不利于项目的维护。不过后面会讲解一种高大上,并且快速简单的权限开发方式Shiro框架,拭目以待。