Spring之Spring Security基本原理

前面博客所述,Spring Security是通过自定义的Filter对相关的URL进行权限控制,这些个filter组合起来通过两个过程对权限进行了控制,认证(authentication)授权(authorization)。认证是来识别当前用户是谁的过程,授权是判断当前用户是否有权限进行相关操作的过程。

认证(authentication)

认证的过程相对简单,基本都是判断当前正在操作的用户(Principal)和密码(credentials)是否匹配。对与简单登录的方式,就是去匹配用户名和密码;对于单点登录,可能就是去验证token是否有效了。获取到这些信息后,会包装到对象Authentication中。这个Authentication就是后续Filter决定页面跳转的依据。Authentication定义如下:

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
授权(authorization)

对于需要控制权限的URL,一般都要三部分信息:

  1. URL的pattern,即对哪些URL进行权限控制
  2. 设置权限,即最低需要什么权限才能访问这些URL
  3. 当前用户是什么权限
获取用户权限

前两点可以通过配置<security:intercept-url pattern="/abc/**" access="hasRole('USER')"/>指定,并被包装成SecurityMetadataSource对象,那么当前登录用户的权限从哪里获取?

Spring Security定义了如下接口, UserDetailsService用于获取UserDetail,而UserDetail里包含权限。

public interface UserDetailsService {
    /**
     * Locates the user based on the username. In the actual implementation, the search
     * may possibly be case sensitive, or case insensitive depending on how the
     * implementation instance is configured. In this case, the <code>UserDetails</code>
     * object that comes back may have a username that is of a different case than what
     * was actually requested...
     */
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
public interface UserDetails extends Serializable {
    /**
     * Returns the authorities granted to the user. Cannot return null.
     */
    Collection<? extends GrantedAuthority> getAuthorities();
    ......
}

具体的获取UserDetail的过程,要根据具体的业务进行处理,比如从数据库获取,或者从缓存中得到。获取之后包装为Authentication对象供后续Filter使用。

关键的filter

AbstractAuthenticationProcessingFilter
用于认证,其步骤如下:

  1. 从request获取相关用户信息(密码、token等)构造Authentication
  2. AuthenticationManager其中的AuthenticationProvider进行Authentication验证;Provider里可能会获取UserDetails(包含用户具有的权限)放入Authentication中;若验证通过,则返回该Authentication中;否则抛异常;
  3. 该Filter捕获到异常,则导航到相关error或login页面;若正常,则根据具体的代码设置,由后面的filter继续处理或直接访问到资源;
        // Authentication success. 上面第三步
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }

        successfulAuthentication(request, response, chain, authResult);

当然,这个filter并不是每个url都会去拦截的,只有满足一定条件的url才会去拦截。比如不需要权限控制的url就不会被拦截。

FilterSecurityInterceptor和ExceptionTranslationFilter
这两个filter用于授权。其在Spring Security的Filter Chain中处于很靠后的位置。
FilterSecurityInterceptor的关键代码如下:

        //获取Authentication
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));
            //这里抛出去的异常会由ExceptionTranslationFilter捕获
            throw accessDeniedException;
        }

ExceptionTranslationFilter关键代码如下:

       try {
            chain.doFilter(request, response);

            logger.debug("Chain processed normally");
        }
        catch (IOException ex) {
            throw ex;
        }
        catch (Exception ex) {
            // Try to extract a SpringSecurityException from the stacktrace
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            RuntimeException ase = (AuthenticationException) throwableAnalyzer
                    .getFirstThrowableOfType(AuthenticationException.class, causeChain);

            if (ase == null) {
                ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
                        AccessDeniedException.class, causeChain);
            }

            if (ase != null) {
                handleSpringSecurityException(request, response, chain, ase);
            }
            else {
            // Rethrow ServletExceptions and RuntimeExceptions as-is
                ........
            }
        }

ExceptionTranslationFilter会根据抛出的异常是AccessDeniedException还是AuthenticationException来判断导航页面。

在Spring Security所有的Filter中,只有两种filter是可以导航到页面的:XXXAuthenticationProcessingFilter和ExceptionTranslationFilter,因此在配置时,需要提供相关页面url给这两种filter。

常用的几个Filter

1. SecurityContextPersistenceFilter

该filter默认启用,作用是保存Authentication对象使后续的filter可以获得这个对象。对于同一个用户(session id相同),会从Session中取出用户的校验结果。若是第一次访问,则会新建SecurityContext放入SecurityContextHolder(该Holder实际上是用一个ThreadLocal来保存SecurityContext)待后续代码保存校验结果。

2. CasAuthenticationFilter

用于处理CAS service的token,对于用到单点登录的系统,这个filter会经常使用。

3. AnonymousAuthenticationFilter

默认启用。由上文可知,AbstractAuthenticationProcessingFilter中可以构造Authentication。那么不需要权限控制的url怎么去构造Authentication呢?AnonymousAuthenticationFilter就发挥了作用。该filter构造了一个pesudo Authentication提供给FilterSecurityInterceptor,从而使所有的url的处理流程统一。

基本步骤

flow.png

上图包括了最基本的验证流程,当然默认还有很多filter会起作用,图中并没有显示。其中AbstractAuthenticationProcessingFilter并不是必需的,其他的四个都会默认启用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335