在使用shiro时都会自定义一个Realm,Realm的作用就是提供给shiro和数据库进行交互的一个中间层,这样shiro能够帮助我们处理登录(成功、失败),授权,访问控制等功能,但是用户登录的用户信息和用户具体的权限信息是shiro未知的,所以需要每次都请求Realm,由Realm提供
比如授权的流程
使用PermissionAuthorizationFilter
在xml中设置权限
${adminPath}/areaInfo = perms[area:query,area:add]
当发出这个请求(已经验证通过)被PermisssionsAuthorizationFilter拦截,发现需要权限
会调用realm的doGetAuthorizationInfo获取数据库中正确的数据库权限(所以realm的作用 就是查询,剩下的活是shiro的)
PermissionAuthorizationFilter对请求的url对应的权限和从realm中获取的权限进行对比
登录和授权,realm只提供查找返回,不提供逻辑处理,都由shiro解决
拿login来分析
当请求信息是login时,FormAuthenticationFilter会对其进行拦截,解析是不是login请求,并处理
//FormAuthenticationFilter 判断是不是一个login请求
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
//allow them to see the login page ;)
return true;
}
} else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the " +
"Authentication url [" + getLoginUrl() + "]");
}
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
可以看到如果是login请求会执行executeLogin方法
//AuthenticatingFilter 此处找到subject.login(token)操作
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
AuthenticationToken token = createToken(request, response);
if (token == null) {
String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
"must be created in order to execute a login attempt.";
throw new IllegalStateException(msg);
}
try {
Subject subject = getSubject(request, response);
subject.login(token);//调用了realm
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
//DelegatingSubject
public void login(AuthenticationToken token) throws AuthenticationException {
clearRunAsIdentitiesInternal();
Subject subject = securityManager.login(this, token);//调用了realm
PrincipalCollection principals;
String host = null;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject) subject;
//we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}
if (principals == null || principals.isEmpty()) {
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);
}
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 = decorate(session);
} else {
this.session = null;
}
}
由securityManager进行登录
重点来了
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
try {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
}
}
throw ae; //propagate
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
我们发现public class DefaultSecurityManager extends SessionsSecurityManager
也就是上面的securityManager的实现类中调用的login,这里就是反悔了一个AuthenticationInfo对象,其中autheticate(token)这个方法继续跟踪到最后就会到达我们的自定义realm中的doGetAuthenticationInfo方法
所以这个info主要是为这行代码服务的
Subject loggedIn = createSubject(token, info, subject);
即生成subject的代码
这样就完成了交互