用户权限管理之权限过滤

来源:互联网 发布:阴茎延长手术死亡知乎 编辑:程序博客网 时间:2024/06/15 19:40

1.前言

  之前有写过一篇小记,当时的用户权限管理十分简陋,只通过判断角色名来进行权限控制.属于0.5版本的权限管理.现在,在用户权限管理模块中,除了之前的用户表,角色表,权限表,日志表4大主表外(还有关联表),又增加了url资源表和行为表.为此,之前的low版本的权限管理不可用了,需要一个新的细粒度的权限过滤.


2.思路

  最初的设想是url资源表和行为表中存储对应请求需要的权限,权限管理过滤器则对请求的url和用户拥护的权限去数据库中匹配,符合条件则通过,不符合则跳转无权限页面.
不过考虑到访问页面的频繁和ajax请求的频繁度,这样的做法会提高数据库的压力.由于url资源表和行为表的数据相对较少,所以考虑在项目启动时将信息存储在内存.这样既提高了速度,又减轻了数据库的压力.由于将数据存在了内存中,当数据库的信息修改时就存在了数据不一致的问题,对此我编写了一个管理类来处理这个问题.

3.代码

3.1 URLRoleFilter.java

public class URLRoleFilter implements Filter {private String redirectPage;private String unCheckURL;@Overridepublic void destroy() {}@Overridepublic void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse)resp;String servletPath = request.getServletPath();String requestMethod = request.getMethod();String requestType = request.getHeader("X-Requested-With");List<String> unCheckList = Arrays.asList(unCheckURL.split(","));if(null!=unCheckList && unCheckList.contains(servletPath)) {chain.doFilter(request, response);return;}UserLoginInfoModel userLogin = SessionUtil.getUserLogin(request);String roleId = userLogin.getRoleId()==null?"":userLogin.getRoleId();//当前用户无任何角色,且不是注销操作时,跳转无权限页面if(StringX.isEmpty(roleId)) {//&& !servletPath.contains("/security/signOut.do")if(!StringX.isEmpty(requestType) && "XMLHttpRequest".equals(requestType)) {//ajax请求JSONObject result = new JSONObject();result.accumulate("success", false);result.accumulate("errUrl", request.getContextPath() + redirectPage);try {response.getWriter().print(result.toString());} catch (IOException e) {ARE.getLog().error(e);}}else if("POST".equals(requestMethod)) {response.setStatus(302);//临时定向响应码  response.setHeader("Location", request.getContextPath() + redirectPage);//代表转向的地址  }else {response.sendRedirect(request.getContextPath() + redirectPage);}return;}if(!UrlAndActionManager.getInstence().match(servletPath, userLogin)) {//权限匹配不通过if(!StringX.isEmpty(requestType) && "XMLHttpRequest".equals(requestType)) {//ajax请求JSONObject result = new JSONObject();result.accumulate("success", false);result.accumulate("errUrl", request.getContextPath() + redirectPage);try {response.getWriter().print(result.toString());} catch (IOException e) {ARE.getLog().error(e);}}else if("POST".equals(requestMethod)) {response.setStatus(302);//临时定向响应码  response.setHeader("Location", request.getContextPath() + redirectPage);//代表转向的地址  }else {response.sendRedirect(request.getContextPath() + redirectPage);}return;}chain.doFilter(request, response);}@Overridepublic void init(FilterConfig cfg) throws ServletException {ServletContext context = cfg.getServletContext();redirectPage = context.getInitParameter("noRolePage");unCheckURL = context.getInitParameter("unCheckURL");}}

  这是过滤器的代码,基本上就是之前的版本,只是在进行权限判断的时候,不通过角色来判断,而是使用了一个管理类来判断.一般情况下这个管理类就是从内存中来比较权限的.

3.2 InitUrlAndActionManagerServlet.java

public class InitUrlAndActionManagerServlet extends HttpServlet {private static final long serialVersionUID = 1L;public void init(ServletConfig config) throws ServletException {ARE.getLog().info("资源权限管理器初始化开始");UrlAndActionManager.getInstence();ARE.getLog().info("资源权限管理器初始化完成");}}

这个类的作用是在项目启动后把url资源表数据和行为表数据加载到内存中(在web.xml中配置一下优先级).

3.3 UrlAndActionManager.java

