1、Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java项目的安全框架,提供了认证、授权、加密、会话管理,与Spring Security一样都是做一个权限的安全框架,但是与Srping Security相比,在于Shiro使用了比较简单易懂易于使用的授权方式。Shiro属于轻量级框架,相对于Security简单的多,也没有Security那么复杂。

2、Shiro基本功能如下图所示:

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户能否做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaEE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色、权限不必每次去查,这样可以提高效率;

Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持

Run As:允许一个用户假装成为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可;

3、Shiro的三大核心组件:

①Subject-------------------当前用户

②SecurityManage-------管理所有的Subject

③Reamls------------------权限信息的验证

    我们需要实现Reamls的Authentication与Authorization,其中Authentication用于验证用户身份,Authorization用于授权访问控制。

    Shiro核心是通过Filter来实现,就好像SpringMVC用DispatchServlet来做控制一样。既然使用Filter,那么我们也可以猜到,Shiro是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列的URL规则和访问权限。

    另外通过Shiro提供的会话管理可以获取Session信息,Shiro也提供缓存功能,使用CacheManager来管理。

4、集成Shiro步骤:

(a)pom.xml中添加Shiro依赖;

(b)注入Shiro Factory和SecurityManager

(c)身份认证

(d)权限控制

(a)pom.xml中添加Shiro依赖

要使用Shiro进行权限控制,那么很明显的就需要添加对Shiro的依赖包,在pom.xml中加入如下配置:

1 <!-- shiro spring. -->  
2        <dependency>  
3            <groupId>org.apache.shiro</groupId>  
4            <artifactId>shiro-spring</artifactId>  
5            <version>1.2.2</version>  
6        </dependency> 

 

(b)注入Shiro Factory和SecurityManager:

    在Spring中注入类都是使用配置文件的方式,在Spring Boot中是使用注解的方式,那么应该如何进行实现呢?

    Shiro的几个核心的类中,第一就是ShiroFilterFactory,第二就是SecurityManager,那么最简单的配置就是注入这两个类。

新建类com.shiro.ShiroConfiguration:

 

 1 package com.kfit.config.shiro;  
 2    
 3 import java.util.LinkedHashMap;  
 4 import java.util.Map;  
 5    
 6 import org.apache.shiro.mgt.SecurityManager;  
 7 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;  
 8 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;  
 9 import org.springframework.context.annotation.Bean;  
10 import org.springframework.context.annotation.Configuration;  
11    
12 /** 
13  * Shiro 配置 
14  * 
15 Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 
16 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。 
17  * 
18  * @author wangyj 
19  * @version v.0.1 
20  */  
21 @Configuration  
22 public class ShiroConfiguration {  
23         
24      
25     /** 
26      * ShiroFilterFactoryBean 处理拦截资源文件问题。 
27      * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在 
28      * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager 
29      * 
30         Filter Chain定义说明 
31        1、一个URL可以配置多个Filter,使用逗号分隔 
32        2、当设置多个过滤器时,全部验证通过,才视为通过 
33        3、部分过滤器可指定参数,如perms,roles 
34      * 
35      */  
36     @Bean  
37     public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){  
38        System.out.println("ShiroConfiguration.shirFilter()");  
39        ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();  
40         
41         // 必须设置 SecurityManager   
42        shiroFilterFactoryBean.setSecurityManager(securityManager);  
43         
44         
45         
46        //拦截器.  
47        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();  
48         
49        //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了  
50        filterChainDefinitionMap.put("/logout", "logout");  
51         
52        //<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;  
53         //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->  
54        filterChainDefinitionMap.put("/**", "authc");  
55         
56        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面  
57         shiroFilterFactoryBean.setLoginUrl("/login");  
58         // 登录成功后要跳转的链接  
59         shiroFilterFactoryBean.setSuccessUrl("/index");  
60         //未授权界面;  
61         shiroFilterFactoryBean.setUnauthorizedUrl("/403");  
62         
63        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);  
64        returnshiroFilterFactoryBean;  
65     }  
66      
67      
68     @Bean  
69     public SecurityManager securityManager(){  
70        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();  
71        return securityManager;  
72     }  
73 }  

 

 这里说一下:ShiroFilterFactory中已经由Shiro官方实现的过滤器;

Shiro内置的FilterChain

 
FilterName Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authz.UserFilter

 

 

 

 

 

 

 

 

 

 

 

 

 

anon:所有url都可以访问;

authc:需要认证才能进行访问;

user:配置记住我或认证通过可以访问;

这几个是我们会用到的,在这里说明一下,其他的请自行查询文档进行学习

这时候我们运行程序,访问"/index"页面我们会发现自动跳转到login页面,当然这个时候输入账号密码是无法访问的

(c)身份认证

    在认证、授权内部实现机制中都有提到,最终处理都交给Realm进行处理,因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息,可以说,Realm是专用于安全框架的DAO。

