spring-security

一 web.xml配置文件

  • 配置2个filter
<filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  • spring security是在DelegatingFilterProxy中进行处理
  • tomcat初始化DelegatingFilterProxy类对象
  • 从spring管理的bean中获取FilterChainProxy,赋值为Filter delegate
  • FilterChainProxy类型bean通过xml标签定义,解析后交给spring容器管理。

二 spring xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security-3.2.xsd">

    <http pattern="/" security="none"/>

    <http auto-config="false" use-expressions="true" entry-point-ref="ssoAuthenticationEntryPoint"
          security-context-repository-ref="myCookieSecurityContextRepository" create-session="stateless">
        <intercept-url pattern="/index.html" access="hasRole('ROLE_USER')"/>

        <!-- 管理员 -->
        <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>

        <custom-filter ref="ssoLogoutFilter" before="LOGOUT_FILTER"/>
        <access-denied-handler ref="accessDeniedHandler"/>

    </http>

    <authentication-manager alias="authenticationManager"/>
</beans:beans>
  • org.springframework.security.config.SecurityNamespaceHandler 注册xml标签解析函数
  • HttpSecurityBeanDefinitionParser http标签解析函数
  • 一个http标签解析成一个DefaultSecurityFilterChain类型过滤串,按匹配规则RequestMatcher requestMatcher对目标url执行一组List<Filter> filters过滤操作。
  • 所有的DefaultSecurityFilterChain用于初始化filterChainProxy属性List<SecurityFilterChain> filterChains。filterChainProxy对象交给spring容器管理
  • 第二个http标签初始化了一组filter,当有请求时依次调用各filter的doFilter()函数处理请求。

三 filter

3.1 SecurityContextPersistenceFilter

  • 实例化
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
        this.forceEagerSessionCreation = false;
        this.repo = repo;
    }
  • http标签配置security-context-repository-ref="myCookieSecurityContextRepository"为构造函数参数repo
  • 过滤处理函数
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if(request.getAttribute("__spring_security_scpf_applied") != null) {//只处理一次
            chain.doFilter(request, response);
        } else {
            boolean debug = this.logger.isDebugEnabled();
            request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
            if(this.forceEagerSessionCreation) {
                HttpSession session = request.getSession();
                if(debug && session.isNew()) {
                    this.logger.debug("Eagerly created session: " + session.getId());
                }
            }

            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
//从请求中获取用户认证信息,cookie或session中保存的认证信息
            SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
            boolean var13 = false;

            try {
                var13 = true;
//默认threadlocal方式保存变量,保存filter处理前的认证信息                SecurityContextHolder.setContext(contextBeforeChainExecution);
//执行后续filter处理
                chain.doFilter(holder.getRequest(), holder.getResponse());
                var13 = false;
            } finally {
                if(var13) {//异常发生
                    SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                    SecurityContextHolder.clearContext();
                    this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
                    request.removeAttribute("__spring_security_scpf_applied");
                    if(debug) {
                        this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
                    }

                }
            }

            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
            //清理缓存数据
            SecurityContextHolder.clearContext();
            //保存认证信息
            this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
//删除处理标记            request.removeAttribute("__spring_security_scpf_applied");
            if(debug) {
                this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
            }

        }
    }

3.2 WebAsyncManagerIntegrationFilter

  • 支持异步处理
  • 只调用一次处理
  • 过滤处理
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);//初始化并注册到request中
        SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor)asyncManager.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
        if(securityProcessingInterceptor == null) {//初始化注册到asyncManager中
            asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY, new SecurityContextCallableProcessingInterceptor());
        }

        filterChain.doFilter(request, response);
    }

3.3 LogoutFilter

  • 实例化
