shiro

目录

 

简介

三大核心组件

身份验证

授权

安全加密:

会话管理

会话

会话管理器

Session超时管理

会话监听

会话存储

Shiro注解实现原理

Shiro的Filter实现原理


简介

Apache Shiro是一个强大且易用的Java安全框架,主要功能有身份验证授权安全加密会话管理。相比较Spring Security,shiro有小巧、简单、易上手等的优点。shiro权限的操作粒度能控制在路径及按钮上,数据粒度通过sql实现。Shrio简单够用。至于OAuth,OpenID 站点间统一登录功能,现如今单点登录很多已经通过cookies实现。因此Shiro完全能够胜任平时项目的安全认证控制。

三大核心组件

  1. Subject表示与系统交互的主体,通常情况下我们理解是用户。他包含了用户安全认证的相关授权信息。

  2. SecurityManager则管理所有用户的安全操作。它是Shiro框架的核心,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。主要实现有:CachingSecurityManager、RealmSecurityManager、AuthenticatingSecurityManager、AuthorizingSecurityManager、SessionsSecurityManager、DefaultSecurityManager,采用装饰模式,最终的DefaultSecurityManager覆盖了其余的SecurityMananger的功能。

  3. Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会通过Realm中查询相关的用户及其权限信息。常见的Realm实现有:JDBCRealm、IniRealm、PropertiesRealm,在实际开发中通常用户的认证信息都存放在数据库中,我们可以通过JDBCRealm查询数据库认证数据,或者通过继承AuthorizingRealm自定义Realm来获取数据库认证数据(JDBCRealm也继承了AuthorizingRealm)

身份验证

先看一段示例代码:

    public String testShiroLogin(HttpServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        UsernamePasswordToken upt = new UsernamePasswordToken(username, password);
        subject.login(upt);
        return "success";
    }

上面这一段代码是一个登陆操作的简单示例代码,这其中主要用到了Shiro的提供的两个对象Subject和UsernamePasswordToken。当Subject调用login()方法登录时,通过源代码断点调试跟踪发现,实际是调用SecurityManager的 login 方法,而在SecurityManager的login方法内部最终的认证其实是Realm接口的getAuthenticationInfo方法,AuthenticatingRealm实现了Realm接口的getAuthenticationInfo方法,在其内部具体先通过getCachedAuthenticationInfo方法查询缓存,如果缓存中没有就调用doGetAuthenticationInfo方法,这里实际是通过模板方法模式,doGetAuthenticationInfo方法是AuthenticatingRealm内部定义的一个抽象方法,实现的该方法的具体实现类主要有JdbcRealm、SimpleAccountRealm(子类IniRealm和PropertiesRealm)此外还可以自定义Realm实现该抽象方法来实现自定义获取身份信息的具体过程。

主要流程如下

shiro_第1张图片

授权

先看一段代码:

    public String testShiro(HttpServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        String username = subject.getPrincipal().toString();
        subject.isPermitted("admin:test");
        return username + "请求成功";
    }

上面这一段代码是一个权限控制的简单示例代码,当Subject调用isPermitted()方法登录时,通过源代码断点调试跟踪发现,实际是调用SecurityManager的 isPermitted方法,而在SecurityManager的isPermitted方法内部最终的是通过AuthorizingRealm的getAuthorizationInfo方法获取用户的授权,在getAuthorizationInfo方法内部先查询缓存,如果缓存中没有授权信息就调用doGetAuthorizationInfo方法,这里实际是通过模板方法模式,doGetAuthorizationInfo方法是AuthorizingRealm内部定义的一个抽象方法,实现的该方法的具体实现类主要有JdbcRealm、SimpleAccountRealm(子类IniRealm和PropertiesRealm)此外还可以自定义Realm实现该抽象方法来实现自定义获取授权信息的具体过程。

主要流程如下:

shiro_第2张图片

安全加密:

