四、shiro授权
4.1.shiro授权的三种方式:
1.编程式:通过写if/else授权代码块完成:
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“admin”)) { //有权限 } else { //无权限 }
2.注解式:通过在执行的Java方法上放置相应的注解完成(没有权限将抛出相应的异常):
@RequiresRoles("admin") public void hello() { //有权限 }
3.JSP/GSP标签:在JSP/GSP页面通过相应的标签完成:
<shiro:hasRole name="admin"> <!— 有权限 —> </shiro:hasRole>
4.2.实现
1.在src包下创建一个shiro-permission.ini配置文件,用于模拟数据库中的权限数据,内容如下:
#基于用户的访问控制 [users] #用户zhangsan的密码是123,此用户具有role1和role2两个角色;用户wang具有role2一个角色 zhangsan=123,role1,role2 wang=123,role2 #基于权限的访问控制 [roles] #角色role1对资源user拥有create、update权限 role1=user:create,user:update #角色role2对资源user拥有create、delete权限 role2=user:create,user:delete #角色role3对资源user拥有create权限 role3=user:create
对配置文件中的解释如下:
权限标识符号规则:资源:操作:实例(中间使用半角:分隔)。如下:
user:create:01,表示对用户资源的01实例进行create操作。
user:create,表示对用户资源进行create操作,相当于user:create:,对所有用户资源实例进行create操作。
user::01,表示对用户资源实例01进行所有操作。
然后写一个测试类Authorization.java,代码如下:
public class AuthorizationTest { //角色授权测试和资源授权测试 @Test public void testAuthorization() { //第一步,创建SecurityManager工厂 Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro-permission.ini"); //第二步:创建SecurityManager SecurityManager securityManager=factory.getInstance(); //第三步,将SecurityManager设置到系统运行环境,和spring整合后会将SecurityManager配置到spring容器中,一般单例管理 SecurityUtils.setSecurityManager(securityManager); //第四步,创建subject Subject subject=SecurityUtils.getSubject(); //创建token令牌,这里的用户名和密码以后由用户输入 UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","123"); try { //执行认证,将用户输入的信息同数据库(即.ini配置文件)中信息进行对比 subject.login(token); }catch (AuthenticationException e) { e.printStackTrace(); } System.out.println("认证状态:"+subject.isAuthenticated()); //认证通过后才能执行授权 //第一种授权方式是基于角色的授权,hasRole传入角色的标识 boolean ishasRole=subject.hasRole("role1");//该用户是否有role1这个角色 System.out.println("单个角色判断"+ishasRole); //hasAllRoles是否拥有多个角色 boolean hasAllRoles=subject.hasAllRoles(Arrays.asList("role1","role2")); System.out.println("多个角色判断"+hasAllRoles);//角色的就讲到这里了,后面我们都是通过资源进行权限讲解 //使用check方法进行授权,如果授权不通过会抛出异常 subject.checkRole("role3"); //第二种授权方式是基于资源的授权,isPermitted传入权限标识符 boolean isPermitted=subject.isPermitted("user:create");//该用户是否有对user资源进行创建的权限 System.out.println("单个权限判断"+isPermitted); //多个权限判断 boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:update"); System.out.println("多个权限判断:"+isPermittedAll); } }
从测试代码中我们可以知道,只有当用户信息得到认证后才能对用户进行授权操作。
上面只是一个简单的入门程序,从代码中直接读取数据库(即配置文件)中的权限数据,然后将其与代码中的给定权限进行判断看该用户是否具有该权限,而实际操作中我们是通过Realm进行读取数据库中的数据的。所以接下来讲自定义Realm进行授权。
4.3.自定义Realm进行授权
1.需求
上边的程序通过shiro-permission.ini对权限信息进行静态配置,实际开发中从数据库中获取权限数据。就需要自定义realm,由realm从数据库查询权限数据。
realm根据用户身份查询权限数据,将权限数据返回给authorizer(授权器)。
2.自定义Realm
在上篇文章自定义的realm中即CustomRealm.java,修改doGetAuthorizationInfo()方法,代码如下:
//上边是进行认证的方法,在上篇文章中已完成 //用于授权,当然首先要实现认证 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //从principals获取主身份信息 //将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的goGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo) String userCode= (String) principals.getPrimaryPrincipal(); //根据身份信息获取权限信息, //模拟从数据库中获取到的动态权限数据 List<String> permissions=new ArrayList<>(); permissions.add("user:create");//模拟user的创建权限 permissions.add("items:add");//模拟商品的添加权限 //查到权限数据,返回授权信息(包括上边的permissions) SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo(); //将上边查询到授权信息填充到simpleAuthorizationInfo对象中 simpleAuthorizationInfo.addStringPermissions(permissions); return simpleAuthorizationInfo; }
然后要对该自定义Realm进行配置,即将自定义Realm放入SecurityManager中,上篇文章中的shiro-realm.ini配置文件中我们已实现。
然后便可以进行测试了,测试代码如下:
//自定义realm进行资源授权测试 @Test public void testAuthorizationCustomRealm() { //第一步,创建SecurityManager工厂 Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:config/shiro-realm.ini"); //第二步:创建SecurityManager SecurityManager securityManager=factory.getInstance(); //第三步,将SecurityManager设置到系统运行环境,和spring整合后会将SecurityManager配置到spring容器中,一般单例管理 SecurityUtils.setSecurityManager(securityManager); //第四步,创建subject Subject subject=SecurityUtils.getSubject(); //创建token令牌,这里的用户名和密码以后由用户输入 UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","111111"); try { //执行认证,将用户输入的信息同数据库(即.ini配置文件)中信息进行对比 subject.login(token); }catch (AuthenticationException e) { e.printStackTrace(); } System.out.println("认证状态:"+subject.isAuthenticated()); //基于资源的授权,调用isPermitted方法会调用CustomRealm从数据库查询正确的权限数据 // isPermitted传入权限标识符,判断user:create:1是否在CustomRealm查询到的权限数据之内 boolean isPermitted=subject.isPermitted("user:create:1");//该用户是否有对user的1资源进行创建的权限 System.out.println("单个权限判断"+isPermitted); //多个权限判断 boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:create"); System.out.println("多个权限判断:"+isPermittedAll); }
授权流程讲解:
1.对subject进行授权,调用方法isPermitted(“permission串”)。
2.SecurityManager执行授权,通过ModularRealmAuthorizer执行授权。
3.ModularRealmAuthorizer执行realm(自定义的CustomRealm)从数据库查询权限数据。即调用realm的授权方法:doGetAuthorizationInfo()。
4.realm从数据库查询权限数据,返回ModularRealmAuthorizer。
5.ModularRealmAuthorizer调用PermissionResolver进行权限串比对。
6.如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。
五、与SpringBoot集成案例
SpringBoot整合Shiro(Java安全框架)案例(含源码)
六、shiro各个拦截器对比及使用配置
6.1.各种拦截器的特点
6.2.INI文件配置拦截器
1.配置之前,我们还需要讲一下 拦截器的通配符的写法,如下所示:
?:匹配一个字符
*:匹配零个或多个字符
**:匹配零个或多个路径
2.然后,我们看一下[urls]模块的示例.这里我只用到了一些常用的拦截器:
[urls] #不需要登录 /login=anon /static/**=anon #需要登录 /home=authc #需要角色 /deleteUser = roles["superAdmin"] #需要用户权限 /addUser = perms["user:create"]
3.但是,我们发现一个问题,假设用户没有该权限和没有该角色,或者没有登录的时候,还有登出的时候,我们都需要给他们配置对应的重定向路径.这时候,我们就不能在[urls]模块中使用了,我们需要在[main]模块中进行各个情况路径的重定向设置了.示例代码如下所示:
[main] #用户登录的地址 authc.loginUrl = /login #用户没有对应角色的跳转重指向 roles.unauthorizedUrl = /login #用户没有对应权限的跳转重指向 perms.unauthorizedUrl = /login #用户登出的跳转重指向 logout.redirectUrl = /login
注意:INI文件中 [urls] 模块拦截顺序是从上往下依次执行.
6.3.代码形式配置拦截器
使用INI文件配置拦截器是较为简洁的配置形式,其实质就是通过Bean注入的形式配置拦截器 接下来我们看一下我们如何通过代码形式配置拦截器.
配置了核心安全事务管理器 和自定义的权限登录器.代码如下所示:
@Configuration public class ShiroConfiguration { //配置核心安全事务管理器 @Bean(name="securityManager") public DefaultWebSecurityManager securityManager(@Qualifier("myShiroRealm") MyShiroRealm myShiroRealm) { DefaultWebSecurityManager manager=new DefaultWebSecurityManager(); manager.setRealm(myShiroRealm); return manager; } //配置自定义的权限登录器 @Bean(name="myShiroRealm") public MyRealm authRealm() { MyRealm myShiroRealm=new MyRealm(); return myShiroRealm; } }
我们继续配置拦截器模块的代码.整体代码如下所示:
@Bean(name="shiroFilter") public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) { ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean(); bean.setSecurityManager(manager); //配置登录的url和登录成功的url以及验证失败的url bean.setLoginUrl("/login"); bean.setSuccessUrl("/home"); bean.setUnauthorizedUrl("/login"); //配置访问权限 LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>(); filterChainDefinitionMap.put("/loginUser", "anon"); filterChainDefinitionMap.put("/static/*", "anon"); filterChainDefinitionMap.put("/logout*","anon"); bean.setFilterChainDefinitionMap(filterChainDefinitionMap); return bean; }
比较过来,依然发现,使用INI文件形式配置拦截器要比代码形式更加简洁.
6.4.简单使用
现在我们来拿/html/login = anon和/html/home = authc以及设置重定向的authc.loginUrl = /html/login来简单说明一下,具体应该如何使用.
我们想让SpringBoot项目支持Html格式的访问,那么我们需要在pom.xml文件中加入如下的Maven依赖.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
同时,我们要在application.properties文件中配置如下信息.设置HTML的存放路径
spring.thymeleaf.prefix=classpath:/templates/ spring.mvc.view.suffix=.html
目录结构如下:
然后,我们把ShiroConfiguration配置文件进行如下的配置:
@Configuration public class ShiroConfiguration { @Bean(name="shiroFilter") public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) { ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean(); bean.setSecurityManager(manager); //配置登录的url和登录成功的url以及验证失败的url bean.setLoginUrl("/index"); bean.setSuccessUrl("/home"); bean.setUnauthorizedUrl("/index"); //配置访问权限 LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>(); filterChainDefinitionMap.put("/index", "anon"); filterChainDefinitionMap.put("/home", "authc"); filterChainDefinitionMap.put("/**","anon"); bean.setFilterChainDefinitionMap(filterChainDefinitionMap); return bean; } //配置核心安全事务管理器 @Bean(name="securityManager") public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager manager=new DefaultWebSecurityManager(); return manager; } }
我们在resources目录下创建两个html页面,一个login.html,一个home.html,其中代码如下所示.
login:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户登录</title> </head> <body> <div style="text-align: center;margin-top: 100px; font-size: 20px;">用户登录界面</div> </body> </html>
home:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>主页面</title> </head> <body> <div style="text-align: center;margin-top: 100px; font-size: 20px;">Home界面</div> </body> </html>
然后我们需要编写两个接口来访问login.html和home.html.这里我创建了一个名为HtmlController的控制器.整体代码较为简单,这里就直接黏贴出所有代码了.如下所示:
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import java.util.Map; @Controller public class HtmlController { @RequestMapping(value = "/index",method = RequestMethod.GET) public String userLoginHtmlAction (){ return "index"; } @RequestMapping(value = "/home",method = RequestMethod.GET) public String userHomeHtmlAction (){ return "home"; } }
然后我们就会猜想有如下情况出现,假设我们可以直接通过浏览器访问/html/login,那么我们会直接访问到login.html ,但是当我们访问/html/home的时候,由于我们没有进行用户登录认证(根本就没有做登录接口.😁),所以我们不可能通过认证,通过登录重指向,浏览器仍然会展示login.html 页面,下面我们就启动项目来验证一下.如下所示,拦截成功.