ApacheShiro是Java 的一个安全(权限)框架。
Shiro可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。
基本功能
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权自传过;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果允许)的身份进行访问;
Remember Me:记住功能,即一次登录后,下次再来的话不用登录了
Shiro架构
Subject:应用代码直接交互的对象是Subject,Subject 代表了当前“用户”,与Subject 的所有交互都会委托给SecurityManager,SecurityManager才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;管理着所有Subject,负责与Shiro的其他组件进行交互。
Realm:Shiro从Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么需要从Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm 得到用户相应的角色/权限进行验证用户是否能进行操作。
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能
shiro中默认的过滤器
URL 匹配模式
url模式使用Ant 风格模式
Ant 路径通配符支持?、*、**,注意通配符匹配不包括目录分隔符“/”
?:匹配单字符,如/admin? 将匹配/admin1,但不匹配/admin 或/admin/;
*:匹配零个或多个字符串,如/admin 将匹配/admin、/admin123,但不匹配/admin/1;
**:匹配路径中的零个或多个路径,如/admin/** 将匹配/admin/a 或/admin/a/b
URL 匹配顺序
URL 权限采取第一次匹配优先的方式,即从头开始使用第一个匹配的url模式对应的拦截器链。
例如:
/bb/**=filter1
/bb/aa=filter2
/**=filter3
当访问uri是/bb/aa时,将进入filter1拦截器
Authentication(身份认证)
用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份
principals:身份,即主体的标识属性,可以是任何属性。如用户名、邮箱等,唯一即可。一 个主体可以有多个principals,但只有一个Primary principals,一般是用户名/邮箱/ 手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
Authenticator
SecurityManager接口继承了Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm 进行验证,验证规则通过AuthenticationStrategy接口指定
AuthenticationStrategy
FirstSuccessfulStrategy:只要有一个Realm 验证成功即可,只返回第一个Realm 身份验证成功的认证信息,其他的忽略;
AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,将返回所有Realm身份验证成功的认证信息;
AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator默认是AtLeastOneSuccessfulStrategy策略
Permissions
规则:资源标识符:操作:对象实例ID。
默认支持通配符权限字符串,: 表示资源/操作/实例的分割;, 表示操作的分割,* 表示任意资源/操作/实例。
多层次管理:
单值:user:query、user:edit
多值:user:query, edit
泛值:user:*
权限注解
@RequiresAuthentication:表示当前Subject已经通过login 进行了身份验证;即Subject. isAuthenticated() 返回true
@RequiresUser:表示当前Subject 已经身份验证或者通过记住我登录的。
@RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前Subject 需要角色admin 和user
@RequiresPermissions(value={“user:a”, “user:b”}, logical= Logical.OR):表示当前Subject 需要权限user:a或user:b。
会话相关的API
Subject.getSession():即可获取会话;其等价于Subject.getSession(true),即如果当前没有创建Session 对象会创建一个;Subject.getSession(false),如果当前没有创建Session 则返回null
session.getId():获取当前会话的唯一标识
session.getHost():获取当前Subject的主机地址
session.getTimeout() & session.setTimeout(毫秒):获取/设置当前Session的过期时间
session.getStartTimestamp() & session.getLastAccessTime():获取会话的启动时间及最后访问时间.(JavaSE应用需要自己定期调用session.touch() 去更新最后访问时间;如果是Web 应用,每次进入ShiroFilter都会自动调用session.touch() 来更新最后访问时间。)
session.touch() & session.stop():更新会话最后访问时间及销毁会话;当Subject.logout()时会自动调用stop 方法来销毁会话。如果在web中,调用HttpSession. invalidate() 也会自动调用ShiroSession.stop方法进行销毁Shiro的会话
SessionDao
AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等
CachingSessionDAO提供了对开发者透明的会话缓存的功能,需要设置相应的CacheManager
MemorySessionDAO直接在内存中进行会话维护
EnterpriseCacheSessionDAO提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
会话验证
Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话
Shiro提供了会话验证调度器SessionValidationScheduler
Shiro也提供了使用Quartz会话验证调度器:QuartzSessionValidationScheduler
缓存
Shiro内部相应的组件(DefaultSecurityManager)会自动检测相应的对象(如Realm)是否实现了CacheManagerAware并自动注入相应的CacheManager。
Realm 缓存
Shiro提供了CachingRealm,其实现了CacheManagerAware接口,提供了缓存的一些基础实现;
AuthenticatingRealm及AuthorizingRealm也分别提供了对AuthenticationInfo和AuthorizationInfo信息的缓存。
Session 缓存
SecurityManager实现了SessionSecurityManager,其会判断SessionManager是否实现了CacheManagerAware接口,如果实现了会把CacheManager设置给它。
SessionManager也会判断相应的SessionDAO(如继承自CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把CacheManager设置给它
设置了缓存的SessionManager,查询时会先查缓存,如果找不到才查数据库。
HelloWord:
public static void main(String[] args) {
// 初始化配置文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
// 设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 获取当前的 Subject. 调用 SecurityUtils.getSubject();
Subject currentUser = SecurityUtils.getSubject();
// 获取 Session: Subject#getSession()
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("---> Retrieved the correct value! [" + value + "]");
}
// 测试当前的用户是否已经被认证. 即是否已经登录.
if (!currentUser.isAuthenticated()) {
// 把用户名和密码封装为 UsernamePasswordToken 对象
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// 记住
token.setRememberMe(true);
try {
// 执行登录.
currentUser.login(token);
} catch (UnknownAccountException uae) {// 若没有指定的账户, 则 shiro将会抛出 UnknownAccountException异常.
log.info("----> There is no user with username of " + token.getPrincipal());
return;
} catch (IncorrectCredentialsException ice) {// 若账户存在, 但密码不匹配, 则 shiro 会抛出 IncorrectCredentialsException 异常。
log.info("----> Password for account " + token.getPrincipal() + " was incorrect!");
return;
} catch (LockedAccountException lae) {// 用户被锁定的异常 LockedAccountException
log.info("The account for username " + token.getPrincipal() + " is locked. "
+ "Please contact your administrator to unlock it.");
} catch (AuthenticationException ae) {// 所有认证时异常的父类.
// unexpected condition? error?
}
}
log.info("----> User [" + currentUser.getPrincipal() + "] logged in successfully.");
// 测试是否有某一个角色. 调用 Subject 的 hasRole 方法.
if (currentUser.hasRole("schwartz")) {
log.info("----> May the Schwartz be with you!");
} else {
log.info("----> Hello, mere mortal.");
return;
}
// 测试用户是否具备某一个行为. 调用 Subject 的 isPermitted() 方法。
if (currentUser.isPermitted("lightsaber:weild")) {
log.info("----> You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
// 测试用户是否具备某一个行为.
if (currentUser.isPermitted("user:delete:zhangsan")) {
log.info("----> You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. "
+ "Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
// 执行登出. 调用 Subject 的 Logout() 方法.
System.out.println("---->" + currentUser.isAuthenticated());
currentUser.logout();
System.out.println("---->" + currentUser.isAuthenticated());
System.exit(0);
}