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用户认证以及授权
Spring-shiro源码陶冶-DefaultFilter的更多相关文章
- Spring-shiro源码陶冶-DelegatingFilterProxy和ShiroFilterFactoryBean
阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用 简单介绍 Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能 web.xml配置Shiro环 ...
- Shiro 源码分析
http://my.oschina.net/huangyong/blog/215153 Shiro 是一个非常优秀的开源项目,源码非常值得学习与研究. 我想尝试做一次 不一样 的源码分析:源码分析不再 ...
- Spring mybatis源码篇章-MybatisDAO文件解析(二)
前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(一) 默认加载mybatis主文件方式 XMLConfigBuilder ...
- Spring mybatis源码篇章-MybatisDAO文件解析(一)
前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-SqlSessionFactory 加载指定的mybatis主文件 Mybatis模板文件,其中的属性 ...
- Spring mybatis源码篇章-NodeHandler实现类具体解析保存Dynamic sql节点信息
前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource SqlNode接口类 publi ...
- Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource
前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(二) 首先了解下sql mapper的动态sql语法 具体的动态sql的 ...
- Spring mybatis源码学习指引目录
前言: 分析了很多方面的mybatis的源码以及与spring结合的源码,但是难免出现错综的现象,为了使源码陶冶更为有序化.清晰化,特作此随笔归纳下分析过的内容.博主也为mybatis官方提供过pul ...
- Springboot security cas源码陶冶-ExceptionTranslationFilter
拦截关键的两个异常,对异常进行处理.主要应用异常则跳转至cas服务端登录页面 ExceptionTranslationFilter#doFilter-逻辑入口 具体操作逻辑如下 public void ...
- shiro源码篇 - 疑问解答与系列总结,你值得拥有
前言 开心一刻 小明的朋友骨折了,小明去他家里看他.他老婆很细心的为他换药,敷药,然后出去买菜.小明满脸羡慕地说:你特么真幸福啊,你老婆对你那么好!朋友哭得稀里哗啦的说:兄弟你别说了,我幸福个锤子,就 ...
随机推荐
- C/C++中substr函数的应用(简单讲解)
substr(字符串,截取开始位置,截取长度) //返回截取的字 substr('Hello World',0,1) //返回结果为 'H' *从字符串第一个字符开始截取长度为1的字符串 subst ...
- 2017广东工业大学程序设竞赛B题占点游戏
Description 众所周知的是,TMK特别容易迟到,终于在TMK某次又迟到了之后,Maple怒了,Maple大喊一声:"我要跟你决一死战!"然后Maple就跟TMK玩起了一个 ...
- hdu_4869(费马小定理+快速幂)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4869 Turn the pokers Time Limit: 2000/1000 MS (Java/O ...
- python数据类型(一)
1.数据类型 python中数有四种类型:整数.长整数.浮点数和复数. 整数, 如 1 长整数 是比较大的整数 浮点数 如 1.23.3E-2 复数 如 1 + 2j. 1.1 + 2.2j 2. 自 ...
- JS——判断一个对象是否为空
判断一个对象是否为空对象,本文给出三种判断方法: 1.最常见的思路,for...in...遍历属性,为真则为"非空数组":否则为"空数组" 2.通过JSON自带 ...
- Linux中ctrl+z 、ctrl+c、 ctrl+d区别
ctrl+c,ctrl+d,ctrl+z在linux程序中意义和区别 ctrl+c和ctrl+z都是中断命令,但是他们的作用却不一样. ctrl+c是强制中断程序的执行,,进程已经终止. ct ...
- 图像变换之Census变换
图像的Census变换 Census变换属于非参数图像变换的一种,它能够较好地检测出图像中的局部结构特征,如边缘.角点特征等.传统Census变换的基本思想是:在图像区域定义一个矩形窗口,用这个矩形窗 ...
- 数据库连接池(c3p0)
(一)问题的提出: 在使用开发基于数据库的web程序时,传统的数据库使用模式按照以下步骤: 在程序中建立数据库连接 进行sql操作 断开数据库连接 但是,这种模式存在着移动的问题: 传统连接模式每次向 ...
- 经典CSS坑:如何完美实现垂直水平居中?
经典CSS坑:如何完美实现垂直水平居中? 踩了各种坑之后总结出的方法,开门见山,直接上代码和效果图.希望对读者有所帮助,后续如果有更好的方法,我也会持续更新 第一种方法:transform(个人认为最 ...
- J.U.C FutureTask之源码解析
通过直接继承Thread, 实现Runnable接口来创建线程.但这两种方式都有一种缺陷:在执行完任务之后无法获得执行结果. 如果需要获得执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果, ...