public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
        Assert.notEmpty(handlers, "LogoutHandlers are required");
       //退出处理函数,清理session及cookie及缓存中的认证信息
        this.handlers = Arrays.asList(handlers);
        Assert.isTrue(!StringUtils.hasLength(logoutSuccessUrl) || UrlUtils.isValidRedirectUrl(logoutSuccessUrl), logoutSuccessUrl + " isn't a valid redirect URL");
        SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
        if(StringUtils.hasText(logoutSuccessUrl)) {
//指定退出后的跳转url            urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
        }
        this.logoutSuccessHandler = urlLogoutSuccessHandler;
        this.setFilterProcessesUrl("/j_spring_security_logout");
    }
  • 过滤处理
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if(!this.requiresLogout(request, response)) {//匹配函数未匹配
            chain.doFilter(request, response);
        } else {
            //获取认证信息
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("Logging out user '" + auth + "' and transferring to logout destination");
            }

            Iterator i$ = this.handlers.iterator();

            while(i$.hasNext()) {//遍历退出处理函数,进行处理
                LogoutHandler handler = (LogoutHandler)i$.next();
                handler.logout(request, response, auth);
            }
            //使用sendRedirect(),跳转到退出url
            this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
        }
    }
  • sendRedirect()与forward()

sendRedirect就是服务端根据逻辑,发送一个状态码(Location ,状态码320),告诉浏览器重新去请求那个地址,一般来说浏览器会用刚才请求的所有参数重新请求,所以session,request参数都可以获取。

forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。

3.4 SecurityContextHolderAwareRequestFilter

  • 获取servlet版本信息
private boolean isServlet3() {
        return ClassUtils.hasMethod(ServletRequest.class, "startAsync", new Class[0]);
    }
  • 根据servlet版本信息初始化请求工厂
    this.requestFactory = (HttpServletRequestFactory)(this.isServlet3()?this.createServlet3Factory(this.rolePrefix):new HttpServlet25RequestFactory(this.trustResolver, this.rolePrefix));
  • servlet3工厂
private HttpServletRequestFactory createServlet3Factory(String rolePrefix) {
        HttpServlet3RequestFactory factory = new HttpServlet3RequestFactory(rolePrefix);
        factory.setTrustResolver(this.trustResolver);
        factory.setAuthenticationEntryPoint(this.authenticationEntryPoint);
        factory.setAuthenticationManager(this.authenticationManager);
        factory.setLogoutHandlers(this.logoutHandlers);
        return factory;
    }
  • 注入认证管理器AuthenticationManager
  • 注入认证入口函数AuthenticationEntryPoint
  • 注入logout处理函数
  • 过滤处理
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

//请求工厂创建一个请求        chain.doFilter(this.requestFactory.create((HttpServletRequest)req, (HttpServletResponse)res), res);
    }
  • 请求的类关系图


    image.png

3.5 AnonymousAuthenticationFilter

  • 支持匿名用户认证,用户名 anonymousUser,角色ROLE_ANONYMOUS
  • 过滤处理
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        if(this.applyAnonymousForThisRequest((HttpServletRequest)req)) {
            if(SecurityContextHolder.getContext().getAuthentication() == null) {//无认证信息
                SecurityContextHolder.getContext().setAuthentication(this.createAuthentication((HttpServletRequest)req));//创建匿名用户认证
                if(this.logger.isDebugEnabled()) {
                    this.logger.debug("Populated SecurityContextHolder with anonymous token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
                }
            } else if(this.logger.isDebugEnabled()) {
                this.logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
            }
        }
//使用匿名用户进行后续处理
        chain.doFilter(req, res);
    }

