Spring-shiro源码陶冶-DefaultFilter
阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用
简单介绍
Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能
Apache Shiro自带的默认Filter
直接查看DefaultFilter类便可以一目了然,具体代码如下
public enum DefaultFilter {
//从此处可看,shiro默认的filter有11个
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);
private final Class<? extends Filter> filterClass;
//存放的是相关的filter类
private DefaultFilter(Class<? extends Filter> filterClass) {
this.filterClass = filterClass;
}
public Filter newInstance() {
return (Filter) ClassUtils.newInstance(this.filterClass);
}
public Class<? extends Filter> getFilterClass() {
return this.filterClass;
}
//创建map集合
public static Map<String, Filter> createInstanceMap(FilterConfig config) {
Map<String, Filter> filters = new LinkedHashMap<String, Filter>(values().length);
for (DefaultFilter defaultFilter : values()) {
Filter filter = defaultFilter.newInstance();
if (config != null) {
try {
filter.init(config);
} catch (ServletException e) {
String msg = "Unable to correctly init default filter instance of type " +
filter.getClass().getName();
throw new IllegalStateException(msg, e);
}
}
filters.put(defaultFilter.name(), filter);
}
return filters;
}
}
从代码中可以查看得知拥有的默认Filter有11个,决定分类逐个分析他们,在此之前先观察下这几个Filter类的共性
默认Filter类的继承关系
大致采用了模板模式的设计模式
-
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 (!isEnabled(request, response) || 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); } } }
-
AdviceFilter抽象类-AOP风格,类似
@Around
注解//复写第一级的父类方法 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) { //在前处理返回true的情况下让过滤链往下走 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); } }
-
PathMatchingFilter抽象类-url匹配Filter类
//复写第二级的父类方法 protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { //如果没有匹配的url集合则表示所有的url都不拦截处理,即返回true 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; } //根据request中的url匹配 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); //此处的config一般指代permission、roles、port的标识集合 Object config = this.appliedPaths.get(path); //其实是调用onPreHandle(request, response, pathConfig)方法,此方法默认是返回true,可供子类复写 return isFilterChainContinued(request, response, path, config); } } //no path matched, allow the request to go through:没有path匹配则放行 return true; }
-
AccessControlFilter extends PathMatchingFilter-资源访问控制抽象类,认证以及授权等Filter类均需继承此类
//复写父类onPreHandler方法 public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { //优先判断isAccessAllowed()方法,再判断onAccessDenied()方法,其中的mappedValue参数为pathConfig,类同permission、roles、port的标识集合 //onAccessDenied()方法true代表放行,false则包装response,自行转发数据 return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); }
-
AuthenticationFilter extends AccessControlFilter-用户认证Filter抽象类
该抽象类只复写了其中的一个条件方法isAccessAllowed()//判断当前对象是否已通过认证,Subject是接口对象类 protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { Subject subject = getSubject(request, response); return subject.isAuthenticated(); }
-
AuthenticatingFilter extends AuthenticationFilter-用户认证Filter补充抽象类,表明该类的实现类针对login请求
//复写父类,额外增加通过率 @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { //在父类的基础上又额外添加了返回true的条件:1.不是login请求 2.pathConfig含有permissive字段 return super.isAccessAllowed(request, response, mappedValue) || (!isLoginRequest(request, response) && isPermissive(mappedValue)); }
另外此类涉及到了用户Token创建,可见如下代码清单
//登录验证 protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { //抽象类,供子类去实现完成创建token,且不允许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); //此处的login方法其实调用了token认证接口 subject.login(token); //login方法无异常,则执行登录成功操作,供子类实现 return onLoginSuccess(token, subject, request, response); } catch (AuthenticationException e) { //login方法有异常,则执行登录失败操作,此处也供子类实现 return onLoginFailure(token, e, request, response); } }
默认的这些类大多都是继承AdviceFilter类或者PathMatchingFilter类,下面将从这两方面对shiro默认的Filter进行归纳
继承PathMatchingFilter抽象类
即需要复写其主要方法onPreHandle方法
-
[anno]AnonymousFilter extends PathMatchingFilter-可见对此filter对应的url全部都放行
@Override protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) { // Always return true since we allow access to anyone return true; }
-
[authc]FormAuthenticationFilter extends AuthenticatingFilter-针对表单提交的认证Filter类
- 设定了表单提交时帐号与密码的参数名,默认为username、password,可通过
<property name="usernameParam/passwordParam">
设置 - 复写了另外一个通过判断条件,即onAccessDenied()方法
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { //首先判断必须为login请求 if (isLoginRequest(request, response)) { //判断必须是post类型的表单提交 if (isLoginSubmission(request, response)) { if (log.isTraceEnabled()) { log.trace("Login submission detected. Attempting to execute login."); } //调用super.executeLogin()方法来进行校验 return executeLogin(request, response); } else { if (log.isTraceEnabled()) { log.trace("Login page view."); } //allow them to see the login page ;) //对login页面请求不拦截 return true; } } else { if (log.isTraceEnabled()) { log.trace("Attempting to access a path which requires authentication. Forwarding to the " + "Authentication url [" + getLoginUrl() + "]"); } //否则跳转至login页面 saveRequestAndRedirectToLogin(request, response); return false; } }
- 复写了createToken()方法
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { String username = getUsername(request); String password = getPassword(request); //主要创建UserpasswordToken对象 return createToken(username, password, request, response); }
- 复写了其中的onLoginSuccess和onLoginFailure()方法
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; } protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { //直接返回true,让过滤链处理 setFailureAttribute(request, e); //login failed, let request continue back to the login page: return true; }
- 设定了表单提交时帐号与密码的参数名,默认为username、password,可通过
-
[authcBasic]BasicHttpAuthenticationFilter extends AuthenticatingFilter-基于http头的认证Filter
- 同样复写了
onAccessDenied
方法
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { boolean loggedIn = false; //false by default or we wouldn't be in this method //request header含有Authorization字段 if (isLoginAttempt(request, response)) { //调用super.executeLogin方法 loggedIn = executeLogin(request, response); } if (!loggedIn) { //登录失败则发送401状态错误以及设置WWW-Authenticate的信息`Basic realm="application"`到response的header sendChallenge(request, response); } return loggedIn; }
- 同样复写了createToken()方法
//主要从request请求的头部中的Authorization获取username/password信息 protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { String authorizationHeader = getAuthzHeader(request); //无Authorization头部信息则默认验证失败 if (authorizationHeader == null || authorizationHeader.length() == 0) { // Create an empty authentication token since there is no // Authorization header. return createToken("", "", request, response); } if (log.isDebugEnabled()) { log.debug("Attempting to execute login with headers [" + authorizationHeader + "]"); } //如果存在应该为(scheme和Base64加密过的username:password组合) 两者以空格分割 String[] prinCred = getPrincipalsAndCredentials(authorizationHeader, request); if (prinCred == null || prinCred.length < 2) { // Create an authentication token with an empty password, // since one hasn't been provided in the request. String username = prinCred == null || prinCred.length == 0 ? "" : prinCred[0]; return createToken(username, "", request, response); } String username = prinCred[0]; String password = prinCred[1]; return createToken(username, password, request, response); }
- 同样复写了
-
[noSessionCreation]NoSessionCreationFilter extends PathMatchingFilter-不允许创建session Filter类,其会在新Subject创建session时报错,原有的则不报错
@Override protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { request.setAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED, Boolean.FALSE); return true; }
-
[perms]PermissionsAuthorizationFilter extends AuthorizationFilter-权限认证Filter类,其默认是获取pathConfig中字段并进行比对,一般来说我们需要自定义permissons集合
```java public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = getSubject(request, response); //pathConfig 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; } ```
-
[port]PortFilter extends AuthorizationFilter-请求对特定的端口放行,否则则跳转指定端口的url 调用示例:port[8008]
- 复写父类的isAccessAllowed()方法
//对应的url必须是指定的端口,否则调用onAccessDenied() protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { int requiredPort = toPort(mappedValue); int requestPort = request.getServerPort(); return requiredPort == requestPort; }
- 复写父类的onAccessDenied()
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { //just redirect to the specified port:默认为80端口 int port = toPort(mappedValue); String scheme = getScheme(request.getScheme(), port); StringBuilder sb = new StringBuilder(); sb.append(scheme).append("://"); sb.append(request.getServerName()); if (port != DEFAULT_HTTP_PORT && port != SslFilter.DEFAULT_HTTPS_PORT) { sb.append(":"); sb.append(port); } if (request instanceof HttpServletRequest) { sb.append(WebUtils.toHttp(request).getRequestURI()); String query = WebUtils.toHttp(request).getQueryString(); if (query != null) { sb.append("?").append(query); } } WebUtils.issueRedirect(request, response, sb.toString()); return false; }
-
[rest]HttpMethodPermissionFilter extends PermissionsAuthorizationFilter-rest风格的请求方法权限Filter类 调用示例:rest[perm1,perm2]-->程序封装:rest[perm1:get,perm:get]
@Override public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { String[] perms = (String[]) mappedValue; // append the http action to the end of the permissions and then back to super String action = getHttpMethodAction(request); //对pathConfig中的内容进行封装,封装为perm:action的组合 String[] resolvedPerms = buildPermissions(perms, action); return super.isAccessAllowed(request, response, resolvedPerms); }
-
[roles]RolesAuthorizationFilter extends AuthorizationFilter-针对roles的权限认证,调用示例:roles[role1,role2]即对指定url需要role1和role2的权限
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { 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); //需要满足指定的所有roles才返回true return subject.hasAllRoles(roles); }
-
[ssl]SslFilter extends PortFilter-针对https协议的指定端口Filter类,同PortFilter 调用示例:ssl[443]
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { //多个必须为https协议的条件判断 return super.isAccessAllowed(request, response, mappedValue) && request.isSecure(); }
-
[user]UserFilter extends AccessControlFilter-用户认证Filter类
- 复写isAccessAllowed()方法
//对login页面请求以及当前用户已存在不拦截,类似于session存在用户属性 protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { if (isLoginRequest(request, response)) { return true; } else { Subject subject = getSubject(request, response); // If principal is not null, then the user is known and should be allowed access. return subject.getPrincipal() != null; } }
- 复写onAccessDenied()方法
//直接跳转至login登录页面 protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { saveRequestAndRedirectToLogin(request, response); return false; }
继承AdviceFilter抽象类
- [logout]LogoutFilter extends AdviceFilter-登录退出Filter类
只复写了父类的一个方法preHandler(),代码清单如下@Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { Subject subject = getSubject(request, response); //默认的退出回调地址为"/" String redirectUrl = getRedirectUrl(request, response, subject); //try/catch added for SHIRO-298: try { subject.logout(); } catch (SessionException ise) { log.debug("Encountered session exception during logout. This can generally safely be ignored.", ise); } //跳转至指定路径 issueRedirect(request, response, redirectUrl); return false; }
下节预告
Spring-shiro源码陶冶-AuthorizingRealm用户认证以及授权