目录
简介
三大核心组件
身份验证
授权
安全加密:
会话管理
会话
会话管理器
Session超时管理
会话监听
会话存储
Shiro注解实现原理
Shiro的Filter实现原理
Apache Shiro是一个强大且易用的Java安全框架,主要功能有身份验证、授权、安全加密和会话管理。相比较Spring Security,shiro有小巧、简单、易上手等的优点。shiro权限的操作粒度能控制在路径及按钮上,数据粒度通过sql实现。Shrio简单够用。至于OAuth,OpenID 站点间统一登录功能,现如今单点登录很多已经通过cookies实现。因此Shiro完全能够胜任平时项目的安全认证控制。
Subject表示与系统交互的主体,通常情况下我们理解是用户。他包含了用户安全认证的相关授权信息。
SecurityManager则管理所有用户的安全操作。它是Shiro框架的核心,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。主要实现有:CachingSecurityManager、RealmSecurityManager、AuthenticatingSecurityManager、AuthorizingSecurityManager、SessionsSecurityManager、DefaultSecurityManager,采用装饰模式,最终的DefaultSecurityManager覆盖了其余的SecurityMananger的功能。
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实现该抽象方法来实现自定义获取身份信息的具体过程。
主要流程如下:
先看一段代码:
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的安全加密主要使用在几个地方:
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实现是PasswordMatcher和HashedCredentialsMatcher。
(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 容器的会话;
根据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注解,如@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,他们自己活着他们的父类实现了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