shiro 框架学习笔记-未完结
说明
该文档中的所有代码为 class 文件 反编译后的源码,与真实源码有出入,仅供大概理解。
shiro 中的重要对象解释
-
Subject:主体,可以看到主体是任何可以和应用交互的 “用户”
-
SecurityManager:安全管理器,相当于springMVC中的DispatcherServlet,所有具体的交互都通过 SecurityManager 来进行控制;它管理所有的 Subject、并且负责认证和授权、及会话、缓存的管理
-
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算是认证通过了;
-
Authorizer:授权器, 或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的那些功能;
-
Relams:域,可以有一个或多个Relams,可以认为是实体安全数据源,即用于获取安全实体的;可以是JDBC实现,或者是LDAP实现,或者内存实现等等。注意:shiro 不知道你的用户/权限存储在哪及以何种格式存储,所以我们一般需要在应用中实现自己的Relams。
-
SessionManager:大家应该都知道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提供了一些常见的加密组件,用于密码加密/解密的。
官方文档
官方文档-Core Concepts: Subject, SecurityManager, and Realms
Subject
Subject:用户,Subject 只是一个安全术语,它真实的含义其实就是 User ,一旦获取了Subject 对象,立刻会拥有在shiro中几乎所有的功能,例如登陆、注销、访问他们的会话、执行授权检查等等;这里的关键点是 Shiro 的 API 在很大程度上是直观的,因为它反映了开发人员考虑“每用户”安全控制的自然倾向。在代码 中的任何位置访问 Subject 也很容易,从而允许在任何需要的地方进行安全操作。
public interface Subject {
Object getPrincipal(); // 返回当前subject的唯一标识,判断subject是否登陆
PrincipalCollection getPrincipals(); // 和 getPrincipal() 一样,返回的是PrincipalCollection对象
/**
* 下面四个方法,是用来判断权限的
*/
boolean isPermitted(String var1);
boolean isPermitted(Permission var1);
boolean[] isPermitted(String... var1);
boolean[] isPermitted(List<Permission> var1);
boolean isPermittedAll(String... var1);
boolean isPermittedAll(Collection<Permission> var1);
void checkPermission(String var1) throws AuthorizationException;
void checkPermission(Permission var1) throws AuthorizationException;
void checkPermissions(String... var1) throws AuthorizationException;
void checkPermissions(Collection<Permission> var1) throws AuthorizationException;
boolean hasRole(String var1);
boolean[] hasRoles(List<String> var1);
boolean hasAllRoles(Collection<String> var1);
void checkRole(String var1) throws AuthorizationException;
void checkRoles(Collection<String> var1) throws AuthorizationException;
void checkRoles(String... var1) throws AuthorizationException;
void login(AuthenticationToken var1) throws AuthenticationException; // 用户登陆
boolean isAuthenticated();
boolean isRemembered();
Session getSession();
Session getSession(boolean var1);
void logout(); // 用户登出
<V> V execute(Callable<V> var1) throws ExecutionException;
void execute(Runnable var1);
<V> Callable<V> associateWith(Callable<V> var1);
Runnable associateWith(Runnable var1);
void runAs(PrincipalCollection var1) throws NullPointerException, IllegalStateException;
boolean isRunAs();
PrincipalCollection getPreviousPrincipals();
PrincipalCollection releaseRunAs();
public static class Builder {
private final SubjectContext subjectContext;
private final SecurityManager securityManager;
public Builder() {
this(SecurityUtils.getSecurityManager());
}
public Builder(SecurityManager securityManager) {
if (securityManager == null) {
throw new NullPointerException("SecurityManager method argument cannot be null.");
} else {
this.securityManager = securityManager;
this.subjectContext = this.newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' cannot be null.");
} else {
this.subjectContext.setSecurityManager(securityManager);
}
}
}
protected SubjectContext newSubjectContextInstance() {
return new DefaultSubjectContext();
}
protected SubjectContext getSubjectContext() {
return this.subjectContext;
}
public Subject.Builder sessionId(Serializable sessionId) {
if (sessionId != null) {
this.subjectContext.setSessionId(sessionId);
}
return this;
}
public Subject.Builder host(String host) {
if (StringUtils.hasText(host)) {
this.subjectContext.setHost(host);
}
return this;
}
public Subject.Builder session(Session session) {
if (session != null) {
this.subjectContext.setSession(session);
}
return this;
}
public Subject.Builder principals(PrincipalCollection principals) {
if (principals != null && !principals.isEmpty()) {
this.subjectContext.setPrincipals(principals);
}
return this;
}
public Subject.Builder sessionCreationEnabled(boolean enabled) {
this.subjectContext.setSessionCreationEnabled(enabled);
return this;
}
public Subject.Builder authenticated(boolean authenticated) {
this.subjectContext.setAuthenticated(authenticated);
return this;
}
public Subject.Builder contextAttribute(String attributeKey, Object attributeValue) {
if (attributeKey == null) {
String msg = "Subject context map key cannot be null.";
throw new IllegalArgumentException(msg);
} else {
if (attributeValue == null) {
this.subjectContext.remove(attributeKey);
} else {
this.subjectContext.put(attributeKey, attributeValue);
}
return this;
}
}
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
}
}
SecurityManager
SecurityManager: 安全管理器,即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有的 Subject ;可以看出它是Shiro 的核心,它负责与其他组件进行交互。
Realm
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
public interface Realm {
String getName(); // 返回一个唯一的 Realm 名字
boolean supports(AuthenticationToken var1); // 判断此 Realm 是否支持此 Token
// 根据 Token 获取认证信息
AuthenticationInfo getAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
}
源码阅读
说明
该文档中的所有代码为 class 文件 反编译后的源码,与真实源码有出入,仅供大概理解。
一、Shiro 登陆流程
1、登陆代码入口
@Override
public String login(LoginReqBody reqBody) {
this.verify(reqBody);
UsernamePasswordToken token = new UsernamePasswordToken(reqBody.getUsername(), reqBody.getPassword());
Subject subject = SecurityUtils.getSubject(); // 创建一个用户对象
try {
subject.login(token); // <----------- 入口
} catch (AuthenticationException e) {
throw new BusinessException("用户名或密码错误", e);
}
return this.getToken(reqBody.getUsername(), reqBody.getTerminal());
}
2、打开 Subject (用户) 的 login() 实现
void login(AuthenticationToken var1) throws AuthenticationException;
DelegatingSubject (授权用户) 的 login() 方法
public void login(AuthenticationToken token) throws AuthenticationException {
this.clearRunAsIdentitiesInternal();
// 调用 SecurityManager 的 login() 方法
Subject subject = this.securityManager.login(this, token);
String host = null;
PrincipalCollection principals;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject)subject;
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}
if (principals != null && !principals.isEmpty()) {
this.principals = principals;
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken)token).getHost();
}
if (host != null) {
this.host = host;
}
Session session = subject.getSession(false);
if (session != null) {
this.session = this.decorate(session);
} else {
this.session = null;
}
} else {
String msg = "Principals returned from securityManager.login( token ) returned a null or empty value. This value must be non null and populated with one or more elements.";
throw new IllegalStateException(msg);
}
}
3、 打开 SecurityManager (安全管理) 的 login() 的实现
Subject login(Subject var1, AuthenticationToken var2) throws AuthenticationException;
DefaultSecurityManager (默认安全管理策略) 的 login() 方法
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
// 调用当前类的认证方法
info = this.authenticate(token);
} catch (AuthenticationException var7) {
AuthenticationException ae = var7;
try {
this.onFailedLogin(token, ae, subject);
} catch (Exception var6) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6);
}
}
throw var7;
}
// 创建 登陆用户对象
Subject loggedIn = this.createSubject(token, info, subject);
// 进行 登陆成功操作
this.onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
DefaultSecurityManager (默认安全管理策略) 的 this.authenticate() 认证方法
调用 -> Authenticator (认证者) 的 authenticate() 认证方法
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
4、 打开 Authenticator (认证者) 的 authenticate() 认证方法
public interface Authenticator { AuthenticationInfo authenticate(AuthenticationToken var1) throws AuthenticationException;}
AbstractAuthenticator (认证者抽象类) 的 authenticate() 方法
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { if (token == null) { throw new IllegalArgumentException("Method argument (authentication token) cannot be null."); } else { log.trace("Authentication attempt received for token [{}]", token); AuthenticationInfo info; try { // 调用 抽象方法 doAuthenticate() info = this.doAuthenticate(token); if (info == null) { String msg = "No account information found for authentication token [" + token + "] by this Authenticator instance. Please check that it is configured correctly."; throw new AuthenticationException(msg); } } catch (Throwable var8) { AuthenticationException ae = null; if (var8 instanceof AuthenticationException) { ae = (AuthenticationException)var8; } if (ae == null) { String msg = "Authentication failed for token submission [" + token + "]. Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException)."; ae = new AuthenticationException(msg, var8); if (log.isWarnEnabled()) { log.warn(msg, var8); } } try { this.notifyFailure(token, ae); } catch (Throwable var7) { if (log.isWarnEnabled()) { String msg = "Unable to send notification for failed authentication attempt - listener error?. Please check your AuthenticationListener implementation(s). Logging sending exception and propagating original AuthenticationException instead..."; log.warn(msg, var7); } } throw ae; } log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info); this.notifySuccess(token, info); return info; }}
5、AbstractAuthenticator (认证者抽象类) 的 doAuthenticate() 抽象方法 实现
protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken var1) throws AuthenticationException;
**ModularRealmAuthenticator (模块化的域认证者) **的 doAuthenticate() 抽象方法 实现
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { this.assertRealmsConfigured(); Collection<Realm> realms = this.getRealms(); // 如果域中的数据 为 1 调用 this.doSingleRealmAuthentication() 方法 return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);}
**ModularRealmAuthenticator (模块化的域认证者) **的 doSingleRealmAuthentication() 单例域认证方法
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) { if (!realm.supports(token)) { String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is configured correctly or that the realm accepts AuthenticationTokens of this type."; throw new UnsupportedTokenException(msg); } else { AuthenticationInfo info = realm.getAuthenticationInfo(token); if (info == null) { String msg = "Realm [" + realm + "] was unable to find account data for the submitted AuthenticationToken [" + token + "]."; throw new UnknownAccountException(msg); } else { return info; } }