认证实现

    Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。

    该方法主要执行以下操作:

    1.检查提交的进行认证的令牌信息

    2.根据令牌信息从数据源(通常为数据库)中获取用户信息

    3.对用户信息进行匹配验证

    4.验证通过将返回一个封装了用户信息的AuthenticationInfo实例。

    5.验证失败则抛出AuthenticationException异常信息。


    而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。

    既然需要进行身份权限控制,那么少不了创建用户实体类,权限实体类。

    在权限管理系统中,有这么几个角色很重要,这个要是不清楚,很难理解编码原理。

    第一是用户表:在用户表中保存了用户的基本信息,账号、密码、姓名、性别等;

    第二是权限表(资源+控制权限):这个表中主要是保存了用户的URL地址,权限信息;

    第三就是角色表:在这个表中保存了系统中的角色;

    第四就是关联表:用户-角色管理表(用户在系统中都有什么角色,比如admin,vip等),角色-权限关联表(每个角色都有什么权限可以进行操作)。

 

 

Shiro的认证最终是交给了Realm进行执行了,所以我们需要自己重新实现一个Realm,此Realm继承AuthorizingRealm.

  1 package com.kfit.config.shiro;  
  2    
  3 import javax.annotation.Resource;  
  4    
  5 import org.apache.shiro.authc.AuthenticationException;  
  6 import org.apache.shiro.authc.AuthenticationInfo;  
  7 import org.apache.shiro.authc.AuthenticationToken;  
  8 import org.apache.shiro.authc.SimpleAuthenticationInfo;  
  9 import org.apache.shiro.authc.UsernamePasswordToken;  
 10 import org.apache.shiro.authz.AuthorizationInfo;  
 11 import org.apache.shiro.authz.SimpleAuthorizationInfo;  
 12 import org.apache.shiro.realm.AuthorizingRealm;  
 13 import org.apache.shiro.subject.PrincipalCollection;  
 14 import org.apache.shiro.util.ByteSource;  
 15    
 16 import com.kfit.core.bean.SysPermission;  
 17 import com.kfit.core.bean.SysRole;  
 18 import com.kfit.core.bean.UserInfo;  
 19 import com.kfit.core.service.UserInfoService;  
 20    
 21 /** 
 22  *  身份校验核心类; 
 23  * @author Angel(QQ:412887952) 
 24  * @version v.0.1 
 25  */  
 26 public class MyShiroRealm extends AuthorizingRealm{  
 27    
 28          
 29     @Resource  
 30     private UserInfoService userInfoService;  
 31      
 32     /** 
 33      * 认证信息.(身份验证) 
 34      * : 
 35      * Authentication 是用来验证用户身份 
 36      * @param token 
 37      * @return 
 38      * @throws AuthenticationException 
 39      */  
 40     @Override  
 41     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
 42        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");  
 43         
 44         
 45        //获取用户的输入的账号.  
 46        String username = (String)token.getPrincipal();  
 47        System.out.println(token.getCredentials());  
 48         
 49        //通过username从数据库中查找 User对象,如果找到,没找到.  
 50        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法  
 51        UserInfo userInfo = userInfoService.findByUsername(username);  
 52        System.out.println("----->>userInfo="+userInfo);  
 53        if(userInfo == null){  
 54            return null;  
 55        }  
 56         
 57        /* 
 58         * 获取权限信息:这里没有进行实现, 
 59         * 请自行根据UserInfo,Role,Permission进行实现; 
 60         * 获取之后可以在前端for循环显示所有链接; 
 61         */  
 62        //userInfo.setPermissions(userService.findPermissions(user));  
 63         
 64         
 65        //账号判断;  
 66         
 67        //加密方式;  
 68        //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现  
 69        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(  
 70               userInfo, //用户名  
 71               userInfo.getPassword(), //密码  
 72                 ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt  
 73                 getName()  //realm name  
 74         );  
 75         
 76         //明文: 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验  
 77 //      SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(  
 78 //           userInfo, //用户名  
 79 //           userInfo.getPassword(), //密码  
 80 //             getName()  //realm name  
 81 //      );  
 82         
 83        return authenticationInfo;  
 84     }  
 85      
 86      
 87      
 88     /** 
 89      * 此方法调用  hasRole,hasPermission的时候才会进行回调. 
 90      * 
 91      * 权限信息.(授权): 
 92      * 1、如果用户正常退出,缓存自动清空; 
 93      * 2、如果用户非正常退出,缓存自动清空; 
 94      * 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。 
 95      * (需要手动编程进行实现;放在service进行调用) 
 96      * 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例, 
 97      * 调用clearCached方法; 
 98      * :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。 
 99      * @param principals 
100      * @return 
101      */  
102     @Override  
103     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
104        /* 
105         * 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行, 
106         * 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理; 
107         * 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了, 
108         * 缓存过期之后会再次执行。 
109         */  
110        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");  
111         
112        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
113        UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();  
114         
115        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法  
116 //     UserInfo userInfo = userInfoService.findByUsername(username)  
117         
118         
119        //权限单个添加;  
120        // 或者按下面这样添加  
121         //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色     
122 //     authorizationInfo.addRole("admin");   
123         //添加权限   
124 //     authorizationInfo.addStringPermission("userInfo:query");  
125         
126         
127         
128        ///在认证成功之后返回.  
129        //设置角色信息.  
130        //支持 Set集合,  
131        //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要  
132 //        List<Role> roleList=user.getRoleList();  
133 //        for (Role role : roleList) {  
134 //            info.addStringPermissions(role.getPermissionsName());  
135 //        }  
136        for(SysRole role:userInfo.getRoleList()){  
137            authorizationInfo.addRole(role.getRole());  
138            for(SysPermission p:role.getPermissions()){  
139               authorizationInfo.addStringPermission(p.getPermission());  
140            }  
141        }  
142         
143        //设置权限信息.  
144 //     authorizationInfo.setStringPermissions(getStringPermissions(userInfo.getRoleList()));  
145         
146        return authorizationInfo;  
147     }  
148      
149      
150     /** 
151      * 将权限对象中的权限code取出. 
152      * @param permissions 
153      * @return 
154      */  
155 //  public Set<String> getStringPermissions(Set<SysPermission> permissions){  
156 //     Set<String> stringPermissions = new HashSet<String>();  
157 //     if(permissions != null){  
158 //         for(SysPermission p : permissions) {  
159 //            stringPermissions.add(p.getPermission());  
160 //          }  
161 //     }  
162 //       return stringPermissions;  
163 //  }  
164      
165 }  

 