Shiro的安全加密主要使用在几个地方:

  1. 散列算法:如Md5Hash、Sha256Hash、Sha512Hash、DefaultHashService等相关类都可以用来对具体数据进行散列值计算。
  2. 加密/解密:Shiro 还提供对称式加密 / 解密算法的支持,如 AES、Blowfish 等。可以使用AesCipherService、BlowfishCipherService服务类进行相关数据的加密和解密操作
  3. PasswordService/CredentialsMatcher:Shiro 提供了 PasswordService 及 CredentialsMatcher 用于提供加密密码及验证密码服务。
    public interface PasswordService {
        //加密
        String encryptPassword(Object var1) throws IllegalArgumentException;
        //匹配
        boolean passwordsMatch(Object var1, String var2);
    }
    常用的PasswordService 实现是 DefaultPasswordService
    public interface CredentialsMatcher {
        //匹配用户输入的token的凭证(未加密)与系统提供的凭证(已加密)
        boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);
    }

    常用的CredentialsMatcher实现是PasswordMatcherHashedCredentialsMatcher
    (1)PasswordMatcher
    DefaultPasswordService 配合 PasswordMatcher 实现简单的密码加密与验证服务,PasswordMatcher中有一个PasswordService类型的成员变量,可以通过setPasswordService()方法指定PasswordService。下面是自定义的Realm中指定CredentialsMatcher的代码:

        //MyCsutomRealm是继承了AuthorizingRealm类的自定义Realm
        public MyCustomRealm() {
            PasswordMatcher passwordMatcher = new PasswordMatcher();
            passwordMatcher.setPasswordService(defaultPasswordService);
            this.setCredentialsMatcher(passwordMatcher);
        }

    注:在实际项目使用中为了方便,利用Spring的依赖注入,我们可以先将DefaultPasswordService通过注解或者xml的方式配置成一个bean对象,在使用时直接注入一个 PasswordService 来加密密码,实际使用时需要在 Service 层使用 PasswordService 加密密码并存到数据库。可以采用@Autowired注入,或者在配置文件中利用constructor-arg标签通过构造函数注入、或者在配置文件中利用property标签通过set方法注入。
    (2)HashedCredentialsMatcher
    Shiro 提供了 CredentialsMatcher 的散列实现 HashedCredentialsMatcher,和之前的 PasswordMatcher 不同的是,它只用于密码验证,且可以提供自己的盐,而不是随机生成盐,且生成密码散列值的算法需要自己写,因为能提供自己的盐。
    因为HashedCredentialsMatcher只能用于密码验证。因此在实际的项目使用中常用PasswordMatcher作为Realm中的CredentialsMatcher。

会话管理

Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如 web 容器 tomcat),不管 JavaSE 还是 JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储 / 持久化、容器无关的集群、失效 / 过期支持、对 Web 的透明支持、SSO 单点登录的支持等特性。即直接使用 Shiro 的会话管理可以直接替换如 Web 容器的会话管理。

会话

Shiro中提供了Session接口,并且有多个实现,如DelegatingSession、HttpServletSession、SimpleSession等多种实现,我们也可以实现Session接口,自定义Session。

会话管理器

会话管理器管理着应用中所有 Subject 的会话的创建、维护、删除、失效、验证等工作。是 Shiro 的核心组件,顶层组件 SecurityManager 直接继承了 SessionManager。SessionsSecurityManager 是SecurityManager的一种实现,它内部定义了SessionManager成员变量。最常使用的DefaultSecurityManager 、 DefaultWebSecurityManager 两种SecurityManager都继承了 SessionsSecurityManager。

Shiro 提供了三个默认实现:

DefaultSessionManager:DefaultSecurityManager 使用的默认实现,用于 JavaSE 环境;
DefaultWebSessionManager:继承DefaultSessionManager用于 Web 环境的实现,可以替代 ServletContainerSessionManager,自己维护着会话,直接废弃了 Servlet 容器的会话管理。
ServletContainerSessionManager:DefaultWebSecurityManager 使用的默认实现,用于 Web 环境,其直接使用 Servlet 容器的会话;

Session超时管理

根据Shiro提供的默认SessionManager的实现:

