Shiro授权管理
一、授权
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
二、Shiro授权概念(RBAC)
1,Subject
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。主体,即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。
2,Resource
在应用中用户可以访问的任何东西,比如访问JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
3,Permission
安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)打印文档等等。
权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro 不会去做这件事情,而是由实现人员提供。Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)。
4,Role
角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:开发工程师、项目经理、技术总监、CTO等都是角色,不同的角色拥有一组不同的权限。
隐式角色:即直接通过角色来验证用户有没有操作权限,如在应用中CTO、技术总监、开发工程师可以使用打印机,假设某天不允许开发工程师使用打印机,此时需要从应用中删除相应代码;再如在应用中CTO、技术总监可以查看用户、查看权限;突然有一天不允许技术总监查看用户、查看权限了,需要在相关代码中把技术总监角色从判断逻辑中删除掉;即粒度是以角色为单位进行访问控制的,粒度较粗;如果进行修改可能造成多处代码修改。
显示角色:在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合;这样假设哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除即可;无须修改多处代码;即粒度是以资源/实例为单位的;粒度较细。
三、授权流程
1、首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer。
2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例。
3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限。
4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer 进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。
四、SpringBoot中Shiro授权Demo
Config.java
1 package io.guangsoft.erp.config; 2
3 import io.guangsoft.erp.realm.Realm; 4 import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 5 import org.apache.shiro.codec.Base64; 6 import org.apache.shiro.mgt.RememberMeManager; 7 import org.apache.shiro.mgt.SecurityManager; 8 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 9 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 10 import org.apache.shiro.web.mgt.CookieRememberMeManager; 11 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 12 import org.apache.shiro.web.servlet.SimpleCookie; 13 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 14 import org.springframework.context.annotation.Bean; 15 import org.springframework.context.annotation.Configuration; 16
17 import java.util.LinkedHashMap; 18 import java.util.Map; 19
20 @Configuration 21 public class Config { 22
23 @Bean 24 public Realm realmManager() { 25 // 加密相关
26 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); 27 // 散列算法
28 hashedCredentialsMatcher.setHashAlgorithmName("md5"); 29 // 散列次数
30 hashedCredentialsMatcher.setHashIterations(2); 31 Realm realm = new Realm(); 32 realm.setCredentialsMatcher(hashedCredentialsMatcher); 33 return realm; 34 } 35
36 @Bean 37 public RememberMeManager rememberMeManager() { 38 CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); 39 //注入自定义cookie(主要是设置寿命, 默认的一年太长)
40 SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); 41 simpleCookie.setHttpOnly(true); 42 //设置RememberMe的cookie有效期为7天
43 simpleCookie.setMaxAge(604800); 44 rememberMeManager.setCookie(simpleCookie); 45 //手动设置对称加密秘钥,防止重启系统后系统生成新的随机秘钥,防止导致客户端cookie无效
46 rememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j3Y+R1aSn5BOlAA==")); 47 return rememberMeManager; 48 } 49
50 @Bean 51 public SecurityManager securityManager() { 52 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 53 securityManager.setRealm(realmManager()); 54 securityManager.setRememberMeManager(rememberMeManager()); 55 return securityManager; 56 } 57
58 //两个Bean开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
59 @Bean 60 public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ 61 DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); 62 advisorAutoProxyCreator.setProxyTargetClass(true); 63 return advisorAutoProxyCreator; 64 } 65
66 @Bean 67 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { 68 AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 69 authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); 70 return authorizationAttributeSourceAdvisor; 71 } 72
73 @Bean 74 public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { 75 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 76 shiroFilterFactoryBean.setSecurityManager(securityManager); 77 //当用户未登录则跳转到登录路径
78 shiroFilterFactoryBean.setLoginUrl("/login"); 79 //登录成功后要跳转的链接,表单登录方式有效
80 shiroFilterFactoryBean.setSuccessUrl("/index"); 81 //未授权界面,指定没有权限操作时跳转页面
82 shiroFilterFactoryBean.setUnauthorizedUrl("/warning"); 83 //配置不会被过滤的链接顺序判断,过虑器链定义,从上向下顺序执行,一般将/**放在最下边
84 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); 85 //对静态资源设置匿名访问,anon:所有url都可以匿名访问
86 filterChainDefinitionMap.put("/assets/**", "anon"); 87 //放开登录接口,允许进行登录操作
88 filterChainDefinitionMap.put("/shiro/login", "anon"); 89 //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
90 filterChainDefinitionMap.put("/shiro/logout", "logout"); 91 //authc:所有url都必须认证通过才可以访问
92 filterChainDefinitionMap.put("/**", "authc"); 93 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 94 return shiroFilterFactoryBean; 95 } 96
97 }
Realm.java
1 package io.guangsoft.erp.realm; 2
3 import org.apache.shiro.SecurityUtils; 4 import org.apache.shiro.authc.AuthenticationException; 5 import org.apache.shiro.authc.AuthenticationInfo; 6 import org.apache.shiro.authc.AuthenticationToken; 7 import org.apache.shiro.authc.SimpleAuthenticationInfo; 8 import org.apache.shiro.authz.AuthorizationInfo; 9 import org.apache.shiro.authz.SimpleAuthorizationInfo; 10 import org.apache.shiro.realm.AuthorizingRealm; 11 import org.apache.shiro.subject.PrincipalCollection; 12 import org.apache.shiro.subject.Subject; 13 import org.apache.shiro.util.ByteSource; 14
15 import java.util.ArrayList; 16 import java.util.List; 17
18 public class Realm extends AuthorizingRealm { 19
20 //认证
21 @Override 22 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 23 String userName = (String) token.getPrincipal(); 24 System.out.println("通过username在数据库中获取用户密码并设置加密盐."); 25 String password = "4d521acb9b8b3b4fa082ab16b3bd363a"; 26 String salt = "guanghe"; 27 return new SimpleAuthenticationInfo(userName, password, ByteSource.Util.bytes(salt), this.getName()); 28 } 29
30 //授权
31 @Override 32 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 33 Subject subject = SecurityUtils.getSubject(); 34 String username = (String) subject.getPrincipal(); 35 System.out.println("通过username在数据库中获取用户角色与权限."); 36 // 角色
37 List<String> roles = new ArrayList<>(); 38 roles.add("admin"); 39 roles.add("user"); 40 // 权限
41 List<String> permissions = new ArrayList<>(); 42 permissions.add("admin:select"); 43 permissions.add("admin:update"); 44 permissions.add("user:select"); 45 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 46 simpleAuthorizationInfo.addStringPermissions(permissions); 47 simpleAuthorizationInfo.addRoles(roles); 48 return simpleAuthorizationInfo; 49 } 50
51 }
mainController.java
1 package io.guangsoft.erp.controller; 2
3 import org.apache.shiro.authz.annotation.Logical; 4 import org.apache.shiro.authz.annotation.RequiresPermissions; 5 import org.apache.shiro.authz.annotation.RequiresRoles; 6 import org.springframework.stereotype.Controller; 7 import org.springframework.web.bind.annotation.RequestMapping; 8
9 @Controller 10 public class mainController { 11
12 @RequestMapping("index") 13 public String index() { 14 return "index.html"; 15 } 16
17 @RequestMapping("login") 18 public String login() { 19 return "login.html"; 20 } 21
22 @RequiresRoles(value = {"admin","user"},logical = Logical.AND) 23 @RequiresPermissions(value={"user:select","admin:select"},logical = Logical.OR) 24 @RequestMapping("select") 25 public String select() { 26 return "manage/select.html"; 27 } 28
29 @RequiresRoles("operator") 30 @RequestMapping("update") 31 public String update() { 32 return "manage/update.html"; 33 } 34
35 @RequiresPermissions("admin:delete") 36 @RequestMapping("delete") 37 public String delete() { 38 return "manage/delete.html"; 39 } 40
41 }
