Spring Security是一个单独的项目,它可以无缝的和Spring MVC集成。Spring Security提供会特性保护web应用来自恶意的攻击。具体可以参看Spring Security的参考文档的章节,例如“CSRF protection”,“Security Response Headers”和“Spring MVC Integration”.注意:使用Spring Security去保护应用不是针对所有的特性。例如CSRF保护可以通过简单的添加CsrfFilter以及CsrfRequestDataValueProcessor来保护你的应用。可以查看Spring MVC Showcase做为一个例子。

另外一个选择是使用专门针对Web安全的框架,HDIV就是这样的一个框架并且可以和Spring MVC集成。

其实上面就是Spring官网对于Spring关于Web Security的介绍。如果就这样我觉得东西太少了。但是如果叫我详细的来解释一下Spring Security那么东西又太多了。那么我就针对一下Spring Security的里面的概念,以及它的入口还有它的源码来给大家讲一下Spring Security是如何做到Web Security的。

1、Spring Security Overview

下面是Spring Security官方的一个关于它的架构图。

springsecurity 集成 satoken_web

下面来介绍一下主要的概念: AbstractSecurityInterceptor:是一个抽象类,实现安全对象的安全拦截,抽象了基于HTTP资源与方法资源的安全验证。 AuthenticationManager:处理验证请求,相当于用户登录验证。 AccessDecisionManager:访问投票管理器。相当于用户权限验证。 SecurityMetadataSource:需要来实现,用于储存并且能够确定ConfigAttribute(用户配置的属性)可以适用于一个给定的安全对象调用。 RunAsManager:创建一个临时的Authentication(验证)对象仅用来验证当前安全对象调用。 AfterInvocationManager:评论安全对象调用返回的对象,能够修改返回的对象或者throw an AccessDeniedException. MethodSecurityInterceptor:提供基于方法级别的验证。 AspectJMethodSecurityInterceptor:AspectJ JoinPoint安全拦截,包装JoinPoint到MethodInvocation代理使得它能够兼容security只能够支持MethodInvocation基础架构类。 FilterSecurityInterceptor:通过Filter的形式执行HTTP资源的安全处理。

2、Spring Security Entrance

这次我们主要是讲Spring Security关于HTTP资源的安全处理,根据官方文档,使用Spring Security第一件事就是需要在web.xml文件中添加下面的filter描述:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

这个配置会提供一个钩子到Spring Security web基础设施中。DelegatingFilterProxy是Spring Framework类用于代理到一个Filter实现,它是被声明在你的application context的Spring bean中。在这种情况下,这个bean会被命名为”springSecurityFilterChain”,它是一个被命名空间创建内部的基础设施bean用于处理web安全。注意:你不能够使用你自定义的bean名称。一旦你添加这个到你的web.xml文件中,你就可以开始修改你的application context文件了。Web安全服务配置使用元素。

3、Spring Security Process

首先展示一下Spring Security处理时序图:

springsecurity 集成 satoken_web_02

其实里面最重要还是beforeInvocation(),finallyInvocation(),afterInvocation().下面我们来具体分析一下这三个方法的代码。大家注意结合Spring Security Overview中的概念对比来看,就会清晰的认识到这个组件的真实意义了。

1)beforeInvocation

主要包括用户的验证与授权。

protected InterceptorStatusToken beforeInvocation(Object object) {
    Assert.notNull(object, "Object was null");
    final boolean debug = logger.isDebugEnabled();

    if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
        throw new IllegalArgumentException(
                "Security invocation attempted for object "
                        + object.getClass().getName()
                        + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
                        + getSecureObjectClass());
    }

    // 1、获取用户资源(SecurityMetadataSource)
    Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
            .getAttributes(object);

    if (attributes == null || attributes.isEmpty()) {
        if (rejectPublicInvocations) {
            throw new IllegalArgumentException(
                    "Secure object invocation "
                            + object
                            + " was denied as public invocations are not allowed via this interceptor. "
                            + "This indicates a configuration error because the "
                            + "rejectPublicInvocations property is set to 'true'");
        }

        if (debug) {
            logger.debug("Public object - authentication not attempted");
        }

        publishEvent(new PublicInvocationEvent(object));

        return null; // no further work post-invocation
    }

    if (debug) {
        logger.debug("Secure object: " + object + "; Attributes: " + attributes);
    }

    if (SecurityContextHolder.getContext().getAuthentication() == null) {
        credentialsNotFound(messages.getMessage(
                "AbstractSecurityInterceptor.authenticationNotFound",
                "An Authentication object was not found in the SecurityContext"),
                object, attributes);
    }

    // 2、用户登录验证(AuthenticationManager)
    Authentication authenticated = authenticateIfRequired();

    // Attempt authorization
    try {
        // 3、用户授权验证(AccessDecisionManager)
        this.accessDecisionManager.decide(authenticated, object, attributes);
    }
    catch (AccessDeniedException accessDeniedException) {
        publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                accessDeniedException));

        throw accessDeniedException;
    }

    if (debug) {
        logger.debug("Authorization successful");
    }

    if (publishAuthorizationSuccess) {
        publishEvent(new AuthorizedEvent(object, attributes, authenticated));
    }

    // 4、试图运行一个不同的用户(RunAsManager)
    Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
            attributes);

    if (runAs == null) {
        if (debug) {
            logger.debug("RunAsManager did not change Authentication object");
        }

        // no further work post-invocation
        return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
                attributes, object);
    }
    else {
        if (debug) {
            logger.debug("Switching to RunAs Authentication: " + runAs);
        }

        SecurityContext origCtx = SecurityContextHolder.getContext();
        SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
        SecurityContextHolder.getContext().setAuthentication(runAs);

        // need to revert to token.Authenticated post-invocation
        return new InterceptorStatusToken(origCtx, true, attributes, object);
    }
}

2)finallyInvocation

设置用户的SecurityContext信息。

protected void finallyInvocation(InterceptorStatusToken token) {
    if (token != null && token.isContextHolderRefreshRequired()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Reverting to original Authentication: " + token.getSecurityContext().getAuthentication());
        }
        // 设置SecurityContext信息
        SecurityContextHolder.setContext(token.getSecurityContext());
    }
}

3)afterInvocation

授权的后置处理,评论安全对象调用返回的对象,能够修改返回的对象或者throw an AccessDeniedException.

protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
    if (token == null) {
        // public object
        return returnedObject;
    }

    finallyInvocation(token); // continue to clean in this method for passivity

    if (afterInvocationManager != null) {
        // Attempt after invocation handling
        try {
            // 后置处理(AfterInvocationManager)
            returnedObject = afterInvocationManager.decide(token.getSecurityContext().getAuthentication(),
                    token.getSecureObject(),
                    token.getAttributes(), returnedObject);
        }
        catch (AccessDeniedException accessDeniedException) {
            AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token
                    .getAttributes(), token.getSecurityContext().getAuthentication(), accessDeniedException);
            publishEvent(event);

            throw accessDeniedException;
        }
    }

    return returnedObject;
}

通过这三个主要的方法是不是把Spring Security当中的组件都组合起来了。就能够达到用户验证与用户授权了。

官网地址:spring-framework-reference-4.2.6.RELEASE