DefaultSessionManager和DefaultWebSessionManager都实现了AbstractSessionManager抽象类,AbstractSessionManager中定义的成员变量globalSessionTimeout表示Session的超时时间,我们在实际使用中,可以通过配置文件或者set方法来设置具体的Session超时时间。

在ServletContainerSessionManager中的Session是HttpServletSession,该类实际上是Shiro对传统的HttpSession进行包装,本质上仍然是HttpSession。因此我们在实际使用中除了通过set方法来设置超时时间外,还可以通过web.xml配置传统session超时时间的方式来设置。

会话监听

Shiro提供了SessionListener接口,我们通过实现该接口可以自定义会话监听功能。

会话存储

Shiro中提供了SessionDao接口,并且定义了AbstractSessionDao抽象类,我们可以通过继承AbstractSessionDao接口,自定义Session的存储,再结合缓存工具,如:Redis,就可以实现将Session存入缓存中,如果需要用到分布式缓存,也可以利用Redis搭建分布式集群来实现。

Shiro注解实现原理

 Shiro注解,如@RequiresPermissions、@RequiresRoles、@RequiresUser等注解实现认证和权限拦截校验的实现原理其实就是AOP:
 Shiro自定义了PointCut,在PointCut利用反射来判断切点方法是否使用了相关的Shiro注解,在Shiro自己定义的PointCut里初始化了一个Class[]数组存放了Shiro的自定义注解类用于判断
 然后Shiro定义了很多MethodInterceptor(AOP里用于环绕通知),初始化这些自定义MethodInterceptor的时候都会初始化他们的成员变量Handler,
 这些Handler自己或者他们的父类都继承了AnnotationHandler,在AnnotationHandler里定义了protected修饰的方法getSubject()来获取当前Subject,
 而在Shiro自定义的MethodInterceptor里重写了invoke(...)方法,在invoke方法内部具体的认证、权限等安全认证判断都是利用Subject来实现的

Shiro的Filter实现原理

 Shiro内部实现了多个Filter,他们自己活着他们的父类实现了javax.servlet.Filter接口,Shiro的实现机制:
 1. Spring中有个FactoryBean接口,通过beanName从spring容器获取bean实例时,一开始获取的是beanName直接关联的bean实例,后续spring容器会根据此bean实例返回我们需要的对象实例;如果bean实例不是FactoryBean类型,则直接返回bean实例,如果bean实例是FactoryBean类型,而beanName又是以&开头,直接返回bean实例,如果bean实例是FactoryBean类型,而beanName不是以&开头,则返回bean实例的getObject()方法获取的对象实例(一般getObject中就是我们需要的实例对象的创建过程)
 2. ShiroFilterFactoryBean实现了FactoryBean接口,通过getObject方法得到的是AbstractShiroFilter,而ShiroFilterFactoryBean的内部类SpringShiroFilter继承了AbstractShiroFilter,所有getObject方法实际上获取的是SpringShiroFilter
 3. 生成DefaultFilterChainManager(实现了FilterChainManager接口)对象,通过该对象中可以获取ShiroFilterFactoryBean中定义的filters
 5. 在实例化SpringShiroFilter的时候,会生成PathMatchingFilterChainResolver(实现了FilterChainResolver接口)过滤器链解析器,将第三步生成的FilterChainManager设置给PathMatchingFilterChainResolver,将该解析器设置为AbstractShiroFilter(SpringShiroFilter的父类)的FilterChainResolver成员变量
 总结:经过上面几步可以看出SpringShiroFilter就是Shiro整个过滤器链的入口,
ShiroFilterFactoryBean->SpringShiroFilter(AbstractShiroFilter)->FilterChainResolver->Map filters
  当Shiro在Spring中使用的时候,Spring会因为ShiroFilterFactory实现了FactoryBean接口,从而获取Bean的时候会调用getObject()方法获取到SpringShiroFilter,会将SpringShiroFilter注入到ServletContext中,
    最后当有满足条件的请求过来的时候,会先走SpringShiroFilter(AbstractShiroFilter)->然后根据他的拦截器链解析器,分别去执行对应的Filter

你可能感兴趣的:(关于Java开发知识点系统整理)