阅读源码有助于陶冶情操,本文旨在简单的分析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类的继承关系

大致采用了模板模式的设计模式

  1. 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);
    }
    }
    }
  2. 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);
    }
    }
  3. 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;
    }
  4. 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);
    }
  5. AuthenticationFilter extends AccessControlFilter-用户认证Filter抽象类

    该抽象类只复写了其中的一个条件方法isAccessAllowed()

    	//判断当前对象是否已通过认证,Subject是接口对象类
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    Subject subject = getSubject(request, response);
    return subject.isAuthenticated();
    }
  6. 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方法

  1. [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;
    }
  2. [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;
    }
  3. [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);
    }
  4. [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;
    }
  5. [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;
    }
    ```
  6. [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;
    }
  7. [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);
    }
  8. [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);
    }
  9. [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();
    }
  10. [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抽象类

  1. [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的更多相关文章

  1. Spring-shiro源码陶冶-DelegatingFilterProxy和ShiroFilterFactoryBean

    阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用 简单介绍 Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能 web.xml配置Shiro环 ...

  2. Shiro 源码分析

    http://my.oschina.net/huangyong/blog/215153 Shiro 是一个非常优秀的开源项目,源码非常值得学习与研究. 我想尝试做一次 不一样 的源码分析:源码分析不再 ...

  3. Spring mybatis源码篇章-MybatisDAO文件解析(二)

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(一) 默认加载mybatis主文件方式 XMLConfigBuilder ...

  4. Spring mybatis源码篇章-MybatisDAO文件解析(一)

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-SqlSessionFactory 加载指定的mybatis主文件 Mybatis模板文件,其中的属性 ...

  5. Spring mybatis源码篇章-NodeHandler实现类具体解析保存Dynamic sql节点信息

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource SqlNode接口类 publi ...

  6. Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(二) 首先了解下sql mapper的动态sql语法 具体的动态sql的 ...

  7. Spring mybatis源码学习指引目录

    前言: 分析了很多方面的mybatis的源码以及与spring结合的源码,但是难免出现错综的现象,为了使源码陶冶更为有序化.清晰化,特作此随笔归纳下分析过的内容.博主也为mybatis官方提供过pul ...

  8. Springboot security cas源码陶冶-ExceptionTranslationFilter

    拦截关键的两个异常,对异常进行处理.主要应用异常则跳转至cas服务端登录页面 ExceptionTranslationFilter#doFilter-逻辑入口 具体操作逻辑如下 public void ...

  9. shiro源码篇 - 疑问解答与系列总结,你值得拥有

    前言 开心一刻 小明的朋友骨折了,小明去他家里看他.他老婆很细心的为他换药,敷药,然后出去买菜.小明满脸羡慕地说:你特么真幸福啊,你老婆对你那么好!朋友哭得稀里哗啦的说:兄弟你别说了,我幸福个锤子,就 ...

随机推荐

  1. hihoCoder #1082 : 然而沼跃鱼早就看穿了一切(字符串处理)

    #1082 : 然而沼跃鱼早就看穿了一切 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 fjxmlhx每天都在被沼跃鱼刷屏,因此他急切的找到了你希望你写一个程序屏蔽所有句 ...

  2. BZOJ 1316: 树上的询问 (点分治+set)

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1316 因为只要求存在某条路径长度为K,所以点分,然后用set判断差值是否在set中就可以了. ...

  3. C#的改进特性

    1.初始器 当你新建一个对象实例的时候,是否遇到下面这种情况: class a = new class(); a.item1 = "; a.item2 = "; 或者写一个构造函数 ...

  4. 使用layui在规定的期限内选择日期

    这几天碰到了layui中的日期与时间这个,本以为很简单的,可是又一个需求是这样的,有两个日期选择框,第一个日期选择框要求最小日期不得小于当前日期,第二个日期选择框要求最小日期为第一个日期选择框的选中日 ...

  5. JS_全

    <script src="jquery-1.9.1.js" type="text/javascript"></script> <s ...

  6. Java数据持久层框架 MyBatis之背景知识二

    对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...

  7. JavaScript的三种类型检测typeof , instanceof , toString比较

    1.typeof typeof是js的一个操作符,在类型检测中,几乎没有任何用处. typeof 返回一个表达式的数据类型的字符串,返回结果为javascript中的基本数据类型,包括:number. ...

  8. P1345 [USACO5.4]奶牛的电信Telecowmunication

    P1345 [USACO5.4]奶牛的电信Telecowmunication 题目描述 农夫约翰的奶牛们喜欢通过电邮保持联系,于是她们建立了一个奶牛电脑网络,以便互相交流.这些机器用如下的方式发送电邮 ...

  9. 2018年web前端学习路线图

    前端的的技术一直在变化,更新和变革,现在基本是三驾马车(vue,angualr,react)主导整个前端框架,但是无论对于新人或者有经验的程序员,这些知识在必须掌握 前端必会技能 上图罗列了整个前端的 ...

  10. AI_神经网络监督学习

    神经网络的神奇之处在哪? 所有神经网络创造出来的价值,都是由一种机器学习,称之为监督学习, 下面这些例子神经网络效果拔群,通过深度学习获利最多的是在线广告 技术的进步来源于计算机视觉和深度学习 例如: ...