继承AuthorizingRealm主要需要实现两个方法:

dogGetAuthenticationInfo();

doGetAuthorizationInfo();

其中doGetAuthenticationInfo主要是用来进行身份认证的,也就是说验证用户输入的账号密码是否正确。

1 SimpleAuthenticationInfoauthenticationInfo =  
2  new SimpleAuthenticationInfo(  
3                 userInfo, //用户名  
4                 userInfo.getPassword(), //密码  
5                 ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt  
6                 getName()  //realm name  
7         );  

 

交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现

如果你是进行明文编码,可以使用如下方式:

1 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(  
2             userInfo, //用户名  
3             userInfo.getPassword(), //密码  
4             getName()  //realm name  
5      ); 

 

将自定义Realm注入到SecurityManager中。

 1 /** 
 2      * 身份认证realm; 
 3      * (这个需要自己写,账号密码校验;权限等) 
 4      * @return 
 5      */  
 6     @Bean  
 7     public MyShiroRealm myShiroRealm(){  
 8        MyShiroRealm myShiroRealm = new MyShiroRealm();  
 9        return myShiroRealm;  
10     }  

 

将myShiroRealm注入到securityManager中:

1 @Bean  
2     public SecurityManager securityManager(){  
3        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();  
4        //设置realm.  
5        securityManager.setRealm(myShiroRealm());  
6        return securityManager;  
7     } 

 

 

(d)权限控制

创建UserInfoController

 1 package com.kfit.core.controller;  
 2    
 3 import org.apache.shiro.authz.annotation.RequiresPermissions;  
 4 import org.springframework.stereotype.Controller;  
 5 import org.springframework.web.bind.annotation.RequestMapping;  
 6    
 7 @Controller  
 8 @RequestMapping("/userInfo")  
 9 public class UserInfoController {  
10      
11     /** 
12      * 用户查询. 
13      * @return 
14      */  
15     @RequestMapping("/userList")  
16     public String userInfo(){  
17        return "userInfo";  
18     }  
19      
20     /** 
21      * 用户添加; 
22      * @return 
23      */  
24     @RequestMapping("/userAdd")  
25     public String userInfoAdd(){  
26        return "userInfoAdd";  
27     }  
28      
29 }

 

 

第一:开启Shiro aop注解支持:

 1 /** 
 2      *  开启shiro aop注解支持. 
 3      *  使用代理方式;所以需要开启代码支持; 
 4      * @param securityManager 
 5      * @return 
 6      */  
 7     @Bean  
 8     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){  
 9        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();  
10        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);  
11        return authorizationAttributeSourceAdvisor;  
12     }  

 

第二:在controller方法中加入相应的注解:

1 /** 
2      * 用户添加; 
3      * @return 
4      */  
5     @RequestMapping("/userAdd")  
6     @RequiresPermissions("userInfo:add")//权限管理;  
7     public String userInfoAdd(){  
8        return "userInfoAdd";  
9     }  

 

 

未完,待完善......

 

posted on 2018-06-02 15:05  程序媛Kasey  阅读(117)  评论(0)    收藏  举报