public class UrlAndActionManager extends Thread{private ReadWriteLock lock;  //读写锁private boolean updateFlag; //管理类map是否正在更新 标志位private Map<String,String> urlMap; //存放url表数据private Map<String,String> actionMap; //存放行为表数据private UrlDao urlDao;  //url表操作类private ActionDao actionDao;  //行为表操作类private boolean dbChangeFlag; //数据库信息是否改变 标志位private static class UrlAndActionManagerHolder{private static final UrlAndActionManager INSTANCE = new UrlAndActionManager();}private UrlAndActionManager(){lock = new ReentrantReadWriteLock(false);updateFlag = false;urlMap = new HashMap<String,String>();actionMap = new HashMap<String,String>();urlDao = new UrlDao();actionDao = new ActionDao();dbChangeFlag = false;init();}public static UrlAndActionManager getInstence() {return UrlAndActionManagerHolder.INSTANCE;}private void init(){lock.writeLock().lock();urlDao.loadUrls(urlMap);actionDao.loadActions(actionMap);ARE.getLog().info("urlMap :");Set set1 = urlMap.keySet();for (Object object : set1) {ARE.getLog().info(object + " : " + urlMap.get(object));}ARE.getLog().info("actionMap :");Set set2 = actionMap.keySet();for (Object object : set2) {ARE.getLog().info(object + " : " + actionMap.get(object));}lock.writeLock().unlock();this.setName("定期监视资源表更新情况线程");this.start();}//用户权限匹配public boolean match(String postUrl, UserLoginInfoModel userLogin) {boolean result = false;if(null == userLogin) {return result;}String privileges = userLogin.getPrivilegeId();if(StringX.isEmpty(privileges)) {privileges = ",,";}else {privileges = "," + privileges + ",";}ARE.getLog().debug("postUrl : " + postUrl);ARE.getLog().debug("userId : " + userLogin.getUserId());ARE.getLog().debug("privileges : " + privileges);if(updateFlag) {//当前正在更新map时,从数据库获取权限进行比较ARE.getLog().debug("从数据库中匹配权限");String permission = null;permission = urlDao.findPermissionByUrlId(postUrl);if(null == permission) {permission = actionDao.findPermissionByActionId(postUrl);if((null != permission && privileges.contains(","+permission+",")) || "".equals(permission)) {result = true;}}else {if(null != permission && privileges.contains(","+permission+",")) {result = true;}}}else {//当前不在更新map时,从map中获取权限进行比较ARE.getLog().debug("从内存中匹配权限");lock.readLock().lock();String url = null;url = urlMap.get(postUrl);if(null == url) {//请求为 请求行为资源String action = null;action = actionMap.get(postUrl);if(null == action) {result = false;}else {if("".equals(action)) {result = true;}else if(privileges.contains(","+action+",")) {result = true;}else {result = false;}}}else {//请求为 请求url资源if("".equals(url)) {result = true;}else if(privileges.contains(","+url+",")) {result = true;}else {result = false;}}lock.readLock().unlock();}return result;}@Overridepublic void run() {while(true) {try {Thread.sleep(1000*60*5);//每隔5分钟执行一次} catch (InterruptedException e) {ARE.getLog().debug(e);}ARE.getLog().info(this.getName() + "  -  开始检测资源权限是否更新");lock.writeLock().lock();if(!updateFlag) {updateFlag = true;urlDao.loadPermissionUpdateDatas(urlMap);actionDao.loadPermissionUpdateDatas(actionMap);updateFlag = false;}lock.writeLock().unlock();ARE.getLog().info(this.getName() + "  -  完成检测资源权限是否更新");}}public void dbChange() {dbChangeFlag = true;}}

这个类就是管理类了,它采用了单例模式,在它第一次生成的时候,会去数据库中加载url表和行为表的数据到对应map中,之后便会开启一个线程,通过一个标志位定期检查数据库数据是否更新,若更新则需要对数据库中的数据重新加载,这时线程会对另一个标志位控制,告知想要读取map中数据的程序当前map正在更新,请直接访问数据库,线程完成更新后再置回该标志位.这个类中用到了读写锁,为何不用synchronized呢,这是由于synchronized会对读加锁,由于大多数情况都是读的情况,所以synchronized对效率的影响比较大,而读写锁的读锁之间不互斥,读锁和写锁之间互斥这样能很好的满足我的需求.该管理类对于数据库更新的情况并没有进行锁控制,只使用了标志位,原因是数据库的更新和用户的访问是分开的,用户优先访问内存,而管理类更新map是通过监控线程来控制的,在设计的时候对实时更新的要求不高,所以不需要锁控制(一次检查更新情况时没有更新上,下一次检查时就会更新上).

3.4 ActionDao.java

public class ActionDao {public List<String> getActionIds() {Connection conn = null;PreparedStatement statement = null;try {conn = ARE.getDBConnection("craw");List<String> result = new ArrayList<>();String sql = "select actionId from security_manage_action";statement = conn.prepareStatement(sql);ResultSet resultSet = statement.executeQuery();if(null != resultSet) {while(resultSet.next()) {String actionId = resultSet.getString("actionId");result.add(actionId);}}resultSet.close();return result;} catch(SQLException e) {ARE.getLog().error(e);return null;} finally {try {if(null != statement) {statement.close();statement = null;}if(null != conn) {conn.close();conn = null;}} catch(SQLException e) {ARE.getLog().error(e);}}}public void initAtionRecords(List<String> loads) {Connection conn = null;PreparedStatement statement = null;PreparedStatement statement2 = null;try {conn = ARE.getDBConnection("craw");conn.setAutoCommit(false);if(null == loads || 0 == loads.size()) {//加载到项目中配置的url数组为空,将数据库中数据删除String sqlAllDel = "delete from security_manage_action";statement = conn.prepareStatement(sqlAllDel);statement.executeQuery();conn.commit();return;}List<String> olds = getActionIds();if(null != olds) {List<String> contains = new ArrayList<>();for (String string : olds) {if(loads.contains(string)) {contains.add(string);}}loads.removeAll(contains); //数据库中没有,项目中存在的路由olds.removeAll(contains); //数据库中有,项目中不存在的路由String sqlAdd = "INSERT security_manage_action(actionId,actionName,permission,updateVersion) VALUES(?,'','',1)";String sqlDel = "DELETE FROM security_manage_action where actionId=?";if(loads.size() > 0) {statement = conn.prepareStatement(sqlAdd);for (String actionId : loads) {statement.setString(1, actionId);statement.addBatch();}}if(olds.size() > 0) {statement2 = conn.prepareStatement(sqlDel);for (String actionId : olds) {statement2.setString(1, actionId);statement2.addBatch();}}if(loads.size() > 0 || olds.size() > 0) {if(null != statement) {statement.executeBatch();}if(null != statement2) {statement2.executeBatch();}conn.commit();}}} catch(SQLException e) {try {conn.rollback();} catch (SQLException e1) {ARE.getLog().error(e1);}ARE.getLog().error(e);} finally {try {if(null != statement2) {statement2.close();statement2 = null;}if(null != statement) {statement.close();statement = null;}if(null != conn) {conn.setAutoCommit(true);conn.close();conn = null;}} catch(SQLException e) {ARE.getLog().error(e);}}}public boolean loadActions(Map<String,String> actionMap) {boolean result = true;Connection conn = null;PreparedStatement statement = null;try {conn = ARE.getDBConnection("craw");String sql = "select actionId,permission from security_manage_action";statement = conn.prepareStatement(sql);ResultSet resultSet = statement.executeQuery();if(null == resultSet) {result = false;}else {while(resultSet.next()) {actionMap.put(resultSet.getString("actionId"),resultSet.getString("permission"));}resultSet.close();}} catch (SQLException e) {result = false;ARE.getLog().error(e);} finally {try {if(null != statement) {statement.close();statement = null;}if(null != conn) {conn.close();conn = null;}} catch(SQLException e) {ARE.getLog().error(e);}}return result;}public boolean loadPermissionUpdateDatas(Map<String,String> actionMap) {boolean result = true;Connection conn = null;PreparedStatement statement = null;PreparedStatement statement2 = null;try {conn = ARE.getDBConnection("craw");conn.setAutoCommit(false);//查找所有更新permission的数据String sql = "select actionId,permission,updateVersion from security_manage_action where isUpdate='Y'";statement = conn.prepareStatement(sql);ResultSet resultSet = statement.executeQuery();//将isUpdate置为NString sql2 = "update security_manage_action set isUpdate='N',updateVersion=updateVersion+1 where actionId=? and updateVersion=? and  isUpdate='Y'";int count = 0;statement2 = conn.prepareStatement(sql2);if(null == resultSet) {result = false;}else {while(resultSet.next()) {count++;actionMap.put(resultSet.getString("actionId"),resultSet.getString("permission"));statement2.setString(1, resultSet.getString("actionId"));statement2.setInt(2, resultSet.getInt("updateVersion"));statement2.addBatch();}resultSet.close();}if(count > 0) {statement2.executeUpdate();}conn.commit();} catch (SQLException e) {if(null != conn) {try {conn.rollback();} catch (SQLException e1) {ARE.getLog().error(e1);;}}result = false;ARE.getLog().error(e);} finally {try {if(null != statement2) {statement2.close();statement2 = null;}if(null != statement) {statement.close();statement = null;}if(null != conn) {conn.setAutoCommit(true);conn.close();conn = null;}} catch(SQLException e) {ARE.getLog().error(e);}}return result;}public String findPermissionByActionId(String actionId) {String result = null;Connection conn = null;PreparedStatement statement = null;try {conn = ARE.getDBConnection("craw");String sql = "select permission from security_manage_action where actionId=?";statement = conn.prepareStatement(sql);statement.setString(1, actionId);ResultSet resultSet = statement.executeQuery();if(null != resultSet) {resultSet.last();int count = resultSet.getRow();if(count > 0) {result = resultSet.getString("permission");}resultSet.close();}if(null == result) {result = "";}} catch(SQLException e) {ARE.getLog().error(e);} finally {try {if(null != statement) {statement.close();statement = null;}if(null != conn) {conn.close();conn = null;}} catch(SQLException e) {ARE.getLog().error(e);}}return result;}}

这个是数据库操作类,主要进行数据库数据操作,由于url资源表结构与action表结构一致,数据库操作类功能类似,所以不贴代码了.

3.5 GetAllJspServlet.java

public class GetAllJspServlet extends HttpServlet {private static final long serialVersionUID = 1L;public void init(ServletConfig config) throws ServletException {ARE.getLog().info("GetAllJspServlet start");ServletContext context = config.getServletContext();ARE.getLog().info("GetAllJspServlet start");String[] dirs = {"craw","security","frame/page","parse"};//要扫描的文件目录List<String> loads = new ArrayList<>();//存储jsp相对路径for (int i = 0; i < dirs.length; i++) {File file = new File(context.getRealPath("/") + dirs[i]);String fatherPath = file.getAbsolutePath();ARE.getLog().info("dir : " + fatherPath);File[] files = file.listFiles();if(null != files && files.length > 0) {for (int j = 0; j < files.length; j++) {String filePath = "/" + dirs[i] + files[j].getPath().replace(fatherPath, "");if(filePath.endsWith(".jsp")) {ARE.getLog().info(filePath);loads.add(filePath);}}}}new UrlDao().initUrlRecords(loads);ARE.getLog().info("GetAllJspServlet end");}}

这个类是在项目部署启动时,读取指定目录下的所有jsp文件的文件名,与数据库中的url资源对比,进行url资源初始化(以项目读到的jsp为准,修改数据库数据)

3.6 GetAllRequestMappingServlet.java

public class GetAllRequestMappingServlet extends HttpServlet {private static final long serialVersionUID = 1L;public void init(ServletConfig config) throws ServletException {ARE.getLog().info("GetAllRequestMappingServlet start");// 查找所以springMVC配置的urlServletContext context = config.getServletContext();WebApplicationContext webApp = WebApplicationContextUtils.getRequiredWebApplicationContext(context);RequestMappingHandlerMapping rmhp = webApp.getBean(RequestMappingHandlerMapping.class);Map<RequestMappingInfo, HandlerMethod> map = rmhp.getHandlerMethods();List<String> loads = new ArrayList<>();for (Iterator<RequestMappingInfo> iterator = map.keySet().iterator(); iterator.hasNext();) {RequestMappingInfo info = iterator.next();String url = info.getPatternsCondition().toString();if (url.length() >= 2) {url = url.substring(1, url.length() - 1);loads.add(url);ARE.getLog().info(url);}}// 查找web.xml中配置的urltry {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = factory.newDocumentBuilder();File file = new File(context.getRealPath("/")+"WEB-INF/web.xml");Document doc = (Document) builder.parse(file);NodeList nodeList = doc.getElementsByTagName("url-pattern");for (int i = 0; i < nodeList.getLength(); i++) {Element tyes = (Element)nodeList.item(i); Text textNode = (Text) tyes.getFirstChild();      String text = textNode.getData().trim();    if(!"null".equals(text) && !text.contains("*")){    ARE.getLog().info(text);    if(!loads.contains(text)) {    loads.add(text);    }    }}} catch (Exception e) {ARE.getLog().error(e);}ARE.getLog().info("GetAllRequestMappingServlet init db");new ActionDao().initAtionRecords(loads);ARE.getLog().info("GetAllRequestMappingServlet end");}}

这个类是在项目启动时,读取所有项目中配置的.do请求(由于项目是使用了springMVC,所以用spring中的类实现这个功能)以及web.xml中配置的请求,作为action资源,并与数据库同步(以读到的数据为准)

4.总结

通过添加url资源表,action表使得整个权限管理系统能够精确到页面级,按钮事件级.而通过管理类把数据加载到内存,减轻了数据库的访问压力,选择使用乐观锁以及开启线程定时检索数据是否更新提高了数据库执行效率.另外,通过项目启动时系统自动检索系统所有资源并与数据库同步,以及杜绝用户新增和删除操作,提高了数据的一致性和安全性.至次0.9版本的用户权限系统升级完毕.
原创粉丝点击