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 }
未完,待完善......
浙公网安备 33010602011771号