BOS项目7::权限概述、权限数据模型、shiro入门{bos中应用}





内容安排:

1、权限概述(认证、授权)

2、常见的权限控制的方式(URL拦截、方法注解)

3、权限的数据模型(权限表、角色表、用户表、角色权限关系表、用户角色关系表)

4、apache shiro框架

5、将shiro应用到bos项目中(认证、授权)

 

1.    权限概述

系统中提供了很多功能,并不是所有的用户都可以操作这些功能,需要对功能的访问进行控制。

认证:系统提供的用于识别用户身份的功能,通常是登录功能(你是谁???)

授权:系统提供的赋予用户可以操作系统某些功能能力(你能做什么???)

 

菜单按钮是访问某个功能的入口,都是发起一次请求,由服务端进行相应的操作。

 

2.    常见的权限控制方式

 

2.1   URL拦截进行权限校验



2.2   方法注解权限控制



3.    权限的数据模型


权限表

角色表:引入角色是为了简化授权

用户表

角色权限关系表(多对多中间表)

用户角色关系表(多对多中间表)

 


 

关键字:注解后面可以使用

路径:访问action的路径

菜单:有些资源会挂到菜单上

优先级:用户菜单排序

父权限编号:权限的上下级,指向本表的主键


实体类


权限实体

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 权限实体 
  3.  * 
  4.  */  
  5. public class Function implements java.io.Serializable {  
  6.   
  7.     // Fields  
  8.   
  9.     private String id;//编号  
  10.     private Function parentFunction;//当前权限对应的上一级权限  
  11.     private String name;//权限名称  
  12.     private String code;//关键字  
  13.     private String description;//描述  
  14.     private String page;//访问URL  
  15.     private String generatemenu;//当前权限是否生成到菜单  1:生成 0:不生成  
  16.     private Integer zindex;//排序  
  17.     private Set children = new HashSet(0);//当前权限对应的多个下级权限  
  18.     private Set roles = new HashSet(0);//当前权限对应的多个角色  

角色实体

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 角色实体  
  3.  * 
  4.  */  
  5. public class Role implements java.io.Serializable {  
  6.   
  7.     // Fields  
  8.   
  9.     private String id;//编号  
  10.     private String name;//角色名称  
  11.     private String code;//关键字  
  12.     private String description;//描述  
  13.     private Set functions = new HashSet(0);//当前角色对应的多个权限  
  14.     private Set users = new HashSet(0);//当前角色对应的多个用户  

用户

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class User implements java.io.Serializable {  
  2.   
  3.     private String id;  
  4.     private String username;  
  5.     private String password;  
  6.     private Double salary;  
  7.     private Timestamp birthday;  
  8.     private String gender;  
  9.     private String station;  
  10.     private String telephone;  
  11.     private String remark;  
  12.     private Set noticebills = new HashSet(0);  
  13.     private Set roles = new HashSet(0);//当前用户对应的多个角色  


4.    apache shiro框架


提供的进行权限控制的方式:

1、URL拦截进行权限控制

2、方法注解权限控制

3、页面标签权限控制

4、代码方式权限控制(了解)

 

shiro提供的功能:


Shiro的4大部分——身份验证,授权,会话管理和加密

•  Authentication:身份验证,简称“登录”。

•  Authorization:授权,给用户分配角色或者权限资源

•  Session Management:用户session管理器,可以让CS程序也使用session来控制权限

•  Cryptography:把JDK中复杂的密码加密方式进行封装

除了以上功能,shiro还提供很多扩展

•  Web Support:主要针对web应用提供一些常用功能。

•  Caching:缓存可以使应用程序运行更有效率。

•  Concurrency:多线程相关功能。

•  Testing:帮助我们进行测试相关功能

•  "Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。

•  “Remember Me”:记住用户身份,提供类似购物车功能。

 

shiro框架运行流程图:


Application Code:应用程序代码,由开发人员负责


Subject

由框架提供的,是与程序进行交互的对象,可以是人也可以是服务或者其他,通常就理解为用户。

所有Subject 实例都必须绑定到一个SecurityManager上。我们与一个 Subject

交互,运行时shiro会自动转化为与 SecurityManager交互的特定 subject的交互


SecurityManager:

安全管理器,由框架提供的,SecurityManager 是 Shiro的核心,初始化时协调各个模块运行。然而,一旦 SecurityManager协调完毕,SecurityManager 会被单独留下,且我们只需要去操作Subject即可,无需操作SecurityManager 。 但是我们得知道,当我们正与一个 Subject 进行交互时,实质上是 SecurityManager在处理 Subject 安全操作。


Realm:

安全数据桥,类似于项目中的DAO,访问安全数据的,框架提供,开发人员也可自己编写

Realms在 Shiro中作为应用程序和安全数据之间的“桥梁”或“连接器”。他获取安全数据来判断subject是否能够登录,subject拥有什么权限。他有点类似DAO。在配置realms时,需要至少一个realm。而且Shiro提供了一些常用的 Realms来连接数据源,如LDAP数据源的JndiLdapRealm,JDBC数据源的JdbcRealm,ini文件数据源的IniRealm,properties文件数据源的PropertiesRealm,等等。我们也可以插入自己的 Realm实现来代表自定义的数据源。 像其他组件一样,Realms也是由SecurityManager控制


小结


1.Subject(org.apache.shiro.subject.Subject):简称用户

2.SecurityManager(org.apache.shiro.mgt.SecurityManager)

如上所述,SecurityManager是shiro的核心,协调shiro的各个组件

3.Authenticator(org.apache.shiro.authc.Authenticator):

登录控制

注:AuthenticationStrategy

(org.apache.shiro.authc.pam.AuthenticationStrategy)

如果存在多个realm,则接口AuthenticationStrategy会确定什么样算是登录成功(例如,如果一个Realm成功,而其他的均失败,是否登录成功?)。

4.Authorizer(org.apache.shiro.authz.Authorizer):

决定subject能拥有什么样角色或者权限。

5.SessionManager(org.apache.shiro.session.SessionManager):

创建和管理用户session。通过设置这个管理器,shiro可以在任何环境下使用session。

6.CacheManager(org.apahce.shiro.cache.CacheManager):

缓存管理器,可以减少不必要的后台访问。提高应用效率,增加用户体验。

7.Cryptography(org.apache.shiro.crypto.*) :

Shiro的api大幅度简化Java api中繁琐的密码加密。

8.Realms(org.apache.shiro.realm.Realm) :程序与安全数据的桥梁

 

5.将shiro应用到bos项目中

 

因为官方例子虽然中有更加简洁的ini配置形式,但是使用ini配置无法与spring整合

 

第一步:导入shiro的jar包到项目中

shiro-all-1.2.2.jar

第二步:在web.xml中配置一个过滤器,是由spring提供的,用于整合shiro

注意:配置到struts2核心过滤器的前面

filter-name 与spring xml 配置的 bean id一致

org.springframework.web.filter.DelegatingFilterProxy

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1.  <!-- 配置spring提供的过滤器,用于整合shiro框架 ,  
  2.     在项目启动过程中,当前过滤器会从spring工厂中获取一个和当前过滤器同名的bean对象-->  
  3. <filter>  
  4.     <filter-name>shiroFilter</filter-name>  
  5.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  6. </filter>  
  7. <filter-mapping>  
  8.     <filter-name>shiroFilter</filter-name>  
  9.     <url-pattern>/*</url-pattern>  
  10. </filter-mapping>  

第三步:在applicationContext.xml中配置bean,ID必须为shiroFilter


org.apache.shiro.spring.web.ShiroFilterFactoryBean 

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 配置shiro的过滤器对象,当前对象用于创建shiro框架需要的过滤器对象 -->  
  2. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  3.     <!-- 注入安全管理器 -->  
  4.     <property name="securityManager" ref="securityManager"/>  
  5.     <!-- 注入系统的登录访问路径 -->  
  6.     <property name="loginUrl" value="/login.jsp"/>  
  7.     <!-- 成功页面 -->  
  8.     <property name="successUrl" value="/index.jsp"/>  
  9.     <!-- 权限不足提示页面 -->  
  10.     <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>  
  11.     <!-- 基于URL拦截权限控制 -->  
  12.     <property name="filterChainDefinitions">  
  13.         <value>  
  14.             /css/** = anon  
  15.             /js/** = anon  
  16.             /images/** = anon  
  17.             /validatecode.jsp* = anon  
  18.             /login.jsp* = anon  
  19.             /userAction_login.action = anon  
  20.             /page_base_staff.action = perms["staff"]  
  21.             /* = authc  
  22.         </value>  
  23.     </property>  
  24. </bean>  

securityManager:这个属性是必须的。




安全管理器配置代码

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 定义安全管理器 -->  
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  3.     <!-- 注入realm -->  
  4.     <property name="realm" ref="bosRealm"></property>  
  5. </bean>  
  6.   
  7. <!-- 自定义Realm -->  
  8. <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>  

loginUrl :没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。

successUrl :登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。

unauthorizedUrl :没有权限默认跳转的页面。

 

过滤器


anon:

例子/admins/**=anon没有参数,表示可以匿名使用。

authc:

例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数

roles:

例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。

perms:

例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。

Rest:

例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等。

port:

例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString

是你访问的url里的?后面的参数。

authcBasic:

例如/admins/user/**=authcBasic没有参数表示httpBasic认证

ssl:

例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

user:

例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查

 

注:

anon,authcBasic,auchc,user是认证过滤器,

perms,roles,ssl,rest,port是授权过滤器

 

 

第四步:修改UserAction中的login方法,使用shiro提供的方式进行认证

过程:

1.通过subjec对象的login方法进行认证

2.Subject会调用securityManager安全管理器,安全管理器会调用Realm

3.securityManager安全管理器不是通过返回参数的方式而是通过抛异常来做出判定结果

用户名不存在异常:

org.apache.shiro.authc.UnknownAccountException  

密码错误异常:

org.apache.shiro.authc.IncorrectCredentialsException

 

代码

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. //提供属性接收验证码  
  2. private String checkcode;  
  3. public void setCheckcode(String checkcode) {  
  4.     this.checkcode = checkcode;  
  5. }  
  6. /** 
  7.  * 使用shiro方式进行认证 
  8.  */  
  9. public String login(){  
  10.     //从session中获取自动生成的验证码  
  11.     String key = (String) ActionContext.getContext().getSession().get("key");  
  12.     if(StringUtils.isNotBlank(checkcode) && checkcode.equals(key)){  
  13.         //使用shiro方式进行认证  
  14.         String username = model.getUsername();  
  15.         String password = model.getPassword();  
  16.         password = MD5Utils.md5(password);  
  17.         Subject subject = SecurityUtils.getSubject();//主体,当前状态为“未认证”状态  
  18.         AuthenticationToken token = new UsernamePasswordToken(username, password);//用户名密码令牌  
  19.         try{  
  20.             subject.login(token);//使用subject调用SecurityManager,安全管理器调用Realm  
  21.             User user = (User) subject.getPrincipal();  
  22.             //登录成功,将User对象放入session   
  23.             ActionContext.getContext().getSession().put("loginUser", user);  
  24.         }catch (UnknownAccountException e) {//用户名不存在异常  
  25.             e.printStackTrace();  
  26.             return "login";  
  27.         }catch (IncorrectCredentialsException e) {//密码错误异常  
  28.             e.printStackTrace();  
  29.             return "login";  
  30.         }  
  31.         return "home";  
  32.     }else{  
  33.         //验证码有误,添加错误信息,跳转到登录页面  
  34.         this.addActionError(this.getText("checkcodeError"));  
  35.         return "login";  
  36.     }  
  37. }  


第五步:自定义一个Realm,实现认证方法


继承抽象类 重写认证与授权方法

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class BOSRealm extends AuthorizingRealm {  
  2.     // 注入dao  
  3.     @Resource  
  4.     private IUserDao userDao;  
  5.   
  6.     // 认证方法  
  7.     protected AuthenticationInfo doGetAuthenticationInfo(  
  8.             AuthenticationToken token) throws AuthenticationException {  
  9.         System.out.println("认证方法。。。。");  
  10.         UsernamePasswordToken pwdToken = (UsernamePasswordToken) token;  
  11.         String username = pwdToken.getUsername();  
  12.         // 根据用户名查询密码,由安全管理器负责比对查询出的数据库中的密码和页面输入的密码是否一致  
  13.         User user = userDao.findUserByUsername(username);  
  14.         if(user == null){  
  15.             return null;  
  16.         }  
  17.         String dbPassword = user.getPassword();  
  18.         //参数一:  
  19.         AuthenticationInfo info = new SimpleAuthenticationInfo(user,  
  20.                 dbPassword, this.getClass().getSimpleName());  
  21.         return info;  
  22.     }  
  23.   
  24.     //授权方法  
  25.     protected AuthorizationInfo doGetAuthorizationInfo(  
  26.             PrincipalCollection principals) {  
  27.         //根据当前登陆用户,查询当前用户的角色,根据角色获取相应的权限,把权限添加到授权信息对象中  
  28.         //三种获得当前登陆用户的方式  
  29.         User user1 = (User) principals.getPrimaryPrincipal();  
  30.         User user2 = (User) SecurityUtils.getSubject().getPrincipal();  
  31.         //User user3 = (User) ActionContext.getContext().getSession().get("session_user");  
  32.   
  33.           
  34.         //由于添加权限 角色的 相应方法还没学习编码 暂用伪代码添加权限  
  35.         //spring是shrio配置中filterChainDefinitions 添加了/page_base_staff.action=perms["staff"]  
  36.         SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();  
  37.         info.addStringPermission("staff");  
  38. //      info.addStringPermission("region.query");  
  39.           
  40.         return info;  
  41.   
  42. }  


第六步:在applicationContext.xml中注册Realm,并注入给安全管理器

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 定义安全管理器 -->  
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  3.     <!-- 注入realm -->  
  4.     <property name="realm" ref="bosRealm"></property>  
  5. </bean>  
  6.   
  7. <!-- 自定义Realm -->  
  8. <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>  


第七步:在自定义Realm中实现授权方法

*授权方法执行的时机:为当访问的路径需要权限认证的时候,才执行授权

*这样每次访问权限路径都执行一次授权方法,效率很低,需要增加缓存管理器

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class BosRealm extends AuthorizingRealm{  
  2.     @Resource  
  3.     private IUserDao userDao;  
  4.     @Resource  
  5.     private IRoleDao roleDao;  
  6.     @Resource  
  7.     private IFunctionDao functionDao;  
  8.       
  9.     //认证方法  
  10.     protected AuthenticationInfo doGetAuthenticationInfo(  
  11.             AuthenticationToken token) throws AuthenticationException {  
  12.         System.out.println("认证方法。。。");  
  13.           
  14.         UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;  
  15.         String username = usernamePasswordToken.getUsername();  
  16.           
  17.         //1 根据用户名查询密码  
  18.         User user = userDao.findUserByUserName(username);  
  19.         if(user == null){  
  20.             // 用户名不存在  
  21.             return null;  
  22.         }  
  23.           
  24.         //2 返回AuthenticationInfo对象  
  25.         Object principal = user;//将当前查询到的用户对象放入SimpleAuthenticationInfo中,可以通过subject获得  
  26.         Object credentials = user.getPassword();//密码,shiro负责比较查询到的密码和用户输入的密码是否一致  
  27.         String realmName = super.getName();  
  28.         AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, credentials, realmName);  
  29.         return authenticationInfo;  
  30.     }  
  31.   
  32.     //授权方法  
  33.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  34.         System.out.println("授权...");  
  35.         // 获取当前登陆用户 ,根据当前登陆用户,查询对应角色信息  
  36.         Subject subject = SecurityUtils.getSubject();  
  37.         User user = (User) subject.getPrincipal();  
  38.   
  39.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  40.         if (user.getUsername().equals("admin")) {  
  41.             // 如果admin ,查询所有角色和所有权限  
  42.             List<Role> roles = roleDao.findAll();  
  43.             for (Role role : roles) {  
  44.                 authorizationInfo.addRole(role.getCode());  
  45.             }  
  46.             List<Function> functions = functionDao.findAll();  
  47.             for (Function function : functions) {  
  48.                 authorizationInfo.addStringPermission(function.getCode());  
  49.             }  
  50.         } else {  
  51.             // 普通用户 , 根据当前用户,查询具有角色,通过角色获取权限  
  52.             List<Role> roles = roleDao.findRolesByUser(user);  
  53.             // 添加角色  
  54.             for (Role role : roles) {  
  55.                 authorizationInfo.addRole(role.getCode());  
  56.                 // 添加角色对应权限  
  57.                 Set<Function> functions = role.getFunctions();  
  58.                 for (Function function : functions) {  
  59.                     authorizationInfo.addStringPermission(function.getCode());  
  60.                 }  
  61.             }  
  62.         }  
  63.         return authorizationInfo;  
  64.     }  
  65.       
  66.     protected AuthorizationInfo doGetAuthorizationInfo_bak(  
  67.             PrincipalCollection principals) {  
  68.         System.out.println("授权方法。。。");  
  69.         User user = (User) principals.getPrimaryPrincipal();  
  70.         System.out.println(user);  
  71.         //根据当前登录用户查询对应的权限和角色  
  72.           
  73.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  74.         authorizationInfo.addStringPermission("staff:query");  
  75.         authorizationInfo.addStringPermission("abc");  
  76.         authorizationInfo.addRole("admin");  
  77.         return authorizationInfo;  
  78.     }  
  79.   
  80. }  


 

6.    基于shiro注解实现权限控制


第一步:在spring配置文件中开启shiro注解功能

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 开启shiro注解自动代理 -->  
  2. <bean id="defaultAdvisorAutoProxyCreator"   
  3.     class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">  
  4.     <!-- 指定强制使用cglib为Action创建代理对象 -->  
  5.     <property name="proxyTargetClass" value="true"/>  
  6. </bean>  
  7.   
  8. <!-- 切面 -->  
  9. <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>  

第二步:在Action方法上使用注解进行权限控制

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.      * 分页查询方法 
  3.      * @throws IOException  
  4.      */  
  5.     @RequiresPermissions(value="region.query")//执行这个方法需要region.query权限  
  6.     //@RequiresRoles(value="admin")  
  7.     public String pageQuery() throws IOException{  
  8.         regionService.pageQuery(pageBean);  
  9.         this.writePageBean2Json(pageBean, new String[]{"currentPage","pageSize","detachedCriteria","subareas"});  
  10.         return NONE;  
  11.     }  

控制精度:
注解方式控制权限
只能是在方法上控制,无法控制类级别访问。
过滤器方式控制是根据访问的URL进行控制。允许使用*匹配URL,所以可以进行粗粒度,也可以进行细粒度控制。


其他注解

@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。


@ RequiresUser

验证用户是否被记忆,user有两种含义:
一种是成功登录的(subject.isAuthenticated() 结果为true);
另外一种是被记忆的( subject.isRemembered()结果为true)。


@ RequiresGuest
验证是否是一个guest的请求,与@ RequiresUser完全相反。
 换言之,RequiresUser  == ! RequiresGuest 。
此时subject.getPrincipal() 结果为null.


@ RequiresRoles

例如:@RequiresRoles("aRoleName");
void someMethod();
如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。


@RequiresPermissions

例如: @RequiresPermissions( {"file:read", "write:aFile.txt"} ) void someMethod();
要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException。

第三步:修改BaseAction的构造方法

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. // 构造方法  
  2. @SuppressWarnings("unchecked")  
  3. public BaseAction() {  
  4.     //获取父类类型(baseAction)  
  5.     ParameterizedType superclass = null;  
  6.     Type type = this.getClass().getGenericSuperclass();  
  7.     //获取父类泛型 强制转化参数类型 需要判断 生成action是否使用代理 既父类是否是参数化类型  
  8.     if(type instanceof ParameterizedType){  
  9.         //没有使用代理直接强制转换  
  10.         superclass = (ParameterizedType) type;  
  11.     }else{  
  12.         //type 不属于参数化类型 则action是cglib代理生成    
  13.         //CGLib是针对目标类生产子类,需要获取父类的父类的类型再强转成参数化类型  
  14.         superclass = (ParameterizedType) this.getClass().getSuperclass().getGenericSuperclass();  
  15.     }  
  16.     ...  


第四步:在自定义Realm中授权


内容安排:

1、权限概述(认证、授权)

2、常见的权限控制的方式(URL拦截、方法注解)

3、权限的数据模型(权限表、角色表、用户表、角色权限关系表、用户角色关系表)

4、apache shiro框架

5、将shiro应用到bos项目中(认证、授权)

 

1.    权限概述

系统中提供了很多功能,并不是所有的用户都可以操作这些功能,需要对功能的访问进行控制。

认证:系统提供的用于识别用户身份的功能,通常是登录功能(你是谁???)

授权:系统提供的赋予用户可以操作系统某些功能能力(你能做什么???)

 

菜单按钮是访问某个功能的入口,都是发起一次请求,由服务端进行相应的操作。

 

2.    常见的权限控制方式

 

2.1   URL拦截进行权限校验



2.2   方法注解权限控制



3.    权限的数据模型


权限表

角色表:引入角色是为了简化授权

用户表

角色权限关系表(多对多中间表)

用户角色关系表(多对多中间表)

 


 

关键字:注解后面可以使用

路径:访问action的路径

菜单:有些资源会挂到菜单上

优先级:用户菜单排序

父权限编号:权限的上下级,指向本表的主键


实体类


权限实体

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 权限实体 
  3.  * 
  4.  */  
  5. public class Function implements java.io.Serializable {  
  6.   
  7.     // Fields  
  8.   
  9.     private String id;//编号  
  10.     private Function parentFunction;//当前权限对应的上一级权限  
  11.     private String name;//权限名称  
  12.     private String code;//关键字  
  13.     private String description;//描述  
  14.     private String page;//访问URL  
  15.     private String generatemenu;//当前权限是否生成到菜单  1:生成 0:不生成  
  16.     private Integer zindex;//排序  
  17.     private Set children = new HashSet(0);//当前权限对应的多个下级权限  
  18.     private Set roles = new HashSet(0);//当前权限对应的多个角色  

角色实体

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 角色实体  
  3.  * 
  4.  */  
  5. public class Role implements java.io.Serializable {  
  6.   
  7.     // Fields  
  8.   
  9.     private String id;//编号  
  10.     private String name;//角色名称  
  11.     private String code;//关键字  
  12.     private String description;//描述  
  13.     private Set functions = new HashSet(0);//当前角色对应的多个权限  
  14.     private Set users = new HashSet(0);//当前角色对应的多个用户  

用户

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class User implements java.io.Serializable {  
  2.   
  3.     private String id;  
  4.     private String username;  
  5.     private String password;  
  6.     private Double salary;  
  7.     private Timestamp birthday;  
  8.     private String gender;  
  9.     private String station;  
  10.     private String telephone;  
  11.     private String remark;  
  12.     private Set noticebills = new HashSet(0);  
  13.     private Set roles = new HashSet(0);//当前用户对应的多个角色  


4.    apache shiro框架


提供的进行权限控制的方式:

1、URL拦截进行权限控制

2、方法注解权限控制

3、页面标签权限控制

4、代码方式权限控制(了解)

 

shiro提供的功能:


Shiro的4大部分——身份验证,授权,会话管理和加密

•  Authentication:身份验证,简称“登录”。

•  Authorization:授权,给用户分配角色或者权限资源

•  Session Management:用户session管理器,可以让CS程序也使用session来控制权限

•  Cryptography:把JDK中复杂的密码加密方式进行封装

除了以上功能,shiro还提供很多扩展

•  Web Support:主要针对web应用提供一些常用功能。

•  Caching:缓存可以使应用程序运行更有效率。

•  Concurrency:多线程相关功能。

•  Testing:帮助我们进行测试相关功能

•  "Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。

•  “Remember Me”:记住用户身份,提供类似购物车功能。

 

shiro框架运行流程图:


Application Code:应用程序代码,由开发人员负责


Subject

由框架提供的,是与程序进行交互的对象,可以是人也可以是服务或者其他,通常就理解为用户。

所有Subject 实例都必须绑定到一个SecurityManager上。我们与一个 Subject

交互,运行时shiro会自动转化为与 SecurityManager交互的特定 subject的交互


SecurityManager:

安全管理器,由框架提供的,SecurityManager 是 Shiro的核心,初始化时协调各个模块运行。然而,一旦 SecurityManager协调完毕,SecurityManager 会被单独留下,且我们只需要去操作Subject即可,无需操作SecurityManager 。 但是我们得知道,当我们正与一个 Subject 进行交互时,实质上是 SecurityManager在处理 Subject 安全操作。


Realm:

安全数据桥,类似于项目中的DAO,访问安全数据的,框架提供,开发人员也可自己编写

Realms在 Shiro中作为应用程序和安全数据之间的“桥梁”或“连接器”。他获取安全数据来判断subject是否能够登录,subject拥有什么权限。他有点类似DAO。在配置realms时,需要至少一个realm。而且Shiro提供了一些常用的 Realms来连接数据源,如LDAP数据源的JndiLdapRealm,JDBC数据源的JdbcRealm,ini文件数据源的IniRealm,properties文件数据源的PropertiesRealm,等等。我们也可以插入自己的 Realm实现来代表自定义的数据源。 像其他组件一样,Realms也是由SecurityManager控制


小结


1.Subject(org.apache.shiro.subject.Subject):简称用户

2.SecurityManager(org.apache.shiro.mgt.SecurityManager)

如上所述,SecurityManager是shiro的核心,协调shiro的各个组件

3.Authenticator(org.apache.shiro.authc.Authenticator):

登录控制

注:AuthenticationStrategy

(org.apache.shiro.authc.pam.AuthenticationStrategy)

如果存在多个realm,则接口AuthenticationStrategy会确定什么样算是登录成功(例如,如果一个Realm成功,而其他的均失败,是否登录成功?)。

4.Authorizer(org.apache.shiro.authz.Authorizer):

决定subject能拥有什么样角色或者权限。

5.SessionManager(org.apache.shiro.session.SessionManager):

创建和管理用户session。通过设置这个管理器,shiro可以在任何环境下使用session。

6.CacheManager(org.apahce.shiro.cache.CacheManager):

缓存管理器,可以减少不必要的后台访问。提高应用效率,增加用户体验。

7.Cryptography(org.apache.shiro.crypto.*) :

Shiro的api大幅度简化Java api中繁琐的密码加密。

8.Realms(org.apache.shiro.realm.Realm) :程序与安全数据的桥梁

 

5.将shiro应用到bos项目中

 

因为官方例子虽然中有更加简洁的ini配置形式,但是使用ini配置无法与spring整合

 

第一步:导入shiro的jar包到项目中

shiro-all-1.2.2.jar

第二步:在web.xml中配置一个过滤器,是由spring提供的,用于整合shiro

注意:配置到struts2核心过滤器的前面

filter-name 与spring xml 配置的 bean id一致

org.springframework.web.filter.DelegatingFilterProxy

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1.  <!-- 配置spring提供的过滤器,用于整合shiro框架 ,  
  2.     在项目启动过程中,当前过滤器会从spring工厂中获取一个和当前过滤器同名的bean对象-->  
  3. <filter>  
  4.     <filter-name>shiroFilter</filter-name>  
  5.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  6. </filter>  
  7. <filter-mapping>  
  8.     <filter-name>shiroFilter</filter-name>  
  9.     <url-pattern>/*</url-pattern>  
  10. </filter-mapping>  

第三步:在applicationContext.xml中配置bean,ID必须为shiroFilter


org.apache.shiro.spring.web.ShiroFilterFactoryBean 

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 配置shiro的过滤器对象,当前对象用于创建shiro框架需要的过滤器对象 -->  
  2. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  3.     <!-- 注入安全管理器 -->  
  4.     <property name="securityManager" ref="securityManager"/>  
  5.     <!-- 注入系统的登录访问路径 -->  
  6.     <property name="loginUrl" value="/login.jsp"/>  
  7.     <!-- 成功页面 -->  
  8.     <property name="successUrl" value="/index.jsp"/>  
  9.     <!-- 权限不足提示页面 -->  
  10.     <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>  
  11.     <!-- 基于URL拦截权限控制 -->  
  12.     <property name="filterChainDefinitions">  
  13.         <value>  
  14.             /css/** = anon  
  15.             /js/** = anon  
  16.             /images/** = anon  
  17.             /validatecode.jsp* = anon  
  18.             /login.jsp* = anon  
  19.             /userAction_login.action = anon  
  20.             /page_base_staff.action = perms["staff"]  
  21.             /* = authc  
  22.         </value>  
  23.     </property>  
  24. </bean>  

securityManager:这个属性是必须的。




安全管理器配置代码

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 定义安全管理器 -->  
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  3.     <!-- 注入realm -->  
  4.     <property name="realm" ref="bosRealm"></property>  
  5. </bean>  
  6.   
  7. <!-- 自定义Realm -->  
  8. <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>  

loginUrl :没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。

successUrl :登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。

unauthorizedUrl :没有权限默认跳转的页面。

 

过滤器


anon:

例子/admins/**=anon没有参数,表示可以匿名使用。

authc:

例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数

roles:

例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。

perms:

例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。

Rest:

例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等。

port:

例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString

是你访问的url里的?后面的参数。

authcBasic:

例如/admins/user/**=authcBasic没有参数表示httpBasic认证

ssl:

例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

user:

例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查

 

注:

anon,authcBasic,auchc,user是认证过滤器,

perms,roles,ssl,rest,port是授权过滤器

 

 

第四步:修改UserAction中的login方法,使用shiro提供的方式进行认证

过程:

1.通过subjec对象的login方法进行认证

2.Subject会调用securityManager安全管理器,安全管理器会调用Realm

3.securityManager安全管理器不是通过返回参数的方式而是通过抛异常来做出判定结果

用户名不存在异常:

org.apache.shiro.authc.UnknownAccountException  

密码错误异常:

org.apache.shiro.authc.IncorrectCredentialsException

 

代码

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. //提供属性接收验证码  
  2. private String checkcode;  
  3. public void setCheckcode(String checkcode) {  
  4.     this.checkcode = checkcode;  
  5. }  
  6. /** 
  7.  * 使用shiro方式进行认证 
  8.  */  
  9. public String login(){  
  10.     //从session中获取自动生成的验证码  
  11.     String key = (String) ActionContext.getContext().getSession().get("key");  
  12.     if(StringUtils.isNotBlank(checkcode) && checkcode.equals(key)){  
  13.         //使用shiro方式进行认证  
  14.         String username = model.getUsername();  
  15.         String password = model.getPassword();  
  16.         password = MD5Utils.md5(password);  
  17.         Subject subject = SecurityUtils.getSubject();//主体,当前状态为“未认证”状态  
  18.         AuthenticationToken token = new UsernamePasswordToken(username, password);//用户名密码令牌  
  19.         try{  
  20.             subject.login(token);//使用subject调用SecurityManager,安全管理器调用Realm  
  21.             User user = (User) subject.getPrincipal();  
  22.             //登录成功,将User对象放入session   
  23.             ActionContext.getContext().getSession().put("loginUser", user);  
  24.         }catch (UnknownAccountException e) {//用户名不存在异常  
  25.             e.printStackTrace();  
  26.             return "login";  
  27.         }catch (IncorrectCredentialsException e) {//密码错误异常  
  28.             e.printStackTrace();  
  29.             return "login";  
  30.         }  
  31.         return "home";  
  32.     }else{  
  33.         //验证码有误,添加错误信息,跳转到登录页面  
  34.         this.addActionError(this.getText("checkcodeError"));  
  35.         return "login";  
  36.     }  
  37. }  


第五步:自定义一个Realm,实现认证方法


继承抽象类 重写认证与授权方法

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class BOSRealm extends AuthorizingRealm {  
  2.     // 注入dao  
  3.     @Resource  
  4.     private IUserDao userDao;  
  5.   
  6.     // 认证方法  
  7.     protected AuthenticationInfo doGetAuthenticationInfo(  
  8.             AuthenticationToken token) throws AuthenticationException {  
  9.         System.out.println("认证方法。。。。");  
  10.         UsernamePasswordToken pwdToken = (UsernamePasswordToken) token;  
  11.         String username = pwdToken.getUsername();  
  12.         // 根据用户名查询密码,由安全管理器负责比对查询出的数据库中的密码和页面输入的密码是否一致  
  13.         User user = userDao.findUserByUsername(username);  
  14.         if(user == null){  
  15.             return null;  
  16.         }  
  17.         String dbPassword = user.getPassword();  
  18.         //参数一:  
  19.         AuthenticationInfo info = new SimpleAuthenticationInfo(user,  
  20.                 dbPassword, this.getClass().getSimpleName());  
  21.         return info;  
  22.     }  
  23.   
  24.     //授权方法  
  25.     protected AuthorizationInfo doGetAuthorizationInfo(  
  26.             PrincipalCollection principals) {  
  27.         //根据当前登陆用户,查询当前用户的角色,根据角色获取相应的权限,把权限添加到授权信息对象中  
  28.         //三种获得当前登陆用户的方式  
  29.         User user1 = (User) principals.getPrimaryPrincipal();  
  30.         User user2 = (User) SecurityUtils.getSubject().getPrincipal();  
  31.         //User user3 = (User) ActionContext.getContext().getSession().get("session_user");  
  32.   
  33.           
  34.         //由于添加权限 角色的 相应方法还没学习编码 暂用伪代码添加权限  
  35.         //spring是shrio配置中filterChainDefinitions 添加了/page_base_staff.action=perms["staff"]  
  36.         SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();  
  37.         info.addStringPermission("staff");  
  38. //      info.addStringPermission("region.query");  
  39.           
  40.         return info;  
  41.   
  42. }  


第六步:在applicationContext.xml中注册Realm,并注入给安全管理器

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 定义安全管理器 -->  
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  3.     <!-- 注入realm -->  
  4.     <property name="realm" ref="bosRealm"></property>  
  5. </bean>  
  6.   
  7. <!-- 自定义Realm -->  
  8. <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>  


第七步:在自定义Realm中实现授权方法

*授权方法执行的时机:为当访问的路径需要权限认证的时候,才执行授权

*这样每次访问权限路径都执行一次授权方法,效率很低,需要增加缓存管理器

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class BosRealm extends AuthorizingRealm{  
  2.     @Resource  
  3.     private IUserDao userDao;  
  4.     @Resource  
  5.     private IRoleDao roleDao;  
  6.     @Resource  
  7.     private IFunctionDao functionDao;  
  8.       
  9.     //认证方法  
  10.     protected AuthenticationInfo doGetAuthenticationInfo(  
  11.             AuthenticationToken token) throws AuthenticationException {  
  12.         System.out.println("认证方法。。。");  
  13.           
  14.         UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;  
  15.         String username = usernamePasswordToken.getUsername();  
  16.           
  17.         //1 根据用户名查询密码  
  18.         User user = userDao.findUserByUserName(username);  
  19.         if(user == null){  
  20.             // 用户名不存在  
  21.             return null;  
  22.         }  
  23.           
  24.         //2 返回AuthenticationInfo对象  
  25.         Object principal = user;//将当前查询到的用户对象放入SimpleAuthenticationInfo中,可以通过subject获得  
  26.         Object credentials = user.getPassword();//密码,shiro负责比较查询到的密码和用户输入的密码是否一致  
  27.         String realmName = super.getName();  
  28.         AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, credentials, realmName);  
  29.         return authenticationInfo;  
  30.     }  
  31.   
  32.     //授权方法  
  33.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  34.         System.out.println("授权...");  
  35.         // 获取当前登陆用户 ,根据当前登陆用户,查询对应角色信息  
  36.         Subject subject = SecurityUtils.getSubject();  
  37.         User user = (User) subject.getPrincipal();  
  38.   
  39.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  40.         if (user.getUsername().equals("admin")) {  
  41.             // 如果admin ,查询所有角色和所有权限  
  42.             List<Role> roles = roleDao.findAll();  
  43.             for (Role role : roles) {  
  44.                 authorizationInfo.addRole(role.getCode());  
  45.             }  
  46.             List<Function> functions = functionDao.findAll();  
  47.             for (Function function : functions) {  
  48.                 authorizationInfo.addStringPermission(function.getCode());  
  49.             }  
  50.         } else {  
  51.             // 普通用户 , 根据当前用户,查询具有角色,通过角色获取权限  
  52.             List<Role> roles = roleDao.findRolesByUser(user);  
  53.             // 添加角色  
  54.             for (Role role : roles) {  
  55.                 authorizationInfo.addRole(role.getCode());  
  56.                 // 添加角色对应权限  
  57.                 Set<Function> functions = role.getFunctions();  
  58.                 for (Function function : functions) {  
  59.                     authorizationInfo.addStringPermission(function.getCode());  
  60.                 }  
  61.             }  
  62.         }  
  63.         return authorizationInfo;  
  64.     }  
  65.       
  66.     protected AuthorizationInfo doGetAuthorizationInfo_bak(  
  67.             PrincipalCollection principals) {  
  68.         System.out.println("授权方法。。。");  
  69.         User user = (User) principals.getPrimaryPrincipal();  
  70.         System.out.println(user);  
  71.         //根据当前登录用户查询对应的权限和角色  
  72.           
  73.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  74.         authorizationInfo.addStringPermission("staff:query");  
  75.         authorizationInfo.addStringPermission("abc");  
  76.         authorizationInfo.addRole("admin");  
  77.         return authorizationInfo;  
  78.     }  
  79.   
  80. }  


 

6.    基于shiro注解实现权限控制


第一步:在spring配置文件中开启shiro注解功能

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 开启shiro注解自动代理 -->  
  2. <bean id="defaultAdvisorAutoProxyCreator"   
  3.     class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">  
  4.     <!-- 指定强制使用cglib为Action创建代理对象 -->  
  5.     <property name="proxyTargetClass" value="true"/>  
  6. </bean>  
  7.   
  8. <!-- 切面 -->  
  9. <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>  

第二步:在Action方法上使用注解进行权限控制

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.      * 分页查询方法 
  3.      * @throws IOException  
  4.      */  
  5.     @RequiresPermissions(value="region.query")//执行这个方法需要region.query权限  
  6.     //@RequiresRoles(value="admin")  
  7.     public String pageQuery() throws IOException{  
  8.         regionService.pageQuery(pageBean);  
  9.         this.writePageBean2Json(pageBean, new String[]{"currentPage","pageSize","detachedCriteria","subareas"});  
  10.         return NONE;  
  11.     }  

控制精度:
注解方式控制权限
只能是在方法上控制,无法控制类级别访问。
过滤器方式控制是根据访问的URL进行控制。允许使用*匹配URL,所以可以进行粗粒度,也可以进行细粒度控制。


其他注解

@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。


@ RequiresUser

验证用户是否被记忆,user有两种含义:
一种是成功登录的(subject.isAuthenticated() 结果为true);
另外一种是被记忆的( subject.isRemembered()结果为true)。


@ RequiresGuest
验证是否是一个guest的请求,与@ RequiresUser完全相反。
 换言之,RequiresUser  == ! RequiresGuest 。
此时subject.getPrincipal() 结果为null.


@ RequiresRoles

例如:@RequiresRoles("aRoleName");
void someMethod();
如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。


@RequiresPermissions

例如: @RequiresPermissions( {"file:read", "write:aFile.txt"} ) void someMethod();
要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException。

第三步:修改BaseAction的构造方法

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. // 构造方法  
  2. @SuppressWarnings("unchecked")  
  3. public BaseAction() {  
  4.     //获取父类类型(baseAction)  
  5.     ParameterizedType superclass = null;  
  6.     Type type = this.getClass().getGenericSuperclass();  
  7.     //获取父类泛型 强制转化参数类型 需要判断 生成action是否使用代理 既父类是否是参数化类型  
  8.     if(type instanceof ParameterizedType){  
  9.         //没有使用代理直接强制转换  
  10.         superclass = (ParameterizedType) type;  
  11.     }else{  
  12.         //type 不属于参数化类型 则action是cglib代理生成    
  13.         //CGLib是针对目标类生产子类,需要获取父类的父类的类型再强转成参数化类型  
  14.         superclass = (ParameterizedType) this.getClass().getSuperclass().getGenericSuperclass();  
  15.     }  
  16.     ...  


第四步:在自定义Realm中授权




内容安排:

1、权限概述(认证、授权)

2、常见的权限控制的方式(URL拦截、方法注解)

3、权限的数据模型(权限表、角色表、用户表、角色权限关系表、用户角色关系表)

4、apache shiro框架

5、将shiro应用到bos项目中(认证、授权)

 

1.    权限概述

系统中提供了很多功能,并不是所有的用户都可以操作这些功能,需要对功能的访问进行控制。

认证:系统提供的用于识别用户身份的功能,通常是登录功能(你是谁???)

授权:系统提供的赋予用户可以操作系统某些功能能力(你能做什么???)

 

菜单按钮是访问某个功能的入口,都是发起一次请求,由服务端进行相应的操作。

 

2.    常见的权限控制方式

 

2.1   URL拦截进行权限校验



2.2   方法注解权限控制



3.    权限的数据模型


权限表

角色表:引入角色是为了简化授权

用户表

角色权限关系表(多对多中间表)

用户角色关系表(多对多中间表)

 


 

关键字:注解后面可以使用

路径:访问action的路径

菜单:有些资源会挂到菜单上

优先级:用户菜单排序

父权限编号:权限的上下级,指向本表的主键


实体类


权限实体

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 权限实体 
  3.  * 
  4.  */  
  5. public class Function implements java.io.Serializable {  
  6.   
  7.     // Fields  
  8.   
  9.     private String id;//编号  
  10.     private Function parentFunction;//当前权限对应的上一级权限  
  11.     private String name;//权限名称  
  12.     private String code;//关键字  
  13.     private String description;//描述  
  14.     private String page;//访问URL  
  15.     private String generatemenu;//当前权限是否生成到菜单  1:生成 0:不生成  
  16.     private Integer zindex;//排序  
  17.     private Set children = new HashSet(0);//当前权限对应的多个下级权限  
  18.     private Set roles = new HashSet(0);//当前权限对应的多个角色  

角色实体

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 角色实体  
  3.  * 
  4.  */  
  5. public class Role implements java.io.Serializable {  
  6.   
  7.     // Fields  
  8.   
  9.     private String id;//编号  
  10.     private String name;//角色名称  
  11.     private String code;//关键字  
  12.     private String description;//描述  
  13.     private Set functions = new HashSet(0);//当前角色对应的多个权限  
  14.     private Set users = new HashSet(0);//当前角色对应的多个用户  

用户

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class User implements java.io.Serializable {  
  2.   
  3.     private String id;  
  4.     private String username;  
  5.     private String password;  
  6.     private Double salary;  
  7.     private Timestamp birthday;  
  8.     private String gender;  
  9.     private String station;  
  10.     private String telephone;  
  11.     private String remark;  
  12.     private Set noticebills = new HashSet(0);  
  13.     private Set roles = new HashSet(0);//当前用户对应的多个角色  


4.    apache shiro框架


提供的进行权限控制的方式:

1、URL拦截进行权限控制

2、方法注解权限控制

3、页面标签权限控制

4、代码方式权限控制(了解)

 

shiro提供的功能:


Shiro的4大部分——身份验证,授权,会话管理和加密

•  Authentication:身份验证,简称“登录”。

•  Authorization:授权,给用户分配角色或者权限资源

•  Session Management:用户session管理器,可以让CS程序也使用session来控制权限

•  Cryptography:把JDK中复杂的密码加密方式进行封装

除了以上功能,shiro还提供很多扩展

•  Web Support:主要针对web应用提供一些常用功能。

•  Caching:缓存可以使应用程序运行更有效率。

•  Concurrency:多线程相关功能。

•  Testing:帮助我们进行测试相关功能

•  "Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。

•  “Remember Me”:记住用户身份,提供类似购物车功能。

 

shiro框架运行流程图:


Application Code:应用程序代码,由开发人员负责


Subject

由框架提供的,是与程序进行交互的对象,可以是人也可以是服务或者其他,通常就理解为用户。

所有Subject 实例都必须绑定到一个SecurityManager上。我们与一个 Subject

交互,运行时shiro会自动转化为与 SecurityManager交互的特定 subject的交互


SecurityManager:

安全管理器,由框架提供的,SecurityManager 是 Shiro的核心,初始化时协调各个模块运行。然而,一旦 SecurityManager协调完毕,SecurityManager 会被单独留下,且我们只需要去操作Subject即可,无需操作SecurityManager 。 但是我们得知道,当我们正与一个 Subject 进行交互时,实质上是 SecurityManager在处理 Subject 安全操作。


Realm:

安全数据桥,类似于项目中的DAO,访问安全数据的,框架提供,开发人员也可自己编写

Realms在 Shiro中作为应用程序和安全数据之间的“桥梁”或“连接器”。他获取安全数据来判断subject是否能够登录,subject拥有什么权限。他有点类似DAO。在配置realms时,需要至少一个realm。而且Shiro提供了一些常用的 Realms来连接数据源,如LDAP数据源的JndiLdapRealm,JDBC数据源的JdbcRealm,ini文件数据源的IniRealm,properties文件数据源的PropertiesRealm,等等。我们也可以插入自己的 Realm实现来代表自定义的数据源。 像其他组件一样,Realms也是由SecurityManager控制


小结


1.Subject(org.apache.shiro.subject.Subject):简称用户

2.SecurityManager(org.apache.shiro.mgt.SecurityManager)

如上所述,SecurityManager是shiro的核心,协调shiro的各个组件

3.Authenticator(org.apache.shiro.authc.Authenticator):

登录控制

注:AuthenticationStrategy

(org.apache.shiro.authc.pam.AuthenticationStrategy)

如果存在多个realm,则接口AuthenticationStrategy会确定什么样算是登录成功(例如,如果一个Realm成功,而其他的均失败,是否登录成功?)。

4.Authorizer(org.apache.shiro.authz.Authorizer):

决定subject能拥有什么样角色或者权限。

5.SessionManager(org.apache.shiro.session.SessionManager):

创建和管理用户session。通过设置这个管理器,shiro可以在任何环境下使用session。

6.CacheManager(org.apahce.shiro.cache.CacheManager):

缓存管理器,可以减少不必要的后台访问。提高应用效率,增加用户体验。

7.Cryptography(org.apache.shiro.crypto.*) :

Shiro的api大幅度简化Java api中繁琐的密码加密。

8.Realms(org.apache.shiro.realm.Realm) :程序与安全数据的桥梁

 

5.将shiro应用到bos项目中

 

因为官方例子虽然中有更加简洁的ini配置形式,但是使用ini配置无法与spring整合

 

第一步:导入shiro的jar包到项目中

shiro-all-1.2.2.jar

第二步:在web.xml中配置一个过滤器,是由spring提供的,用于整合shiro

注意:配置到struts2核心过滤器的前面

filter-name 与spring xml 配置的 bean id一致

org.springframework.web.filter.DelegatingFilterProxy

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1.  <!-- 配置spring提供的过滤器,用于整合shiro框架 ,  
  2.     在项目启动过程中,当前过滤器会从spring工厂中获取一个和当前过滤器同名的bean对象-->  
  3. <filter>  
  4.     <filter-name>shiroFilter</filter-name>  
  5.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  6. </filter>  
  7. <filter-mapping>  
  8.     <filter-name>shiroFilter</filter-name>  
  9.     <url-pattern>/*</url-pattern>  
  10. </filter-mapping>  

第三步:在applicationContext.xml中配置bean,ID必须为shiroFilter


org.apache.shiro.spring.web.ShiroFilterFactoryBean 

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 配置shiro的过滤器对象,当前对象用于创建shiro框架需要的过滤器对象 -->  
  2. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  3.     <!-- 注入安全管理器 -->  
  4.     <property name="securityManager" ref="securityManager"/>  
  5.     <!-- 注入系统的登录访问路径 -->  
  6.     <property name="loginUrl" value="/login.jsp"/>  
  7.     <!-- 成功页面 -->  
  8.     <property name="successUrl" value="/index.jsp"/>  
  9.     <!-- 权限不足提示页面 -->  
  10.     <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>  
  11.     <!-- 基于URL拦截权限控制 -->  
  12.     <property name="filterChainDefinitions">  
  13.         <value>  
  14.             /css/** = anon  
  15.             /js/** = anon  
  16.             /images/** = anon  
  17.             /validatecode.jsp* = anon  
  18.             /login.jsp* = anon  
  19.             /userAction_login.action = anon  
  20.             /page_base_staff.action = perms["staff"]  
  21.             /* = authc  
  22.         </value>  
  23.     </property>  
  24. </bean>  

securityManager:这个属性是必须的。




安全管理器配置代码

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 定义安全管理器 -->  
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  3.     <!-- 注入realm -->  
  4.     <property name="realm" ref="bosRealm"></property>  
  5. </bean>  
  6.   
  7. <!-- 自定义Realm -->  
  8. <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>  

loginUrl :没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。

successUrl :登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。

unauthorizedUrl :没有权限默认跳转的页面。

 

过滤器


anon:

例子/admins/**=anon没有参数,表示可以匿名使用。

authc:

例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数

roles:

例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。

perms:

例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。

Rest:

例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等。

port:

例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString

是你访问的url里的?后面的参数。

authcBasic:

例如/admins/user/**=authcBasic没有参数表示httpBasic认证

ssl:

例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

user:

例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查

 

注:

anon,authcBasic,auchc,user是认证过滤器,

perms,roles,ssl,rest,port是授权过滤器

 

 

第四步:修改UserAction中的login方法,使用shiro提供的方式进行认证

过程:

1.通过subjec对象的login方法进行认证

2.Subject会调用securityManager安全管理器,安全管理器会调用Realm

3.securityManager安全管理器不是通过返回参数的方式而是通过抛异常来做出判定结果

用户名不存在异常:

org.apache.shiro.authc.UnknownAccountException  

密码错误异常:

org.apache.shiro.authc.IncorrectCredentialsException

 

代码

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. //提供属性接收验证码  
  2. private String checkcode;  
  3. public void setCheckcode(String checkcode) {  
  4.     this.checkcode = checkcode;  
  5. }  
  6. /** 
  7.  * 使用shiro方式进行认证 
  8.  */  
  9. public String login(){  
  10.     //从session中获取自动生成的验证码  
  11.     String key = (String) ActionContext.getContext().getSession().get("key");  
  12.     if(StringUtils.isNotBlank(checkcode) && checkcode.equals(key)){  
  13.         //使用shiro方式进行认证  
  14.         String username = model.getUsername();  
  15.         String password = model.getPassword();  
  16.         password = MD5Utils.md5(password);  
  17.         Subject subject = SecurityUtils.getSubject();//主体,当前状态为“未认证”状态  
  18.         AuthenticationToken token = new UsernamePasswordToken(username, password);//用户名密码令牌  
  19.         try{  
  20.             subject.login(token);//使用subject调用SecurityManager,安全管理器调用Realm  
  21.             User user = (User) subject.getPrincipal();  
  22.             //登录成功,将User对象放入session   
  23.             ActionContext.getContext().getSession().put("loginUser", user);  
  24.         }catch (UnknownAccountException e) {//用户名不存在异常  
  25.             e.printStackTrace();  
  26.             return "login";  
  27.         }catch (IncorrectCredentialsException e) {//密码错误异常  
  28.             e.printStackTrace();  
  29.             return "login";  
  30.         }  
  31.         return "home";  
  32.     }else{  
  33.         //验证码有误,添加错误信息,跳转到登录页面  
  34.         this.addActionError(this.getText("checkcodeError"));  
  35.         return "login";  
  36.     }  
  37. }  


第五步:自定义一个Realm,实现认证方法


继承抽象类 重写认证与授权方法

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class BOSRealm extends AuthorizingRealm {  
  2.     // 注入dao  
  3.     @Resource  
  4.     private IUserDao userDao;  
  5.   
  6.     // 认证方法  
  7.     protected AuthenticationInfo doGetAuthenticationInfo(  
  8.             AuthenticationToken token) throws AuthenticationException {  
  9.         System.out.println("认证方法。。。。");  
  10.         UsernamePasswordToken pwdToken = (UsernamePasswordToken) token;  
  11.         String username = pwdToken.getUsername();  
  12.         // 根据用户名查询密码,由安全管理器负责比对查询出的数据库中的密码和页面输入的密码是否一致  
  13.         User user = userDao.findUserByUsername(username);  
  14.         if(user == null){  
  15.             return null;  
  16.         }  
  17.         String dbPassword = user.getPassword();  
  18.         //参数一:  
  19.         AuthenticationInfo info = new SimpleAuthenticationInfo(user,  
  20.                 dbPassword, this.getClass().getSimpleName());  
  21.         return info;  
  22.     }  
  23.   
  24.     //授权方法  
  25.     protected AuthorizationInfo doGetAuthorizationInfo(  
  26.             PrincipalCollection principals) {  
  27.         //根据当前登陆用户,查询当前用户的角色,根据角色获取相应的权限,把权限添加到授权信息对象中  
  28.         //三种获得当前登陆用户的方式  
  29.         User user1 = (User) principals.getPrimaryPrincipal();  
  30.         User user2 = (User) SecurityUtils.getSubject().getPrincipal();  
  31.         //User user3 = (User) ActionContext.getContext().getSession().get("session_user");  
  32.   
  33.           
  34.         //由于添加权限 角色的 相应方法还没学习编码 暂用伪代码添加权限  
  35.         //spring是shrio配置中filterChainDefinitions 添加了/page_base_staff.action=perms["staff"]  
  36.         SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();  
  37.         info.addStringPermission("staff");  
  38. //      info.addStringPermission("region.query");  
  39.           
  40.         return info;  
  41.   
  42. }  


第六步:在applicationContext.xml中注册Realm,并注入给安全管理器

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 定义安全管理器 -->  
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  3.     <!-- 注入realm -->  
  4.     <property name="realm" ref="bosRealm"></property>  
  5. </bean>  
  6.   
  7. <!-- 自定义Realm -->  
  8. <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>  


第七步:在自定义Realm中实现授权方法

*授权方法执行的时机:为当访问的路径需要权限认证的时候,才执行授权

*这样每次访问权限路径都执行一次授权方法,效率很低,需要增加缓存管理器

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class BosRealm extends AuthorizingRealm{  
  2.     @Resource  
  3.     private IUserDao userDao;  
  4.     @Resource  
  5.     private IRoleDao roleDao;  
  6.     @Resource  
  7.     private IFunctionDao functionDao;  
  8.       
  9.     //认证方法  
  10.     protected AuthenticationInfo doGetAuthenticationInfo(  
  11.             AuthenticationToken token) throws AuthenticationException {  
  12.         System.out.println("认证方法。。。");  
  13.           
  14.         UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;  
  15.         String username = usernamePasswordToken.getUsername();  
  16.           
  17.         //1 根据用户名查询密码  
  18.         User user = userDao.findUserByUserName(username);  
  19.         if(user == null){  
  20.             // 用户名不存在  
  21.             return null;  
  22.         }  
  23.           
  24.         //2 返回AuthenticationInfo对象  
  25.         Object principal = user;//将当前查询到的用户对象放入SimpleAuthenticationInfo中,可以通过subject获得  
  26.         Object credentials = user.getPassword();//密码,shiro负责比较查询到的密码和用户输入的密码是否一致  
  27.         String realmName = super.getName();  
  28.         AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, credentials, realmName);  
  29.         return authenticationInfo;  
  30.     }  
  31.   
  32.     //授权方法  
  33.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  34.         System.out.println("授权...");  
  35.         // 获取当前登陆用户 ,根据当前登陆用户,查询对应角色信息  
  36.         Subject subject = SecurityUtils.getSubject();  
  37.         User user = (User) subject.getPrincipal();  
  38.   
  39.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  40.         if (user.getUsername().equals("admin")) {  
  41.             // 如果admin ,查询所有角色和所有权限  
  42.             List<Role> roles = roleDao.findAll();  
  43.             for (Role role : roles) {  
  44.                 authorizationInfo.addRole(role.getCode());  
  45.             }  
  46.             List<Function> functions = functionDao.findAll();  
  47.             for (Function function : functions) {  
  48.                 authorizationInfo.addStringPermission(function.getCode());  
  49.             }  
  50.         } else {  
  51.             // 普通用户 , 根据当前用户,查询具有角色,通过角色获取权限  
  52.             List<Role> roles = roleDao.findRolesByUser(user);  
  53.             // 添加角色  
  54.             for (Role role : roles) {  
  55.                 authorizationInfo.addRole(role.getCode());  
  56.                 // 添加角色对应权限  
  57.                 Set<Function> functions = role.getFunctions();  
  58.                 for (Function function : functions) {  
  59.                     authorizationInfo.addStringPermission(function.getCode());  
  60.                 }  
  61.             }  
  62.         }  
  63.         return authorizationInfo;  
  64.     }  
  65.       
  66.     protected AuthorizationInfo doGetAuthorizationInfo_bak(  
  67.             PrincipalCollection principals) {  
  68.         System.out.println("授权方法。。。");  
  69.         User user = (User) principals.getPrimaryPrincipal();  
  70.         System.out.println(user);  
  71.         //根据当前登录用户查询对应的权限和角色  
  72.           
  73.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  74.         authorizationInfo.addStringPermission("staff:query");  
  75.         authorizationInfo.addStringPermission("abc");  
  76.         authorizationInfo.addRole("admin");  
  77.         return authorizationInfo;  
  78.     }  
  79.   
  80. }  


 

6.    基于shiro注解实现权限控制


第一步:在spring配置文件中开启shiro注解功能

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 开启shiro注解自动代理 -->  
  2. <bean id="defaultAdvisorAutoProxyCreator"   
  3.     class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">  
  4.     <!-- 指定强制使用cglib为Action创建代理对象 -->  
  5.     <property name="proxyTargetClass" value="true"/>  
  6. </bean>  
  7.   
  8. <!-- 切面 -->  
  9. <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>  

第二步:在Action方法上使用注解进行权限控制

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.      * 分页查询方法 
  3.      * @throws IOException  
  4.      */  
  5.     @RequiresPermissions(value="region.query")//执行这个方法需要region.query权限  
  6.     //@RequiresRoles(value="admin")  
  7.     public String pageQuery() throws IOException{  
  8.         regionService.pageQuery(pageBean);  
  9.         this.writePageBean2Json(pageBean, new String[]{"currentPage","pageSize","detachedCriteria","subareas"});  
  10.         return NONE;  
  11.     }  

控制精度:
注解方式控制权限
只能是在方法上控制,无法控制类级别访问。
过滤器方式控制是根据访问的URL进行控制。允许使用*匹配URL,所以可以进行粗粒度,也可以进行细粒度控制。


其他注解

@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。


@ RequiresUser

验证用户是否被记忆,user有两种含义:
一种是成功登录的(subject.isAuthenticated() 结果为true);
另外一种是被记忆的( subject.isRemembered()结果为true)。


@ RequiresGuest
验证是否是一个guest的请求,与@ RequiresUser完全相反。
 换言之,RequiresUser  == ! RequiresGuest 。
此时subject.getPrincipal() 结果为null.


@ RequiresRoles

例如:@RequiresRoles("aRoleName");
void someMethod();
如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。


@RequiresPermissions

例如: @RequiresPermissions( {"file:read", "write:aFile.txt"} ) void someMethod();
要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException。

第三步:修改BaseAction的构造方法

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. // 构造方法  
  2. @SuppressWarnings("unchecked")  
  3. public BaseAction() {  
  4.     //获取父类类型(baseAction)  
  5.     ParameterizedType superclass = null;  
  6.     Type type = this.getClass().getGenericSuperclass();  
  7.     //获取父类泛型 强制转化参数类型 需要判断 生成action是否使用代理 既父类是否是参数化类型  
  8.     if(type instanceof ParameterizedType){  
  9.         //没有使用代理直接强制转换  
  10.         superclass = (ParameterizedType) type;  
  11.     }else{  
  12.         //type 不属于参数化类型 则action是cglib代理生成    
  13.         //CGLib是针对目标类生产子类,需要获取父类的父类的类型再强转成参数化类型  
  14.         superclass = (ParameterizedType) this.getClass().getSuperclass().getGenericSuperclass();  
  15.     }  
  16.     ...  


第四步:在自定义Realm中授权


内容安排:

1、权限概述(认证、授权)

2、常见的权限控制的方式(URL拦截、方法注解)

3、权限的数据模型(权限表、角色表、用户表、角色权限关系表、用户角色关系表)

4、apache shiro框架

5、将shiro应用到bos项目中(认证、授权)

 

1.    权限概述

系统中提供了很多功能,并不是所有的用户都可以操作这些功能,需要对功能的访问进行控制。

认证:系统提供的用于识别用户身份的功能,通常是登录功能(你是谁???)

授权:系统提供的赋予用户可以操作系统某些功能能力(你能做什么???)

 

菜单按钮是访问某个功能的入口,都是发起一次请求,由服务端进行相应的操作。

 

2.    常见的权限控制方式

 

2.1   URL拦截进行权限校验



2.2   方法注解权限控制



3.    权限的数据模型


权限表

角色表:引入角色是为了简化授权

用户表

角色权限关系表(多对多中间表)

用户角色关系表(多对多中间表)

 


 

关键字:注解后面可以使用

路径:访问action的路径

菜单:有些资源会挂到菜单上

优先级:用户菜单排序

父权限编号:权限的上下级,指向本表的主键


实体类


权限实体

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 权限实体 
  3.  * 
  4.  */  
  5. public class Function implements java.io.Serializable {  
  6.   
  7.     // Fields  
  8.   
  9.     private String id;//编号  
  10.     private Function parentFunction;//当前权限对应的上一级权限  
  11.     private String name;//权限名称  
  12.     private String code;//关键字  
  13.     private String description;//描述  
  14.     private String page;//访问URL  
  15.     private String generatemenu;//当前权限是否生成到菜单  1:生成 0:不生成  
  16.     private Integer zindex;//排序  
  17.     private Set children = new HashSet(0);//当前权限对应的多个下级权限  
  18.     private Set roles = new HashSet(0);//当前权限对应的多个角色  

角色实体

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * 角色实体  
  3.  * 
  4.  */  
  5. public class Role implements java.io.Serializable {  
  6.   
  7.     // Fields  
  8.   
  9.     private String id;//编号  
  10.     private String name;//角色名称  
  11.     private String code;//关键字  
  12.     private String description;//描述  
  13.     private Set functions = new HashSet(0);//当前角色对应的多个权限  
  14.     private Set users = new HashSet(0);//当前角色对应的多个用户  

用户

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class User implements java.io.Serializable {  
  2.   
  3.     private String id;  
  4.     private String username;  
  5.     private String password;  
  6.     private Double salary;  
  7.     private Timestamp birthday;  
  8.     private String gender;  
  9.     private String station;  
  10.     private String telephone;  
  11.     private String remark;  
  12.     private Set noticebills = new HashSet(0);  
  13.     private Set roles = new HashSet(0);//当前用户对应的多个角色  


4.    apache shiro框架


提供的进行权限控制的方式:

1、URL拦截进行权限控制

2、方法注解权限控制

3、页面标签权限控制

4、代码方式权限控制(了解)

 

shiro提供的功能:


Shiro的4大部分——身份验证,授权,会话管理和加密

•  Authentication:身份验证,简称“登录”。

•  Authorization:授权,给用户分配角色或者权限资源

•  Session Management:用户session管理器,可以让CS程序也使用session来控制权限

•  Cryptography:把JDK中复杂的密码加密方式进行封装

除了以上功能,shiro还提供很多扩展

•  Web Support:主要针对web应用提供一些常用功能。

•  Caching:缓存可以使应用程序运行更有效率。

•  Concurrency:多线程相关功能。

•  Testing:帮助我们进行测试相关功能

•  "Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。

•  “Remember Me”:记住用户身份,提供类似购物车功能。

 

shiro框架运行流程图:


Application Code:应用程序代码,由开发人员负责


Subject

由框架提供的,是与程序进行交互的对象,可以是人也可以是服务或者其他,通常就理解为用户。

所有Subject 实例都必须绑定到一个SecurityManager上。我们与一个 Subject

交互,运行时shiro会自动转化为与 SecurityManager交互的特定 subject的交互


SecurityManager:

安全管理器,由框架提供的,SecurityManager 是 Shiro的核心,初始化时协调各个模块运行。然而,一旦 SecurityManager协调完毕,SecurityManager 会被单独留下,且我们只需要去操作Subject即可,无需操作SecurityManager 。 但是我们得知道,当我们正与一个 Subject 进行交互时,实质上是 SecurityManager在处理 Subject 安全操作。


Realm:

安全数据桥,类似于项目中的DAO,访问安全数据的,框架提供,开发人员也可自己编写

Realms在 Shiro中作为应用程序和安全数据之间的“桥梁”或“连接器”。他获取安全数据来判断subject是否能够登录,subject拥有什么权限。他有点类似DAO。在配置realms时,需要至少一个realm。而且Shiro提供了一些常用的 Realms来连接数据源,如LDAP数据源的JndiLdapRealm,JDBC数据源的JdbcRealm,ini文件数据源的IniRealm,properties文件数据源的PropertiesRealm,等等。我们也可以插入自己的 Realm实现来代表自定义的数据源。 像其他组件一样,Realms也是由SecurityManager控制


小结


1.Subject(org.apache.shiro.subject.Subject):简称用户

2.SecurityManager(org.apache.shiro.mgt.SecurityManager)

如上所述,SecurityManager是shiro的核心,协调shiro的各个组件

3.Authenticator(org.apache.shiro.authc.Authenticator):

登录控制

注:AuthenticationStrategy

(org.apache.shiro.authc.pam.AuthenticationStrategy)

如果存在多个realm,则接口AuthenticationStrategy会确定什么样算是登录成功(例如,如果一个Realm成功,而其他的均失败,是否登录成功?)。

4.Authorizer(org.apache.shiro.authz.Authorizer):

决定subject能拥有什么样角色或者权限。

5.SessionManager(org.apache.shiro.session.SessionManager):

创建和管理用户session。通过设置这个管理器,shiro可以在任何环境下使用session。

6.CacheManager(org.apahce.shiro.cache.CacheManager):

缓存管理器,可以减少不必要的后台访问。提高应用效率,增加用户体验。

7.Cryptography(org.apache.shiro.crypto.*) :

Shiro的api大幅度简化Java api中繁琐的密码加密。

8.Realms(org.apache.shiro.realm.Realm) :程序与安全数据的桥梁

 

5.将shiro应用到bos项目中

 

因为官方例子虽然中有更加简洁的ini配置形式,但是使用ini配置无法与spring整合

 

第一步:导入shiro的jar包到项目中

shiro-all-1.2.2.jar

第二步:在web.xml中配置一个过滤器,是由spring提供的,用于整合shiro

注意:配置到struts2核心过滤器的前面

filter-name 与spring xml 配置的 bean id一致

org.springframework.web.filter.DelegatingFilterProxy

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1.  <!-- 配置spring提供的过滤器,用于整合shiro框架 ,  
  2.     在项目启动过程中,当前过滤器会从spring工厂中获取一个和当前过滤器同名的bean对象-->  
  3. <filter>  
  4.     <filter-name>shiroFilter</filter-name>  
  5.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  6. </filter>  
  7. <filter-mapping>  
  8.     <filter-name>shiroFilter</filter-name>  
  9.     <url-pattern>/*</url-pattern>  
  10. </filter-mapping>  

第三步:在applicationContext.xml中配置bean,ID必须为shiroFilter


org.apache.shiro.spring.web.ShiroFilterFactoryBean 

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 配置shiro的过滤器对象,当前对象用于创建shiro框架需要的过滤器对象 -->  
  2. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  3.     <!-- 注入安全管理器 -->  
  4.     <property name="securityManager" ref="securityManager"/>  
  5.     <!-- 注入系统的登录访问路径 -->  
  6.     <property name="loginUrl" value="/login.jsp"/>  
  7.     <!-- 成功页面 -->  
  8.     <property name="successUrl" value="/index.jsp"/>  
  9.     <!-- 权限不足提示页面 -->  
  10.     <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>  
  11.     <!-- 基于URL拦截权限控制 -->  
  12.     <property name="filterChainDefinitions">  
  13.         <value>  
  14.             /css/** = anon  
  15.             /js/** = anon  
  16.             /images/** = anon  
  17.             /validatecode.jsp* = anon  
  18.             /login.jsp* = anon  
  19.             /userAction_login.action = anon  
  20.             /page_base_staff.action = perms["staff"]  
  21.             /* = authc  
  22.         </value>  
  23.     </property>  
  24. </bean>  

securityManager:这个属性是必须的。




安全管理器配置代码

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 定义安全管理器 -->  
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  3.     <!-- 注入realm -->  
  4.     <property name="realm" ref="bosRealm"></property>  
  5. </bean>  
  6.   
  7. <!-- 自定义Realm -->  
  8. <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>  

loginUrl :没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。

successUrl :登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。

unauthorizedUrl :没有权限默认跳转的页面。

 

过滤器


anon:

例子/admins/**=anon没有参数,表示可以匿名使用。

authc:

例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数

roles:

例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。

perms:

例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。

Rest:

例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等。

port:

例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString

是你访问的url里的?后面的参数。

authcBasic:

例如/admins/user/**=authcBasic没有参数表示httpBasic认证

ssl:

例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

user:

例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查

 

注:

anon,authcBasic,auchc,user是认证过滤器,

perms,roles,ssl,rest,port是授权过滤器

 

 

第四步:修改UserAction中的login方法,使用shiro提供的方式进行认证

过程:

1.通过subjec对象的login方法进行认证

2.Subject会调用securityManager安全管理器,安全管理器会调用Realm

3.securityManager安全管理器不是通过返回参数的方式而是通过抛异常来做出判定结果

用户名不存在异常:

org.apache.shiro.authc.UnknownAccountException  

密码错误异常:

org.apache.shiro.authc.IncorrectCredentialsException

 

代码

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. //提供属性接收验证码  
  2. private String checkcode;  
  3. public void setCheckcode(String checkcode) {  
  4.     this.checkcode = checkcode;  
  5. }  
  6. /** 
  7.  * 使用shiro方式进行认证 
  8.  */  
  9. public String login(){  
  10.     //从session中获取自动生成的验证码  
  11.     String key = (String) ActionContext.getContext().getSession().get("key");  
  12.     if(StringUtils.isNotBlank(checkcode) && checkcode.equals(key)){  
  13.         //使用shiro方式进行认证  
  14.         String username = model.getUsername();  
  15.         String password = model.getPassword();  
  16.         password = MD5Utils.md5(password);  
  17.         Subject subject = SecurityUtils.getSubject();//主体,当前状态为“未认证”状态  
  18.         AuthenticationToken token = new UsernamePasswordToken(username, password);//用户名密码令牌  
  19.         try{  
  20.             subject.login(token);//使用subject调用SecurityManager,安全管理器调用Realm  
  21.             User user = (User) subject.getPrincipal();  
  22.             //登录成功,将User对象放入session   
  23.             ActionContext.getContext().getSession().put("loginUser", user);  
  24.         }catch (UnknownAccountException e) {//用户名不存在异常  
  25.             e.printStackTrace();  
  26.             return "login";  
  27.         }catch (IncorrectCredentialsException e) {//密码错误异常  
  28.             e.printStackTrace();  
  29.             return "login";  
  30.         }  
  31.         return "home";  
  32.     }else{  
  33.         //验证码有误,添加错误信息,跳转到登录页面  
  34.         this.addActionError(this.getText("checkcodeError"));  
  35.         return "login";  
  36.     }  
  37. }  


第五步:自定义一个Realm,实现认证方法


继承抽象类 重写认证与授权方法

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class BOSRealm extends AuthorizingRealm {  
  2.     // 注入dao  
  3.     @Resource  
  4.     private IUserDao userDao;  
  5.   
  6.     // 认证方法  
  7.     protected AuthenticationInfo doGetAuthenticationInfo(  
  8.             AuthenticationToken token) throws AuthenticationException {  
  9.         System.out.println("认证方法。。。。");  
  10.         UsernamePasswordToken pwdToken = (UsernamePasswordToken) token;  
  11.         String username = pwdToken.getUsername();  
  12.         // 根据用户名查询密码,由安全管理器负责比对查询出的数据库中的密码和页面输入的密码是否一致  
  13.         User user = userDao.findUserByUsername(username);  
  14.         if(user == null){  
  15.             return null;  
  16.         }  
  17.         String dbPassword = user.getPassword();  
  18.         //参数一:  
  19.         AuthenticationInfo info = new SimpleAuthenticationInfo(user,  
  20.                 dbPassword, this.getClass().getSimpleName());  
  21.         return info;  
  22.     }  
  23.   
  24.     //授权方法  
  25.     protected AuthorizationInfo doGetAuthorizationInfo(  
  26.             PrincipalCollection principals) {  
  27.         //根据当前登陆用户,查询当前用户的角色,根据角色获取相应的权限,把权限添加到授权信息对象中  
  28.         //三种获得当前登陆用户的方式  
  29.         User user1 = (User) principals.getPrimaryPrincipal();  
  30.         User user2 = (User) SecurityUtils.getSubject().getPrincipal();  
  31.         //User user3 = (User) ActionContext.getContext().getSession().get("session_user");  
  32.   
  33.           
  34.         //由于添加权限 角色的 相应方法还没学习编码 暂用伪代码添加权限  
  35.         //spring是shrio配置中filterChainDefinitions 添加了/page_base_staff.action=perms["staff"]  
  36.         SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();  
  37.         info.addStringPermission("staff");  
  38. //      info.addStringPermission("region.query");  
  39.           
  40.         return info;  
  41.   
  42. }  


第六步:在applicationContext.xml中注册Realm,并注入给安全管理器

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 定义安全管理器 -->  
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  3.     <!-- 注入realm -->  
  4.     <property name="realm" ref="bosRealm"></property>  
  5. </bean>  
  6.   
  7. <!-- 自定义Realm -->  
  8. <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>  


第七步:在自定义Realm中实现授权方法

*授权方法执行的时机:为当访问的路径需要权限认证的时候,才执行授权

*这样每次访问权限路径都执行一次授权方法,效率很低,需要增加缓存管理器

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public class BosRealm extends AuthorizingRealm{  
  2.     @Resource  
  3.     private IUserDao userDao;  
  4.     @Resource  
  5.     private IRoleDao roleDao;  
  6.     @Resource  
  7.     private IFunctionDao functionDao;  
  8.       
  9.     //认证方法  
  10.     protected AuthenticationInfo doGetAuthenticationInfo(  
  11.             AuthenticationToken token) throws AuthenticationException {  
  12.         System.out.println("认证方法。。。");  
  13.           
  14.         UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;  
  15.         String username = usernamePasswordToken.getUsername();  
  16.           
  17.         //1 根据用户名查询密码  
  18.         User user = userDao.findUserByUserName(username);  
  19.         if(user == null){  
  20.             // 用户名不存在  
  21.             return null;  
  22.         }  
  23.           
  24.         //2 返回AuthenticationInfo对象  
  25.         Object principal = user;//将当前查询到的用户对象放入SimpleAuthenticationInfo中,可以通过subject获得  
  26.         Object credentials = user.getPassword();//密码,shiro负责比较查询到的密码和用户输入的密码是否一致  
  27.         String realmName = super.getName();  
  28.         AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, credentials, realmName);  
  29.         return authenticationInfo;  
  30.     }  
  31.   
  32.     //授权方法  
  33.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  34.         System.out.println("授权...");  
  35.         // 获取当前登陆用户 ,根据当前登陆用户,查询对应角色信息  
  36.         Subject subject = SecurityUtils.getSubject();  
  37.         User user = (User) subject.getPrincipal();  
  38.   
  39.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  40.         if (user.getUsername().equals("admin")) {  
  41.             // 如果admin ,查询所有角色和所有权限  
  42.             List<Role> roles = roleDao.findAll();  
  43.             for (Role role : roles) {  
  44.                 authorizationInfo.addRole(role.getCode());  
  45.             }  
  46.             List<Function> functions = functionDao.findAll();  
  47.             for (Function function : functions) {  
  48.                 authorizationInfo.addStringPermission(function.getCode());  
  49.             }  
  50.         } else {  
  51.             // 普通用户 , 根据当前用户,查询具有角色,通过角色获取权限  
  52.             List<Role> roles = roleDao.findRolesByUser(user);  
  53.             // 添加角色  
  54.             for (Role role : roles) {  
  55.                 authorizationInfo.addRole(role.getCode());  
  56.                 // 添加角色对应权限  
  57.                 Set<Function> functions = role.getFunctions();  
  58.                 for (Function function : functions) {  
  59.                     authorizationInfo.addStringPermission(function.getCode());  
  60.                 }  
  61.             }  
  62.         }  
  63.         return authorizationInfo;  
  64.     }  
  65.       
  66.     protected AuthorizationInfo doGetAuthorizationInfo_bak(  
  67.             PrincipalCollection principals) {  
  68.         System.out.println("授权方法。。。");  
  69.         User user = (User) principals.getPrimaryPrincipal();  
  70.         System.out.println(user);  
  71.         //根据当前登录用户查询对应的权限和角色  
  72.           
  73.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  74.         authorizationInfo.addStringPermission("staff:query");  
  75.         authorizationInfo.addStringPermission("abc");  
  76.         authorizationInfo.addRole("admin");  
  77.         return authorizationInfo;  
  78.     }  
  79.   
  80. }  


 

6.    基于shiro注解实现权限控制


第一步:在spring配置文件中开启shiro注解功能

[html]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. <!-- 开启shiro注解自动代理 -->  
  2. <bean id="defaultAdvisorAutoProxyCreator"   
  3.     class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">  
  4.     <!-- 指定强制使用cglib为Action创建代理对象 -->  
  5.     <property name="proxyTargetClass" value="true"/>  
  6. </bean>  
  7.   
  8. <!-- 切面 -->  
  9. <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>  

第二步:在Action方法上使用注解进行权限控制

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.      * 分页查询方法 
  3.      * @throws IOException  
  4.      */  
  5.     @RequiresPermissions(value="region.query")//执行这个方法需要region.query权限  
  6.     //@RequiresRoles(value="admin")  
  7.     public String pageQuery() throws IOException{  
  8.         regionService.pageQuery(pageBean);  
  9.         this.writePageBean2Json(pageBean, new String[]{"currentPage","pageSize","detachedCriteria","subareas"});  
  10.         return NONE;  
  11.     }  

控制精度:
注解方式控制权限
只能是在方法上控制,无法控制类级别访问。
过滤器方式控制是根据访问的URL进行控制。允许使用*匹配URL,所以可以进行粗粒度,也可以进行细粒度控制。


其他注解

@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。


@ RequiresUser

验证用户是否被记忆,user有两种含义:
一种是成功登录的(subject.isAuthenticated() 结果为true);
另外一种是被记忆的( subject.isRemembered()结果为true)。


@ RequiresGuest
验证是否是一个guest的请求,与@ RequiresUser完全相反。
 换言之,RequiresUser  == ! RequiresGuest 。
此时subject.getPrincipal() 结果为null.


@ RequiresRoles

例如:@RequiresRoles("aRoleName");
void someMethod();
如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。


@RequiresPermissions

例如: @RequiresPermissions( {"file:read", "write:aFile.txt"} ) void someMethod();
要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException。

第三步:修改BaseAction的构造方法

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. // 构造方法  
  2. @SuppressWarnings("unchecked")  
  3. public BaseAction() {  
  4.     //获取父类类型(baseAction)  
  5.     ParameterizedType superclass = null;  
  6.     Type type = this.getClass().getGenericSuperclass();  
  7.     //获取父类泛型 强制转化参数类型 需要判断 生成action是否使用代理 既父类是否是参数化类型  
  8.     if(type instanceof ParameterizedType){  
  9.         //没有使用代理直接强制转换  
  10.         superclass = (ParameterizedType) type;  
  11.     }else{  
  12.         //type 不属于参数化类型 则action是cglib代理生成    
  13.         //CGLib是针对目标类生产子类,需要获取父类的父类的类型再强转成参数化类型  
  14.         superclass = (ParameterizedType) this.getClass().getSuperclass().getGenericSuperclass();  
  15.     }  
  16.     ...  


第四步:在自定义Realm中授权


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值