如何使用shiro进行登陆验证,这个比较熟悉,看下面的代码,是不是很熟悉呢?
@Bean
public ShiroFilterFactoryBean shiroFilter2(final SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new HashMap<String, String>();
filterChainDefinitionMap.put("/logout", "logout");
// authc:所有url都必须认证通过才可以访问;
// anon:所有url都都可以匿名访问,
// 先配置anon再配置authc。
filterChainDefinitionMap.put("/getUser", "authc");
shiroFilterFactoryBean.setLoginUrl("/login.html");
shiroFilterFactoryBean.setSuccessUrl("/success.html");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
那么“authc”,到底是如何进行登陆验证的呢?
原来shiro有一个默认的过滤器映射表DefaultFilter
public enum DefaultFilter {
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
}
那么今天的主角就要登场了-----FormAuthenticationFilter
shiro框架已经为我们实现了许多复杂的过滤器功能,FormAuthenticationFilter也是在这基础之上而来的。
下面先来看一下FormAuthenticationFilter的继承类图
接下来逐一学习。
NameableFilter
给filter起名字,如果没有设置默认就是FilterName
OncePerRequestFilter
可以防止多次执行filter,每一次请求只会走一次过滤器链,并提供enabled属性,表示是否开启过滤器实例,默认是true,如果不想让该过滤器工作,可以设置为false.代码如下OncePerRequestFilter.doFilter
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
// 每次请求只执行一次filter,执行过后就设置request的属性,属性名是FilterName
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
filterChain.doFilter(request, response);
} else //该过滤器如果不启用,直接跳过
if (/* added in 1.2: */ !isEnabled(request, response) ||
/* retain backwards compatibility: */ shouldNotFilter(request) ) {
getName());
filterChain.doFilter(request, response);
} else {
// 每次请求只执行一次filter,执行过后就设置request的属性,属性名是FilterName
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
// 最终由子类实现
doFilterInternal(request, response, filterChain);
} finally {
// Once the request has finished, we're done and we don't
// need to mark as 'already filtered' any more.
//一旦请求结束,就可以清除该属性。
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
AdviceFilter
AdviceFilter提供了AOP风格的支持。
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception
protected void postHandle(ServletRequest request, ServletResponse response) throws Exception
public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception)throws Exception
preHandle:前置处理,在过滤器执行之前执行,如果返回true则继续执行过滤器链,否则终止过滤器链的执行,可以用来预处理(身份验证,授权等)。
PostHandle:后处理,如果过滤器链抛出异常则跳过执行。
AfterCompletion:完成时处理,不管过滤器链是否抛出异常都会执行,可以用来释放资源。
源码如下:AdviceFilter.doFilterInternal
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
Exception exception = null;
try {
// 前置预处理,例如登陆验证,权限验证等,由子类实现。
boolean continueChain = preHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]");
}
// 前置预处理是否通过,通过则继续过滤器链,否则终止过滤器链,由子类实现。
if (continueChain) {
executeChain(request, response, chain);
}
//后处理,如果过滤器链抛出异常,则跳过执行
postHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Successfully invoked postHandle method");
}
} catch (Exception e) {
exception = e;
} finally {
// cleanUp会调用afterCompletion,用于释放资源,afterCompletion由子类实现
cleanup(request, response, exception);
}
}
PathMatchingFilter
PathMathingFilter implements PathConfigProcessor{
// 将请求的path和对应的config配置给filter,例如 “/deleteUser roles[admin]”
// 其中path="deleteUser" roles对应filter,而config=admin
public Filter processPathConfig(String path, String config) {
String[] values = null;
if (config != null) {
values = split(config);
}
this.appliedPaths.put(path, values);
return this;
}
}
详细内容请见下一篇。
AccessControlFilter
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
// 是否允许访问,如果允许请求访问返回true,否则需要通过onAccessDenied处理请求
abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
// 调用重载函数onAccessDenied,当访问拒绝,来处理请求。
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return onAccessDenied(request, response);
}
AuthenticationFilter
// 通过验证用户是否登陆,来判断是否允许访问。
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
AuthenticatingFilter
// 重写父类方法,验证登陆或者(登陆请求并且mappedvalue包含PERMISSIVE限定符)
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(request, response) && isPermissive(mappedValue));
}
FormAuthenticationFilter
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 是否是登陆请求,通过和LoginUrl对比
if (isLoginRequest(request, response)) {
// 是否是post请求
if (isLoginSubmission(request, response)) {
// 执行登陆
return executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
//allow them to see the login page ;)
return true;
}
} else {
// 保存当前请求的url,并重定向到登陆页面,登陆成功后,会重新请求该url
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
如果是登陆请求,执行登陆
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
// 创建token
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);
// 登陆成功,跳转
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
// 登陆失败
return onLoginFailure(token, e, request, response);
}
}
如果登陆成功则重定向 这里分两种情况1.如果session中保存了request请求,说明在登陆之前已经有请求url,只不过被拦截了,所以需要重新请求该url.2.否则直接重定向到successUrl
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
ServletRequest request, ServletResponse response) throws Exception {
// 登陆成功重定向 这里分两种情况1.如果session中保存了request请求,说明在登陆之前已经有请
//求url,只不过被拦截了,所以需要重新请求该url.2.否则直接重定向到successUrl
issueSuccessRedirect(request, response);
//we handled the success redirect directly, prevent the chain from continuing:
return false;
}