# 1.在 shiro 中的概念 #
用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份
Subject 及 Realm,分别是主体及验证主体的数据源
认证[身份验证]: 验证在应用中是否是他本人.
授权[访问控制]: 控制谁能访问哪些资源
主体, 即访问应用的用户,Subject
资源, 在应用中用户可以访问的任何东西,用户授权之后才能访问.
权限, 安全策略的原子授权单位, 表示在应用中用户有没有操作某个资源的权力。
角色, 角色代表权限的集合.为了赋予用户权限时方便, 一般赋予用户多个角色。
# 2.身份验证大体流程 #
1. 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;
2. SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
3. Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
4. Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
5. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。
# 3.Authenticator配置 #
Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验证核心的入口点
SecurityManager 接口继承了 Authenticator,另外还有一个 **ModularRealmAuthenticator** 实现,其委托给多个 Realm 进行验证,验证规则通过 AuthenticationStrategy 接口指定,默认提供的实现:
FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
AtLeastOneSuccessfulStrategy:只要有一个 Realm 验证成功即可,和 FirstSuccessfulStrategy 不同,返回所有 Realm 身份验证成功的认证信息;
AllSuccessfulStrategy:所有 Realm 验证成功才算成功,且返回所有 Realm 身份验证成功的认证信息,如果有一个失败就失败了。
***ModularRealmAuthenticator*** 默认使用 ***AtLeastOneSuccessfulStrategy*** 策略。
如果需要自定义的AuthenticationStrategy, 可以继承AbstractAuthenticationStrategy.
# 4.授权方式 #
1. 编程式授权
Subject subject = SecurityUtils.getSubject();
if (subject.hasRole("admin")){
// 有权限
} else {
// 无权限
}
2. 注解式
通过在执行的JAVA方法上放置相应的注解完成.
@RequiresRoles("admin")
public void hello(){
// 有权限才能访问, 无权限将抛出异常.
}
3. JSP/GSP标签: 在jsp/gsp页面中通过标签完成
# 5.基于资源的访问控制 #
1. 字符串通配符规则
'资源描述符:操作: 对象实例ID' 即对哪个资源的哪个实例可以继续哪些操作. 默认支持通配符权限字符串, ":" 表示资源/操作/实例的分隔;
','表示操作的分隔; "*"表示任意资源/操作/实例
[e.单个资源单个权限]
subject().checkPermissions("system:user:update");
[e.单个资源全部权限]
"system:user"的'create','update','delete'和'view'所有权限,可以简写成:
.ini: role52=system:user:* / system:user
然后通过代码判断:
subject().checkPermissions("system:user:*");
subject().checkPermissions("system:user");
[e.所有资源全部权限]
.ini: role61=*:view #所有资源的view权限
subject().checkPermissions("user:view");
[e.实例级别的权限]
.ini:role71=user:view:1 #对资源user的1实例拥有view权限
subject().checkPermissions("user:view:1");
[e.单个实例多个权限]
.ini: role72="user:update,delete:1"
subject().checkPermissions("user:delete,update:1");
subject().checkPermissions("user:update:1", "user:delete:1");
[e.所有实例 单个权限]
.ini: role74=user:auth:* #对资源user:auth拥有权限
subject().checkPermmissions("user:auth:1", "user:auth:2");
[e.所有实例所有权限]
role75=user:*:* #对资源user的1实例拥有所有权限
subject().checkPermmissions("user:view:1", "user:auth:2");
权限字符串 缺失部分处理
"user:view"等价于"user:view:*"
"organization"等价于"organization:*:*"
*表示匹配所有, 不加*可以进行前缀匹配;
tips:
subject().checkPermission("menu:view:1");
subject().checkPermission(new WildcardPermission("menu:view:1"));
这两种是等价的.
# 6.加密与解密 #
## DefaultHashService的使用 ##
设置的散列算法: SHA-512, MD5 等
service可以设置私盐(默认无), 公盐(默认不)
HashRequest 可以设置了公盐
service 如果设置了私盐, service无论如何都会用公盐
返回的Hash对象只可能包含公盐.[私盐在service对象内]
HashRequest里面的配置优先于HashService内的配置.
## 对称式加密与解密 ##
AES, Blowfish等算法
详情见:http://wiki.jikexueyuan.com/project/shiro/encoding.html
## PasswordService/CredentialsMatcher ##
加密密码与密码校验
AuthenticatingRealm默认使用assertCredentialsMatch方法验证token的明文密码和info的密文密码是否一致
而这个方法的默认实现是:
1. 拿到当前realm的 credentialsMatcher, 用它来验证.
我们可以重写方法,也可以重写设置credentialsMatcher.
下面是重写设置matcher的配置.
# 这里配置的matcher, 将用于 token里面的明文密码 散列, 和realm返回的info的ps比对
# 所以, 数据库存储时是如何将密码加密存储的, 这里就如何配置
credentialsMatcher=com.ren.credentials.RetryLimitHashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=md5
credentialsMatcher.hashIterations=2
credentialsMatcher.storedCredentialsHexEncoded=true
userRealm=com.ren.realm.UserRealm
userRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$userRealm
# 7.Realm #
AuthenticationToken 用于收集用户提交的身份(如用户名)及凭据(如密码)
AuthenticationInfo 有两个作用:
如果 Realm 是 AuthenticatingRealm 子类,则提供给 AuthenticatingRealm 内部使用的 CredentialsMatcher 进行凭据验证;(如果没有继承它需要在自己的 Realm 中自己实现验证);
提供给 SecurityManager 来创建 Subject(提供身份信息);
# 8.Subject #
是 Shiro 的核心对象,基本所有身份验证、授权都是通过 Subject 完成。
##1、身份信息获取##
##2、身份验证 ##
void login(AuthenticationToken token) throws AuthenticationException;
boolean isAuthenticated();
boolean isRemembered();
通过 login 登录,如果登录失败将抛出相应的 AuthenticationException,如果登录成功调用 isAuthenticated 就会返回 true,即已经通过身份验证;
如果 isRemembered 返回 true,表示是通过记住我功能登录的而不是调用 login 方法登录的。isAuthenticated/isRemembered 是互斥的,即如果其中一个返回 true,另一个返回 false。
3、角色授权验证
boolean hasRole(String roleIdentifier);
void checkRole(String roleIdentifier) throws
hasRole 进行角色验证,验证后返回 true/false;而 checkRole 验证失败时抛出 AuthorizationException 异常。
4、权限授权验证
boolean isPermitted(String permission);
...
isPermitted 进行权限验证,验证后返回 true/false;而 checkPermission 验证失败时抛出 AuthorizationException。
5、会话
Session getSession(); //相当于getSession(true)
Session getSession(boolean create);
类似于 Web 中的会话。如果登录成功就相当于建立了会话,接着可以使用 getSession 获取;如果 create=false 如果没有会话将返回 null,而 create=true 如果没有会话会强制创建一个。
6、退出
void logout();
7、RunAs
void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException;
boolean isRunAs();
PrincipalCollection getPreviousPrincipals();
PrincipalCollection releaseRunAs();
RunAs 即实现 “允许 A 假设为 B 身份进行访问”;通过调用 subject.runAs(b) 进行访问;
接着调用 subject.getPrincipals 将获取到 B 的身份;此时调用 isRunAs 将返回 true;而 a 的身份需要通过 subject. getPreviousPrincipals 获取;如果不需要 RunAs 了调用 subject. releaseRunAs 即可。
8、多线程
void execute(Runnable runnable);
Runnable associateWith(Runnable runnable);
实现线程之间的 Subject 传播,因为 Subject 是线程绑定的;因此在多线程执行中需要传播到相应的线程才能获取到相应的 Subject。最简单的办法就是通过 execute(runnable/callable 实例) 直接调用;或者通过 associateWith(runnable/callable 实例) 得到一个包装后的实例;它们都是通过:1、把当前线程的 Subject 绑定过去;2、在线程执行结束后自动释放。
Subject 自己不会实现相应的身份验证 / 授权逻辑,而是通过 DelegatingSubject 委托给 SecurityManager 实现;及可以理解为 Subject 是一个面门。
对于 Subject 的构建一般没必要我们去创建;一般通过 SecurityUtils.getSubject() 获取:
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
即首先查看当前线程是否绑定了 Subject,如果没有通过 Subject.Builder 构建一个然后绑定到现场返回。
如果想自定义创建,可以通过:
new Subject.Builder().principals(身份).authenticated(true/false).buildSubject()
这种可以创建相应的 Subject 实例了,然后自己绑定到线程即可。在 new Builder() 时如果没有传入 SecurityManager,自动调用 SecurityUtils.getSecurityManager 获取;也可以自己传入一个实例。
对于 Subject 我们一般这么使用:
1.login 2.授权(hasRole/...) 5.logout
至于多线程身份传播, ..
#9.web集成 #
ini配置中的 url配置
[urls]
/login=anon
/unauthorized=anon
/static/**=anon
/authenticated=authc
/role=authc,roles[admin]
/permission=authc,perms["user:create"]
url模式匹配顺序,url模式匹配顺序按照配置中声明的顺序匹配,即从头开始使用第一个匹配的url模式对于的拦截器.如:
/bb/**=filter1
/bb/aa=filter2
/**=filter3
如果请求是/bb/aa, 将会按照顺序匹配,
默认登录URL配置:[访问这些地址首先判断用户有没有登录, 如果没有登录默认会跳转到登录页面..,默认是/login.jsp,]
authc.loginUrl=/login