3.6 ExceptionTranslationFilter

  • 异常处理
  • 实例化
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint, RequestCache requestCache) {
        this.accessDeniedHandler = new AccessDeniedHandlerImpl();//禁止访问处理
        this.authenticationTrustResolver = new AuthenticationTrustResolverImpl();
        this.throwableAnalyzer = new ExceptionTranslationFilter.DefaultThrowableAnalyzer();//异常解析
        this.requestCache = new HttpSessionRequestCache();
        Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
        Assert.notNull(requestCache, "requestCache cannot be null");
//认证入口,异常发生时,调用认证入口函数
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.requestCache = requestCache;
    }
  • 过滤处理
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;

        try {
            chain.doFilter(request, response);
            this.logger.debug("Chain processed normally");
        } catch (IOException var9) {//io异常不处理
            throw var9;
        } catch (Exception var10) {
//解析异常
            Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
            RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
            if(ase == null) {
                ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
            }

            if(ase == null) {//解析失败,抛出异常
                if(var10 instanceof ServletException) {
                    throw (ServletException)var10;
                }

                if(var10 instanceof RuntimeException) {
                    throw (RuntimeException)var10;
                }

                throw new RuntimeException(var10);
            }
//解析成功,进行处理
            this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
        }

    }
  • 异常解析结果处理
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
        if(exception instanceof AuthenticationException) {
            this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
//认证异常,重新认证
            this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);
        } else if(exception instanceof AccessDeniedException) {//权限异常
            if(this.authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {//匿名用户,重新认证
                this.logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point", exception);
                this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException("Full authentication is required to access this resource"));
            } else {//调用认证失败处理函数处理
                this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
                this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
            }
        }

    }

3.7 FilterSecurityInterceptor

  • doFilter处理
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);//初始化
        this.invoke(fi);
    }
  • invoke()
public void invoke(FilterInvocation fi) throws IOException, ServletException {
        if(fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } else {
            if(fi.getRequest() != null) {//只处理一次
                fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }
            //调用各模版函数
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.finallyInvocation(token);
            }

            super.afterInvocation(token, (Object)null);
        }

    }
  • beforeInvocation()
protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        boolean debug = this.logger.isDebugEnabled();
        if(!this.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: " + this.getSecureObjectClass());
        } else {
            //获取url匹配pattern规则的`intercept-url`配置
            Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
            if(attributes != null && !attributes.isEmpty()) {
                if(debug) {
                    this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
                }

 //未认证会创建匿名认证信息               if(SecurityContextHolder.getContext().getAuthentication() == null) {
                    this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
                }
//检查是否需要进行认证,一种是匿名认证,一种是配置了重复认证。
                Authentication authenticated = this.authenticateIfRequired();

                try {//授权校验处理,失败则抛AccessDeniedException
                    this.accessDecisionManager.decide(authenticated, object, attributes);
                } catch (AccessDeniedException var7) {
                    this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));//发送通知事件
                    throw var7;//异常处理函数中处理
                }

                if(debug) {
                    this.logger.debug("Authorization successful");
                }
                //通知授权结果
                if(this.publishAuthorizationSuccess) {
                    this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
                }

                Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
                if(runAs == null) {
                    if(debug) {
                        this.logger.debug("RunAsManager did not change Authentication object");
                    }

                    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
                } else {
                    if(debug) {
                        this.logger.debug("Switching to RunAs Authentication: " + runAs);
                    }

                    SecurityContext origCtx = SecurityContextHolder.getContext();
                    SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
                    SecurityContextHolder.getContext().setAuthentication(runAs);
                    return new InterceptorStatusToken(origCtx, true, attributes, object);
                }
            } else if(this.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'");
            } else {
                if(debug) {
                    this.logger.debug("Public object - authentication not attempted");
                }

                this.publishEvent(new PublicInvocationEvent(object));
                return null;
            }
        }
    }
  • finallyInvocation
protected void finallyInvocation(InterceptorStatusToken token) {
        if(token != null && token.isContextHolderRefreshRequired()) {//配置每次都刷新缓存的认证信息,默认配置为否
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("Reverting to original Authentication: " + token.getSecurityContext().getAuthentication());
            }

            SecurityContextHolder.setContext(token.getSecurityContext());
        }

    }
  • afterInvocation
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
        if(token == null) {
            return returnedObject;
        } else {
            this.finallyInvocation(token);
            if(this.afterInvocationManager != null) {
                try {
//处理后校验
                    returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject);
                } catch (AccessDeniedException var5) {
                    AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(), var5);
                    this.publishEvent(event);
                    throw var5;
                }
            }

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