shiro内置了许多过滤器用来控制认证授权
anon : org.apache.shiro.web.filter.authc.AnonymousFilter
authc : org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic : org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms : org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port : org.apache.shiro.web.filter.authz.PortFilter
rest : org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles : org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl : org.apache.shiro.web.filter.authz.SslFilter
user : org.apache.shiro.web.filter.authc.UserFilter
其中认证是否已登录FormAuthenticationFilter
认证是否授权(角色、权限)RolesAuthorizationFilter,PermissionsAuthorizationFilter
(以下以RolesAuthorizationFilter为例)
核心都继承自AccessControlFilter
左边是认证登录,右边是认证授权。注意红框位置FormAuthenticationFilter 和 RolesAuthorizationFilter 都通过了过渡类继承自AccessControlFilter
简单介绍下这个类的几个方法
onPreHandle:判断认证是否通过,以及后续处理上代码:
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
isAccessAllowed:判断认证是否通过(FormAuthenticationFilter中是认证是否已登录,RolesAuthorizationFilter是认证是否已授权)
onAccessDenied:认证失败的后续处理
isLoginRequest:判断是否是登录请求,登录地址在配置文件中已配:
<property name="loginUrl" value="/login"></property>
<!--登录成功后跳转的默认地址-->
<property name="successUrl" value="/main"></property>
<!--如果您请求的资源不再您的权限范围,则跳转到/400请求地址 -->
<property name="unauthorizedUrl" value="400"></property>
saveRequest:保存当前请求
redirectToLogin:重定向到登录页面
saveRequestAndRedirectToLogin:保存当前请求并重定向到登录页面
认证登录流程按照继承关系:
AccessControlFilter
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
AuthenticatingFilter(判断是否认证通过,将调用父类的isAccessAllowed)
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(request, response) && isPermissive(mappedValue));
}
AuthenticationFilter(在这里真正判断是否已登录)
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
登录的话,直接跳转,如果未登录:
FormAuthenticationFilter
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;
}
}
将走realm中的doGetAuthenticationInfo方法。
ps:执行executeLogin方法过程中会生成AuthenticationToken,具体代码:
ServletRequest request, ServletResponse response) {
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
return createToken(username, password, rememberMe, host);
}
登录成功或失败会走onLoginSuccess或onLoginFailure
如果不是登录请求则保存当前请求跳转至登录页面,如果想封装ajax请求的shiro处理,可自定义自己的FormAuthenticationFilter
认证授权流程按照继承关系
AccessControlFilter
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
}
Set<String> roles = CollectionUtils.asSet(rolesArray);
return subject.hasAllRoles(roles);
}
AuthorizationFilter(认证角色未通过后的处理)
Subject subject = getSubject(request, response);
// If the subject isn't identified, redirect to login URL
if (subject.getPrincipal() == null) {
saveRequestAndRedirectToLogin(request, response);
} else {
// If subject is known but not authorized, redirect to the unauthorized URL if there is one
// If no unauthorized URL is specified, just return an unauthorized HTTP status code
String unauthorizedUrl = getUnauthorizedUrl();
//SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit:
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
return false;
}
如果未登录,保存当前请求跳转至登录页面,否则判断是否有配置认证权限未通过须要跳转的请求,如果配置了则跳转,否则抛出错误码SC_UNAUTHORIZED(401),可在web.xml中捕获该错误码自己定义要跳转的页面。
这是我理解的shiro认证授权策略,其中认证授权前后所要做的处理都可以通过继承相应类重现方法来达到自定义效果,无需在contrller层写多余的业务逻辑代码,shiro的这些接口和相关配置文件都可以帮你搞定!
最后需要在shiro配置文件中配置
02 < property name = "securityManager" ref = "securityManager" />
03 <!-- 未登录状态下访问 authc 则进入 loginUrl 配置的路径 -->
04 < property name = "loginUrl" value = "/login" ></ property >
05 <!-- 登录成功后跳转的默认地址 -->
06 < property name = "successUrl" value = "/main" ></ property >
07 <!-- 如果您请求的资源不再您的权限范围,则跳转到 / 400 请求地址 -->
08 < property name = "unauthorizedUrl" value = "400" ></ property >
09 <property name="filters">
10 <map>
11 <entry key="authc">
12 <bean class="com.luming.shiro.MyFormAuthenticationFilter"/>
13 </entry>
14 <entry key="roles">
15 <bean class="com.luming.shiro.MyRolesAuthorizationFilter"/>
16 </entry>
17 </map>
18 </property>
19 </ bean >
MyFormAuthenticationFilter继承自FormAuthenticationFilter
MyRolesAuthorizationFilter继承自RolesAuthorizationFilter
最后基于自身项目的shiro自定义认证授权还须要结合自定义的realm:shiro认证授权
因为JdbcRealm的doGetAuthenticationInfo定义了如何对用户提交的数据进行认证,doGetAuthorizationInfo定义了授予用户何种权限,
而FormAuthenticationFilter和RolesAuthorizationFilter主要是负责认证及授权的前后须要做的一些自定义操作,比如IP过滤等