起初刚接触到授权这个词我以为是将某个权限授予某个用户,后来经过网络调研发现,授权实际上是检验某个用户是否有访问某个资源的权限。
比如,普通用户想观看VIP视频会被网站阻止,因为该用户没有访问该资源的权限。
关于授权需要了解的概念:
主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)
主体:在shiro中代表用户,不一定是某个人,也许是爬虫。
资源:用户可以访问的东西。
权限:代表的是用户有没有某些操作(crud之类的)的权限。
角色:用户的角色,权限的集合。
粗粒度:以角色来授权。
细粒度:以权限来授权。
如果采用粗粒度的方式,当某个角色不再具备某个权限时,需要改动代码。
授权的三种方式:
代码授权、JSP标签授权(这个就不学了)、注解授权(这个着重看一下)。
代码授权:
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“角色”)) { //有权限 } else { //无权限 }
注解授权:
@RequiresRoles("角色") public void check() { //有权限 }
教程里关于这部分的代码貌似没给太多...于是,我自己发挥了一下...
首先,是代码授权,自定义realm,假装是从数据源中获取数据...
public class SelfRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //授权会调用这个方法,从数据源中获取权限列表 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRole("admin"); //假装从数据源中获取的用户权限为admin return simpleAuthorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //认证方法,和上一篇笔记的一样 String username = (String) authenticationToken.getPrincipal(); String password = new String((char[])authenticationToken.getCredentials()); if(!"joker".equals(username)) { throw new UnknownAccountException(); } if(!"shiro".equals(password)) { throw new IncorrectCredentialsException(); } return new SimpleAuthenticationInfo(username, password, getName()); } }
然后,修改了一下测试方法,假装用户登录后访问某个资源,然后进行判断(采用的方式是粗粒度)
public class Boot { @Test public void test() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(new SelfRealm()); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("joker", "shiro"); try { subject.login(token); } catch (Exception e) {} System.out.println("登录状态:" + subject.isAuthenticated()); System.out.println("是否为管理员:" + subject.hasRole("admin")); //此处假装访问某资源,进行校验 } }
经测试发现,如果用户不登录,则授权结果一定为false,如果用户登录后登出,那么结果也是false。
授权流程:
主体(Subject)调用isPermitted(是否拥有某权限,细粒度)/hasRole(是否拥有某角色,粗粒度),会调用到(核心管理器)SecurityManager,然后会调用到授权器(Authorizer)。
后面还有一些流程,感觉记不太住,此处暂不做记录,等以后有能力阅读shiro源码再详细学习。
教程中介绍了两种授权的流程:
授权器授权、realm授权(没太搞懂怎么弄...)
还有一些高端操作,自定义授权器什么的...估计以后有能力读懂源码,就可以领会这些进阶操作,现阶段暂时略过。
目前没整合其他框架搭建一个小的项目,没想到用什么简单的方式测试注解方式授权,只好拿着上次根据网上教程搭建的练手shiro整合springboot项目改造了一下。
@GetMapping("/role") @RequiresRoles("admin") public String role() { return "role success"; }
然后惊讶地发现这个注解压根不生效,但是,如果采用代码授权的话还是可以正确执行。
于是,再一次面向网络编程,发现搜到一堆xml之类的配置,嗯... ...但是我需要的是配置类的方式进行配置,终于找到一篇文章说需要加入一个Bean。
@Bean public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ return new DefaultAdvisorAutoProxyCreator(); }
加入之后发现还是不生效... ...
无奈之下,看了一波xml配置,发现其中用到了AuthorizationAttributeSourceAdvisor,并且将securityManager交给了这个类的实例对象。
于是,又加入了一段代码:
@Bean public static AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
这一次注解终于生效了,之前看到一篇文章说这个注解必须写在service层才行,写在controller层不管用,经实践表明写在controller层是管用的,并且还有一个意外发现,当controller和service两层都有这个注解时,
如果controller层通过授权,则即便service层也会通过,即便该用户并不具备service层注解要求的权限也一样会通过。
以下是我根据网上的文章配置的简易版shiro配置类(记录一下,省得下次再去查):
@Configuration public class ShiroConfig { @Bean public BlogRealm newBlogRealm() { return new BlogRealm(); } @Bean public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ return new DefaultAdvisorAutoProxyCreator(); } @Bean public static AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } @Bean("securityManager") public SecurityManager newSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(newBlogRealm()); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> map = new HashMap<>(); map.put("/blog/login", "anon");//登录这一行和下一行应该配置一个即可(个人猜测,暂未实践) shiroFilterFactoryBean.setLoginUrl("/blog/login"); map.put("/blog/role", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } }