自定义过滤器
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter{
/**
* 认证成功后会调用次方法
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
ServletResponse response) throws Exception {
// 获取登录信息
String user = (String) subject.getPrincipal();
System.out.println("登录账户:"+user);
Session session = subject.getSession();
session.setAttribute("msg", "自定义FormFilter传递的信息...");
return super.onLoginSuccess(token, subject, request, response);
}
}
public class RememberFormAuthenticationFilter extends FormAuthenticationFilter{
// @Autowired
// private SimpleCookie simpleCookie;
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
Session session = subject.getSession();
// 记住密码,没有登录isAuthenticated()肯定为false
System.out.println("subject.isAuthenticated():"+subject.isAuthenticated());
System.out.println("subject.isRemembered():"+subject.isRemembered());
System.out.println("session.getAttribute(\"msg\"):"+session.getAttribute("msg"));
if(!subject.isAuthenticated()
&&subject.isRemembered()
&&session.getAttribute("msg")==null){
System.out.println("记住的用户是:"+subject.getPrincipal());
session.setAttribute("msg", "remember中保存的信息");
}
return subject.isAuthenticated()||subject.isRemembered();
}
}
FormAuthenticationFilter继承结构
继承结构中各类作用
类: 作用
AbstractFilter : 加载FilterConfig的相关信息
NameableFilter: 定义每一个filter的名字
OncePerRequestFilter 保证客户端请求后该filter的doFilter只会执行一次
AdviceFilter 主要是对doFilterInternal做了更细致的处理
PathMatchingFilter 主要是对preHandle做进一步细化控制
AccessControlFilter 对onPreHandle方法做了进一步细化
AuthenticationFilter
AuthenticatingFilter 用来做认证的Filter
FormAuthenticationFiltershiro 用来具体的实现登录的Filter
这里要先说明,这样便于接下在的阅读,这个过滤器链,返回有false则被退出过滤器链,一直返回值为true则可以到达目标的controller控制层,也就是登陆的控制层,返回登陆界面,并不是登陆成功界面,只有未登录状态并且请求地址成功地址,请求方法为post,才可以在关键方法中返回为false,认证成功,并且成定向到成功地址
NameableFilter
public abstract class NameableFilter extends AbstractFilter implements Nameable {
/**
* The name of this filter, unique within an application.
*/
private String name;
/**
* Returns the filter's name.
* <p/>
* Unless overridden by calling the {@link #setName(String) setName(String)} method, this value defaults to the
* filter name as specified by the servlet container at start-up:
* <pre>
* this.name = {@link #getFilterConfig() getFilterConfig()}.{@link javax.servlet.FilterConfig#getFilterName() getName()};</pre>
*
* @return the filter name, or {@code null} if none available
* @see javax.servlet.GenericServlet#getServletName()
* @see javax.servlet.FilterConfig#getFilterName()
*/
protected String getName() {
if (this.name == null) {
FilterConfig config = getFilterConfig();
if (config != null) {
this.name = config.getFilterName();
}
}
return this.name;
}
/**
* Sets the filter's name.
* <p/>
* Unless overridden by calling this method, this value defaults to the filter name as specified by the
* servlet container at start-up:
* <pre>
* this.name = {@link #getFilterConfig() getFilterConfig()}.{@link javax.servlet.FilterConfig#getFilterName() getName()};</pre>
*
* @param name the name of the filter.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns a StringBuilder instance with the {@link #getName() name}, or if the name is {@code null}, just the
* {@code super.toStringBuilder()} instance.
*
* @return a StringBuilder instance to use for appending String data that will eventually be returned from a
* {@code toString()} invocation.
*/
protected StringBuilder toStringBuilder() {
String name = getName();
if (name == null) {
return super.toStringBuilder();
} else {
StringBuilder sb = new StringBuilder();
sb.append(name);
return sb;
}
}
}
NameableFilter有一个name属性,定义每一个filter的名字.在FilterChainManager 中会调用配置文件中的配置属性名字来为每一个filter命名以及为默认的filter命名,如authc.
OncePerRequestFilter
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName());
filterChain.doFilter(request, response);
} else //noinspection deprecation
if (/* added in 1.2: */ !isEnabled(request, response) ||
/* retain backwards compatibility: */ shouldNotFilter(request) ) {
log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.",
getName());
filterChain.doFilter(request, response);
} else {
// Do invoke this filter...
log.trace("Filter '{}' not yet executed. Executing now.", getName());
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);
}
}
}
当满足拦截条件的请求到来的时候执行的就是本方法中的OncePerRequestFilter 中的doFilter方法.
shiro是通过在request中设置一个该filter特定的属性值来保证该filter只会执行一次的,
就是enabled属性决定了是否执行本Filter:
doFilter的实质内容是在doFilterInternal方法中完成的。所以实质上是保证每一个filter的 doFilterInternal只会被执行一次。
例如在配置中配置路径 /login = authc,authc则只会执行authc中的doFilterInternal一次。doFilterInternal在shiro整个filter体系中的核心方法及实质入口。
这里的doFilterInternal实际上是个抽象方法,具体实现是在 AdviceFilter。
AdviceFilter
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(request, response, exception);
}
}
看到了preHandle和postHandle是不是很像SpringMVC中的拦截器
AdviceFilter对OncePerRequestFilter中的doFilterInternal做了一定程度的细化控制。
PathMatchingFilter
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
if (log.isTraceEnabled()) {
log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately.");
}
return true;
}
for (String path : this.appliedPaths.keySet()) {
// If the path does match, then pass on to the subclass implementation for specific checks
//(first match 'wins'):
if (pathsMatch(path, request)) {
log.trace("Current requestURI matches pattern '{}'. Determining filter chain execution...", path);
Object config = this.appliedPaths.get(path);
return isFilterChainContinued(request, response, path, config);
}
}
//no path matched, allow the request to go through:
return true;
}
preHandle中,若请求的路径非该filter中配置的拦截路径,则直接返回true进行下一个filter。若包含在此filter路径中,则会在isFilterChainContinued做一些控制。
private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
String path, Object pathConfig) throws Exception {
if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2
if (log.isTraceEnabled()) {
log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}]. " +
"Delegating to subclass implementation for 'onPreHandle' check.",
new Object[]{getName(), path, pathConfig});
}
//The filter is enabled for this specific request, so delegate to subclass implementations
//so they can decide if the request should continue through the chain or not:
return onPreHandle(request, response, pathConfig);
}
if (log.isTraceEnabled()) {
log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}]. " +
"The next element in the FilterChain will be called immediately.",
new Object[]{getName(), path, pathConfig});
}
//This filter is disabled for this specific request,
//return 'true' immediately to indicate that the filter will not process the request
//and let the request/response to continue through the filter chain:
return true;
}
AccessControlFilter
onPreHandle方法在PathMatchingFilter就简单的返回了true,在AccessControlFilter 中更细化的实现了。
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
这里使用的是“||”“或”,所以调用完isAccessAllowed得到返回值后,需要调用onAccessDenied得到返回值,如果其中有true则进入下一个过滤器,为false则退出过滤器链进行相关操作。
isAccessAllowed方法和onAccessDenied方法达到控制效果
(抽象方法,需要子类实现)
isAccessAllowed和onAccessDenied方法会影响到onPreHandle方法,而onPreHandle方法会影响到preHandle方法,而preHandle方法会达到控制filter链是否执行下去的效果。所以如果正在执行的filter中isAccessAllowed和onAccessDenied都返回false,则整个filter控制链都将结束,不会到达目标方法(客户端请求的接口),而是直接跳转到某个页面(由filter定义的,将会在authc中看到)
AuthenticationFilter
isAccessAllowed
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
这里返回的subjec.isAuthenticated()就是AuthenticatingFilter中isAccessAllowed的super.isAccessAllowed。
从subject中isAuthenticated判断是否认证
AuthenticatingFilter
isAccessAllowed
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(request, response) && isPermissive(mappedValue));
}
AuthenticatingFilter中isAccessAllowed的实现了AuthenticationFilter中的isAccessAllowed抽象方法。
AuthenticationFilter和AuthenticatingFilter认证的filter,在抽象类中AuthenticatingFilter实现了isAccessAllowed方法,该方法是用来判断用户是否已登录,若未登录再判断是否请求的是登录地址,是登录地址则放行,否则返回false终止filter链。
isAccessAllowed方法是用来判断用户是否已登录,若未登录再判断是否请求的是登录地址,是登录地址则放行,否则返回false终止filter链
isLoginRequest(request, response)是用来判断客户端请求的地址是否是登陆地址
FormAuthenticationFiltershiro
FormAuthenticationFiltershiro提供的登录的filter,如果用户未登录,即AuthenticatingFilter中的isAccessAllowed判断了用户未登录,则会调用onAccessDenied方法做用户登录操作。
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.");
}
//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;
}
}
前面讲了:
isLoginRequest(request, response):用来判断客户端请求的地址是否是登陆地址
isLoginSubmission(request, response):用来判断客户端请求是什么方法,post还是get
若用户请求的不是登录地址,则跳转到登录地址,并且返回false直接终止filter链。
若用户请求的是登录地址,请求方法是post请求则进行登录操作,由AuthenticatingFilter中提供的executeLogin方法执行。
否则直接通过继续执行filter链,并最终跳转到登录页面(因为用户请求的就是登录地址,若不是登录地址也会重定向到登录地址)
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 = getSubject(request, response);
subject.login(token);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
onLoginSuccess
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
ServletRequest request, ServletResponse response) throws Exception {
issueSuccessRedirect(request, response);
//we handled the success redirect directly, prevent the chain from continuing:
return false;
}
onLoginFailure
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
ServletRequest request, ServletResponse response) {
setFailureAttribute(request, e);
//login failed, let request continue back to the login page:
return true;
}
补充:
我们会自定义过滤器例如:
那么这些自定义的filter需要我们加入过滤器链中。
1.重写FormAuthenticationFilter中的onLoginSuccess
它的父类是
我们既然重写了,那么执行时会执行我们重写的方法。
那么重写它能有什么作用了?
例如我们可以重写onLoginSuccess来设置使用ajax异步提交
/**
*
* 当登录成功
*
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String requestType = ((HttpServletRequest)request).getHeader("X-Requested-With");
if (requestType==null){// 不是ajax请求
issueSuccessRedirect(request, response);
} else {
httpServletResponse.setCharacterEncoding("UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.println(JSONObject.toJSON(Resp.success()));
out.flush();
out.close();
}
return false;
}
根据判断在请求头中是否有X-Requested-With,而判断是否有XMLHttpRequest对象,可以进行ajax提交,这样写可以在ajax回调函数中成功或者失败以json输出。
2. 重写FormAuthenticationFilter中的isAccessAllowed
我们需要在登陆后,更改session内容,我们需要重写isAccessAllowed,在其中对session进行设置。
这里需要把重写了isAccessAllowed方法的自定义过滤器加入过滤器链中
3.实现rememberMe记住我功能不用特地的重写isAccessAllowed
但是必须配置为 user级别,authc级别的rememberMe没有效果
过滤器链与认证过程
不知道有没有人和我一样产生过疑问,虽然知道了重写这些接口以及这个过滤器链执行的流程,也知道了认证过程,那么他们之间有啥关系吗?
在FormAuthenticationFilter过滤器的onAccessDenied方法中有executeLogin是执行登陆的
进入executeLogin
这里进行了登陆和认证
在配置文件中
在注册SecurityManager时初始化了,进入
org.apache.shiro.web.mgt.DefaultWebSecurityManager查看realms属性,
所以:认证的方法以及后面的认证的流程注入了SecurityManager中,而过滤器链执行流程中,在FormAuthenticationFilter过滤器的onAccessDenied方法中有executeLogin是执行了登陆和认证,从SecurityManager获取了Security对象也初始化了Security的认证。