shiro是Java的一个安全权限框架
主要四大功能
authentication authorization session mangement cryptography
认证 授权 会话管理 加密
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用
户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用
户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信
息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到 Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能
把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录
记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过
相应的接口注入给 Shiro 即可。
application - >subject ->shrio securitymanager -> realm
应用 当前用户 验证 接受用户信息
Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
SecurityManager : 相 当 于 SpringMVC 中 的 DispatcherServlet 或 者 Struts2 中的
FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,
这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);
SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO中可以使用 Cache 进行缓存,以提高性能;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本
上很少去改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密
shiroFilter 拦截器
spring集成时,web.xml配置拦截器DelegatingFilterProxy 的名字和IOC容器shiroFilter的id一致
DelegatingFilterProxy是shiroFilter 的代理,以上情况是默认情况。
可以用配置初始化参数指定IOC容器中shiroFilter 拦截器的id
<param-name>targetBeanName</>
<param-value>id</>
shiroFilter 拦截器的属性
filterChainDefinitions属性
url=拦截器[参数] anon匿名 authc有权限 logout退出登录 roles角色 roles[权限] user
url支持ANT风格 * ** ?
url权限第一次优先匹配的方式
Shiro 身份验证
在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名 / 密码 / 手机号。
credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。
最常见的 principals 和 credentials 组合就是用户名 / 密码了。
=========================
shiro.ini 配置文件
[users]
zhang=123
wang=123
此处使用 ini 配置文件,通过 [users] 指定了两个主体:zhang/123、wang/123。
测试用例
@Test
public void testHelloworld() {
//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
Factory<org.apache.shiro.mgt.SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
//2、得到SecurityManager实例 并绑定给SecurityUtils
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
//4、登录,即身份验证
subject.login(token);
} catch (AuthenticationException e) {
//5、身份验证失败
}
Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录
//6、退出
subject.logout();
}
普通认证流程
1 获取当前的subject,调用securetyUtils.getSubject()
2 测试当前用户是否以及被认证了,即是否登录了,调用subject.isAuthenticated()
3 若没有被认证,获取表单数据,则把用户名和密码封装成usernamePasswordToken对象
4 执行登录 ,调用subject.login(AuthenticationToken)方法,把封装的token对象传入该方法
5 自定义realm方法,从数据库中获取用户名的信息,返回给shiro
6 shiro进行密码比对。
===================
Realm:域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。如我们之前的 ini 配置方式将使用 org.apache.shiro.realm.text.IniRealm。
重要的一点是:realm需要交给securityManager管理,这样才能当用户调用subject.login()方法时,调用我们设置的realm,可以用代码设置,也可以在容器里配置
securityManager.setRealms(new MyRealm()); 多个realm对象时,把这些对象打包成一个list,再传入。
自定义realm
org.apache.shiro.realm.AuthencatingReam类
实现doGetAuthenticationInfo(AuthenticationToken)方法,这个方法中的token对象即为subject.login(AuthenticationToken)传入的token对象。
doGetAuthenticationInfo方法需要做的事
1 AuthenticationToken对象转换成UsernamePasswordToken
2 UsernamePasswordToken获取username
3 调用数据库的方法,查询数据库username的用户信息
4 若用户不存在,被锁定,抛出相应的异常
5 根据用户信息的情况,抛出其他异常AuthenticationException
6 根据用户的情况,构建AuthenticationInfo对象,并返回给方法
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
if(!"zhang".equals(username)) {
throw new UnknownAccountException(); //如果用户名错误
}
if(!"123".equals(password)) {
throw new IncorrectCredentialsException(); //如果密码错误
}
//如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(username, password, getName());
}
AuthenticationInfo 通常使用实现类SimpleAuthenticationInfo
principal 认证的实体信息
credentials 密码 以上两个信息是从数据库获取
realmName =getName()调用父类getName的获取
SimpleAuthenticationInfo( principal credentials credentialsSalt=null realmName)
credentialsSalt=ByteSource.Util.bytes(username)
SimpleHash(hashAlgorithName creden.. salt hashIterations)hashAlgorithName加密算法的名字 hashIterations加密次数
salt盐值 密码相同时,区分用户的手段,以上方法可以返回加密密码返回的结果哈希值
要求盐值唯一,一采用用户名或者随机的字符串
密码加密 为了不让密码以明文的方式展现
密码的比对 AuthenticatingRealm 的credentialsMatcher属性类进行密码匹配
AuthenticatingRealm 类的属性credentialsMatcher可以赋予不同的实现类,采用不同的加密算法。
HashedCredentialsMatcher实现类 具有hashAlgorithName=MD5 hashIterations=1024
myRealm.sethashAlgorithName() 为credentialsMatcher属性设置加密算法
myRealm.sethashIterations() 为credentialsMatcher属性设置加密次数
SimpleHash()这个方法可以算出密码加密后的结果。
多各数据库,即多个realm的情况下
Authenticator 认证器,管理realms的类
ModularRealmAuthenticatior类是认证器的一个实现类
ModularRealmAuthenticatior类的realms属性 是列表类型,可以装入多个realm,进行管理。
ModularRealmAuthenticatior 具有AuthenticationStrategy属性,这个属性很重要,如果你不想使用默认的认证策略,你可以生成一个认证类,用setter
更改认证策略,再交给securityManager处理,期间你可以用认证器realms属性添加全部的realm,就无需向securityManager设置realms源。
realms属性列表的realm顺序即为认证的顺序
当有多个realm的情况下又采用认证策略
AuthenticationStrategy
AuthenticationStrategy接口的默认实现类有
FirstSuccessfulStrategy 只要有一个realm认证成功即可,返回第一个realm认证成功的信息
AtLeastOneSuccessfulStrategy只要有一个realm认证成功即可,返回所有realm的认证信息 默认的认证策略
AllSuccessfulStrategy 所有的realm验证成功才返回信息,任何一个失败认证失败
授权
Authrizer:授权器
需要把realm 传给SecurityManager
也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)
判断用户是否有权限,两种方式
#1 if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
#2 @RequiresRoles("admin")
public void hello() {
//有权限
}
==================
最重要的一点是,需要在shiro的配置文件设置用户用于的权限或者角色。
而与Web集成时,只需要在shiroFilter设置页面的访问权限,又在配置文件设置用户的权限,用户进入相应的页面后,shiro自动调用过滤器判断
用户是否有权限,无需显示地调用代码判断用户权限。
自定义
权限
安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面
查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)
打印文档等等。。。
shiroFilter属性unauthorizedUrl 指定没有权限去的页面
subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去调用这个是否有什么角色或者是否有什么权限的时候;
@RequiresRoles(“admin”) :在方法上加注解的时候,或者xml中配置了某个请求需要什么权限的时候;
将会执行
AuthrizingRealm类
doGetAuthorizationInfo(PrincipalCollection )
1 从PrincipalCollection获取登录用户的信息
2 利用登录用户的信息,从数据库中查找该用户是否具有相应的角色或权限
3 创建SimpleAuthorizationInfo,并设置roles属性。 SimpleAuthorizationInfo(roles)
4 返回Info
roles实现
roles[system,general] ,表示同时需要“system”和“general” 2个角色才通过认证
给shiroFilter的filterChainDefinitions属性设置页面的访问权限 而unauthorizedUrl属性 :没有权限默认跳转的页面
successUrl属性 :登录成功默认跳转页面,不配置则跳转 至”/” loginUrl属性 :没有登录的用户请求需要登录的页面时 自动跳转到登录页面,不是必须的属性
<value>
/login = authc
/login/logout = anon 匿名
/ = anon
/XXX/** = user,anyRoles[system,general]
/TTT = role[system]
/** = user
</value>
filter的过滤器有 anno authc需要登录 authcBasic perms roles user logout
注:anon,authcBasic,auchc,user是认证过滤器,
perms,roles,ssl,rest,port是授权过滤器
user:例如/admins/user/**=user没有参数表示必须存在用户, 当登入操作时不做检查
perms:例子/admins/user/**=perms[user:add:*],参数可以写 多个,多个时必须加上引号,并且参数之间用逗号分割, 例如/admins/user/**=perms["user:add:*,user:modify:*"],当 有多个参数时必须每个参数都通过才通过,想当于 isPermitedAll()方法。
权限注解
@RequiresAuthentication 当前的用户subject已经通过login验证,subject.isAuthenticated()返回true
@RequiresUser 表示当前用户已经经过身份验证 或通过记录我登录
@RequiresGuest 表示当前用户没有经过身份验证 和没通过记录我登录,为游客身份
@RequiresRoles(value={'a','b'} ,logical=Logical.AND)表示当前用户同时需要a,b角色
@RequiresPermissions(value={'user:a' ,'user:b'},logical=Logical.OR)
表示当前用户需要a 权限或者b权限
shiro从数据库中获取初始资源,即页面的权限信息等。
shiroFilter 的filterChainDefinitionMap属性
配置一个bean,实际上是一个对象,通过实例工厂方法的方式factory-method指定的bean方法返回map对象
map.put('/url','anon') LinkedHashMap必须的,添加要有顺序
会话管理
session 与serlvet的session没有多大的区别
sessionListener提供的session监听器
SecurityUtils.getSubject().getSession()
service层也可以访问session里面的数据
SessionDao接口
EnterpriseCacheSessionDao实现类 开发时继承该Dao
MemorySessionDao实现类 内存当中操作session
sessionIdGenerator
EnterpriseCacheSessionDao
属性sessionIdGenerator =要配置sessionID 必须的
会话管理器
sessionManager
EnterpriseCacheSessionDao实现类要配置给会话管理器,而会话管理器要配置给Security
sessionDao对session进行增删改查,利用对象输入输出流
会话验证 验证会话是否过期,过期停止会话
在web环境,shiro 提供会话验证调度器,定期验证会话是否过期
SessionValidationScheduler
提供了一个Quartz调度器QuartzSessionValidationScheduler
缓存
shiro内部相应的组件DefaultSecurityManager会自动检测相应的对象,如realm
是否实现了CacheManagerAware接口,并自动注入相应的CacheManager中。
RemenberMe 记住我
把cookie写到客户端
subject.isRemenberd()
认证和记住我只能二选其一
user过滤器,可以通过记住我的方式方面页面
token.setRemenberMe(true)
securityManager的remenberMeManager属性 设置cookie过期时间remenberMeManager.cookie.Maxage