一、身份验证流程(左),权限验证流程(右)
二、原理
- 组件
组件 | 功能 |
---|---|
shiro Filter | 各种验证流程的入口 |
Subject | 代表了登录人,记录session,凭证等相关信息。定义了验证流程所需要的基本的验证方法,每次请求都会创建新的Subject |
SecurityManager | 管理Subject,session等信息,辅助subject实现功能 |
session | 类似servlet session,每个登录用户都有唯一一个session相对应 |
Realm | 需要开发者实现的部分,通过realm获取权限,角色信息,身份信息 |
AuthenticationInfo | 代表了通过realm获取的用于身份验证的信息 |
AuthorizationInfo | 代表了通过realm获取的用于权限验证的信息 |
AuthenticationToken | 代表了请求的凭证信息 |
CredentialsMatcher | 主要用于密码验证 |
PasswordService | 用于生成密码和辅助CredentialsMatcher密码验证 |
AuthenticationStrategy | 身份验证策略,多个Realm时需要策略来决定身份验证是否通过。类似投票表决 |
Authenticator | 身份验证 |
-
组件间关系
-
身份验证流程相关对象和方法
- AbstractShiroFilter.doFilterInternal)
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
//每次请求都会创建新的Subject
final Subject subject = createSubject(request, response);
//将创建的subject绑定到当前线程,并调用FormAuthenticationFilter过滤器
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException ex) {
t = ex.getCause();
} catch (Throwable throwable) {
t = throwable;
}
}
- FormAuthenticationFilter.onAccessDenied)
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.");
}
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;
}
}
- AuthenticatingFilter.executeLogin
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 subject = getSubject(request, response);
//登录
subject.login(token);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
- DelegatingSubject.login
public void login(AuthenticationToken token) throws AuthenticationException {
//清除当前的身份证明信息
clearRunAsIdentitiesInternal();
//登录,返回的subject只是临时的,作用只是把数据传递给当前subject
Subject subject = securityManager.login(this, token);
PrincipalCollection principals;
String host = null;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject) subject;
//获取身份证明信息
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);
}
//为当前subject设置身份证明信息
this.principals = principals;
//为当前subject设置授权
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken) token).getHost();
}
if (host != null) {
//为当前subject设置host
this.host = host;
}
//获取session信息
Session session = subject.getSession(false);
if (session != null) {
//为当前subject设置session
this.session = decorate(session);
} else {
this.session = null;
}
}
- DefaultSecurityManager.login
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;
}
- ModularRealmAuthenticator.authenticate
//验证策略选择
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
//一般的用户名、密码验证
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
//多种策略,默认提供了三种
//1.全成功则成功 2.至少一个成功则成功 3.首个成功则成功
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
//用户名、密码验证
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);
}
//用户名、密码验证
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);
}
return info;
}
- AuthenticatingRealm.getAuthenticationInfo
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//调用用户realm获取用户信息,这里也可以进行用户名、密码的验证
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
//通过CredentialsMatcher来验证密码
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
- SimpleCredentialsMatcher.equals
protected boolean equals(Object tokenCredentials, Object accountCredentials) {
if (log.isDebugEnabled()) {
log.debug("Performing credentials equality check for tokenCredentials of type [" +
tokenCredentials.getClass().getName() + " and accountCredentials of type [" +
accountCredentials.getClass().getName() + "]");
}
if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) {
if (log.isDebugEnabled()) {
log.debug("Both credentials arguments can be easily converted to byte arrays. Performing " +
"array equals comparison");
}
byte[] tokenBytes = toBytes(tokenCredentials);
byte[] accountBytes = toBytes(accountCredentials);
return MessageDigest.isEqual(tokenBytes, accountBytes);
} else {
//简单验证请求的密码和库里密码是否相等
return accountCredentials.equals(tokenCredentials);
}
}
- 权限验证流程相关对象和方法
用到了两个过滤器- UserFilter:判断用户是否登录
- PermissionsAuthorizationFilter:判断用户权限是否匹配
- AbstractShiroFilter.doFilterInternal)
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
//每次请求都会创建新的Subject
final Subject subject = createSubject(request, response);
//将创建的subject绑定到当前线程,并调用FormAuthenticationFilter过滤器
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException ex) {
t = ex.getCause();
} catch (Throwable throwable) {
t = throwable;
}
}
- UserFilter.isAccessAllowed
public class UserFilter extends AccessControlFilter {
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//不拦截登录流程
if (isLoginRequest(request, response)) {
return true;
} else {
//是否登录
Subject subject = getSubject(request, response);
return subject.getPrincipal() != null;
}
}
}
- PermissionsAuthorizationFilter.isAccessAllowed
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
//配置的权限信息
String[] perms = (String[]) mappedValue;
boolean isPermitted = true;
if (perms != null && perms.length > 0) {
//验证权限
if (perms.length == 1) {
if (!subject.isPermitted(perms[0])) {
isPermitted = false;
}
} else {
if (!subject.isPermittedAll(perms)) {
isPermitted = false;
}
}
}
return isPermitted;
}
- DelegatingSubject.isPermitted
public boolean isPermitted(String permission) {
//是否有权限信息,有则验证
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
- DefaultSecurityManager.isPermitted
public boolean isPermitted(PrincipalCollection principals, String permissionString) {
//代理给authorizer验证
return this.authorizer.isPermitted(principals, permissionString);
}
- ModularRealmAuthorizer.isPermitted
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
//调用realm验证
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
- AuthorizingRealm.isPermitted
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
Collection<Permission> perms = getPermissions(info);
if (perms != null && !perms.isEmpty()) {
for (Permission perm : perms) {
//获取用户的权限和配置的权限进行比较
if (perm.implies(permission)) {
return true;
}
}
}
return false;
}
三、总结
- 权限验证更灵活的方式是使用aop和注解方式
- 除了权限验证还有一个角色验证,流程和权限验证类似,使用了UserFilter,
RolesAuthorizationFilter