基于角色的访问控制 RBAC(role based access control),基于角色的访问控制。 好比: 系统角色包括 :部门经理、总经理。(角色针对用户来划分) 系统代码中实现: //若是该user是部门经理则能够访问if中的代码 if(user.hasRole('部门经理')){ //系统资源内容 //用户报表查看 } 问题: 角色针对人划分的,人做为用户在系统中属于活动内容,若是该 角色能够访问的资源出现变动,须要修改你的代码了,好比:须要变动为部门经理和总经理均可以进行用户报表查看,代码改成: if(user.hasRole('部门经理') || user.hasRole('总经理') ){ //系统资源内容 //用户报表查看 } 基于角色的访问控制是不利于系统维护(可扩展性不强)。 基于资源的访问控制 RBAC(Resource based access control),基于资源的访问控制。 资源在系统中是不变的,好比资源有:类中的方法,页面中的按钮。 对资源的访问须要具备permission权限,代码能够写为: if(user.hasPermission ('用户报表查看(权限标识符)')){ //系统资源内容 //用户报表查看 } 上边的方法就能够解决用户角色变动不用修改上边权限控制的代码。 若是须要变动权限只须要在分配权限模块去操做,给部门经理或总经理增或删除权限。 建议使用基于资源的访问控制实现权限管理。
粗粒度权限管理,对资源类型的权限管理。细粒度权限管理,对资源实例的权限管理。
粗粒度权限管理,对资源类型的权限管理。资源类型好比:菜单、url链接、用户添加页面、用户信息、类方法、页面中按钮。。
粗粒度权限管理好比:超级管理员能够访问户添加页面、用户信息等所有页面。
部门管理员能够访问用户信息页面包括 页面中全部按钮。
细粒度权限管理,对资源实例的权限管理。资源实例就资源类型的具体化,好比:用户id为001的修改链接,1110班的用户信息、行政部的员工。
细粒度权限管理就是数据级别的权限管理。
细粒度权限管理好比:部门经理只能够访问本部门的员工信息,用户只能够看到本身的菜单,大区经理只能查看本辖区的销售订单。。
粗粒度和细粒度例子:(spring security是类型级别)
系统有一个用户列表查询页面,对用户列表查询分权限,若是粗颗粒管理,张三和李四都有用户列表查询的权限,张三和李四均可以访问用户列表查询。
进一步进行细颗粒管理,张三(行政部)和李四(开发部)只能够查询本身本部门的用户信息。张三只能查看行政部 的用户信息,李四只能查看开发部门的用户信息。细粒度权限管理就是数据级别的权限管理。
如何实现粗粒度权限管理?
粗粒度权限管理比较容易将权限管理的代码抽取出来在系统架构级别统一处理。好比:经过springmvc的拦截器实现受权。
如何实现细粒度权限管理? 复杂
对细粒度权限管理在数据级别是没有共性可言,针对细粒度权限管理就是系统业务逻辑的一部分,若是在业务层去处理相对比较简单,若是将细粒度权限管理统一在系统架构级别去抽取,比较困难,即便抽取的功能可能也存在扩展不强。
建议细粒度权限管理在业务层去控制。
好比:部门经理只查询本部门员工信息,在service接口提供一个部门id的参数,controller中根据当前用户的信息获得该 用户属于哪一个部门,调用service时将部门id传入service,实现该用户只查询本部门的员工。
对于粗粒度权限管理,建议使用优秀权限管理框架来实现,节省开发成功,提升开发效率。
shiro就是一个优秀权限管理框架。
不用权限框架,是用基于url拦截的方式实如今实际开发中比较经常使用的一种方式。
对于web系统,经过filter过虑器实现url拦截,也能够springmvc框架的拦截器实现基于url的拦截。
基于url的方式是不须要shiro,对于细粒度的放在业务层去实现,粗粒度的用框架或者url拦截方式。java
基于url权限管理流程:有认证过滤器和受权过滤器。“我的中心”就是认证经过可是不须要受权的url。都是以url进行操做的。
percode是权限标识符,parentids是权限路径,sortstring是排序字段,mysql
mysql5.1数据库中建立表:用户表、角色表、权限表(实质上是权限和资源的结合 )、用户角色表、角色权限表。jquery
完成权限管理的数据模型建立。web
jdk1.7.0_72redis
eclipse 3.7 indigospring
技术架构:springmvc+mybatis+jquery-easyuisql
新建一个动态web项目。数据库
建立config文件夹缓存
系统 登录至关 于用户身份认证,用户成功,要在session中记录用户的身份信息.
操做流程:
用户进行登录页面
输入用户名和密码进行登录
进行用户名和密码校验:service(进行用户名和密码校验)
若是校验经过,在session记录用户身份信息:controler里面记录session,只有controller能够访问session.
建立专门类用于记录用户身份信息。
mapper接口: 根据用户帐号查询用户(sys_user)信息(不用写,使用逆向工程生成的mapper)
使用逆向工程生成如下表的基础代码:
Service:session
public class SysServiceImpl implements SysService { @Autowired private SysUserMapper sysUserMapper; @Autowired private SysPermissionMapperCustom sysPermissionMapperCustom; @Override public ActiveUser authenticat(String userCode, String password) throws Exception { /** 认证过程: 根据用户身份(帐号)查询数据库,若是查询不到用户不存在 对输入的密码 和数据库密码 进行比对,若是一致,认证经过 */ //根据用户帐号查询数据库 SysUser sysUser = this.findSysUserByUserCode(userCode); if(sysUser == null){ //抛出异常 throw new CustomException("用户帐号不存在"); } //数据库密码 (md5密码 ) String password_db = sysUser.getPassword(); //对输入的密码 和数据库密码 进行比对,若是一致,认证经过 //对页面输入的密码 进行md5加密 String password_input_md5 = new MD5().getMD5ofStr(password); if(!password_input_md5.equalsIgnoreCase(password_db)){ //抛出异常 throw new CustomException("用户名或密码 错误"); } //获得用户id String userid = sysUser.getId(); //根据用户id查询菜单 List<SysPermission> menus =this.findMenuListByUserId(userid); //根据用户id查询权限url List<SysPermission> permissions = this.findPermissionListByUserId(userid); //认证经过,返回用户身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsercode(userCode); activeUser.setUsername(sysUser.getUsername());//用户名称 //放入权限范围的菜单和url activeUser.setMenus(menus); activeUser.setPermissions(permissions); return activeUser; }
Controller:
@Autowired private SysService sysService; //用户登录提交方法 /** * @param session * @param randomcode 输入的验证码 * @param usercode 用户帐号 * @param password 用户密码 */ @RequestMapping("/login") public String login(HttpSession session, String randomcode,String usercode,String password)throws Exception{ //校验验证码,防止恶性攻击 //从session获取正确验证码 String validateCode = (String) session.getAttribute("validateCode"); //输入的验证和session中的验证进行对比 if(!randomcode.equals(validateCode)){ //抛出异常 throw new CustomException("验证码输入错误"); } //调用service校验用户帐号和密码的正确性 ActiveUser activeUser = sysService.authenticat(usercode, password); //若是service校验经过,将用户身份记录到session session.setAttribute("activeUser", activeUser); //重定向到商品查询页面 return "redirect:/first.action"; }
配置能够匿名访问的url,直接放行。
资源文件读取类:
package cn.itcast.ssm.util; import java.io.Serializable; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import java.util.Set; /** * 资源文件读取工具类 */ public class ResourcesUtil implements Serializable { private static final long serialVersionUID = -7657898714983901418L; /** * 系统语言环境,默认为中文zh */ public static final String LANGUAGE = "zh"; /** * 系统国家环境,默认为中国CN */ public static final String COUNTRY = "CN"; private static Locale getLocale() { Locale locale = new Locale(LANGUAGE, COUNTRY); return locale; } /** * 根据语言、国家、资源文件名和key名字获取资源文件值 * @param language 语言 * @param country国家 * @param fileName资源文件名 * @param key名字 * @return 值 */ private static String getProperties(String fileName, String key) { String retValue = ""; try { Locale locale = getLocale(); ResourceBundle rb = ResourceBundle.getBundle(fileName, locale); retValue = (String) rb.getObject(key); } catch (Exception e) { e.printStackTrace(); } return retValue; } /** * 经过key从资源文件读取内容 * @param fileName资源文件名 * @param key索引 * @return 索引对应的内容 */ public static String getValue(String fileName, String key) { String value = getProperties(fileName,key); return value; } public static List<String> gekeyList(String baseName) { Locale locale = getLocale(); ResourceBundle rb = ResourceBundle.getBundle(baseName, locale); List<String> reslist = new ArrayList<String>(); Set<String> keyset = rb.keySet(); for (Iterator<String> it = keyset.iterator(); it.hasNext();) { String lkey = (String)it.next(); reslist.add(lkey); } return reslist; } /** * 经过key从资源文件读取内容,并格式化 * @param fileName资源文件名 * @param key索引 * @param objs 格式化参数 * @return 格式化后的内容 */ public static String getValue(String fileName, String key, Object[] objs) { String pattern = getValue(fileName, key); String value = MessageFormat.format(pattern, objs); return value; } public static void main(String[] args) { System.out.println(getValue("resources.messages", "101",new Object[]{100,200})); //根据操做系统环境获取语言环境 /*Locale locale = Locale.getDefault(); System.out.println(locale.getCountry());//输出国家代码 System.out.println(locale.getLanguage());//输出语言代码s //加载国际化资源(classpath下resources目录下的messages.properties,若是是中文环境会优先找messages_zh_CN.properties) ResourceBundle rb = ResourceBundle.getBundle("resources.messages", locale); String retValue = rb.getString("101");//101是messages.properties文件中的key System.out.println(retValue); //信息格式化,若是资源中有{}的参数则须要使用MessageFormat格式化,Object[]为传递的参数,数量根据资源文件中的{}个数决定 String value = MessageFormat.format(retValue, new Object[]{100,200}); System.out.println(value);*/ } }
编写认证拦截器 package cn.itcast.ssm.controller.interceptor; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import cn.itcast.ssm.po.ActiveUser; import cn.itcast.ssm.util.ResourcesUtil; /** * * <p>Title: HandlerInterceptor1</p> * <p>Description: 用户身份认证拦截器</p> * <p>Company: www.itcast.com</p> * @author 传智.燕青 * @date 2015-3-22下午4:11:44 * @version 1.0 */ public class LoginInterceptor implements HandlerInterceptor { //在执行handler以前来执行的 //用于用户认证校验、用户权限校验 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获得请求的url String url = request.getRequestURI(); //判断是不是公开 地址 //实际开发中须要公开 地址配置在配置文件中 //从配置中取逆名访问url List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL"); //遍历公开 地址,若是是公开 地址则放行 for(String open_url:open_urls){ if(url.indexOf(open_url)>=0){ //若是是公开 地址则放行,不然跳转到登录页面 return true; } } //判断用户身份在session中是否存在 HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //若是用户身份在session中存在放行 if(activeUser!=null){ return true; } //执行到这里拦截,跳转到登录页面,用户进行身份认证 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); //若是返回false表示拦截不继续执行handler,若是返回true表示放行 return false; } //在执行handler返回modelAndView以前来执行 //若是须要向页面提供一些公用 的数据或配置一些视图信息,使用此方法实现 从modelAndView入手 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("HandlerInterceptor1...postHandle"); } //执行handler以后执行此方法 //做系统 统一异常处理,进行方法执行性能监控,在preHandle中设置一个时间点,在afterCompletion设置一个时间,两个时间点的差就是执行时长 //实现 系统 统一日志记录 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("HandlerInterceptor1...afterCompletion"); } }
配置拦截器
在springmvc.xml中配置拦截器
commonURL.properties
在此配置文件配置公用访问地址,公用访问地址只要经过用户认证,不须要对公用访问地址分配权限便可访问。好比首页和退出,只须要登录,每一个用户都同样。
用户权限相关的东西能够放在缓存或者session或者redis中。
获取用户权限范围的菜单
思路:
在用户认证时,认证经过,根据用户id从数据库获取用户权限范围的菜单,将菜单的集合存储在session中。
mapper接口:根据用户id查询用户权限的菜单