1、使用代理。
web.xml中配置的org.springframework.web.filter.DelegatingFilterProxy,是一个代理bean,并且依赖org.springframework.security.web.FilterChainProxy对执行所有的过滤器。在启动的时候,spring会解析spring-security.xml中的所有配置,初始化FilterChainProxy这个对象,并且将用到的过滤器放到FilterChainProxy的列表中。这样FilterChainProxy就使用责任链的模式,一个个的调用用到的过滤器。
2、责任链调用过滤器,这里注意有些过滤器在执行doFilter()之后还有有代码,则会在后面的过滤器执行完之后,再回来执行。比如SecurityContextPersisten
3、springsecurity包含登陆和授权两方面内容。登陆主要集中在UsernamePasswordAuthenti
4、我们使用的springsecurity3 一般用到的过滤器按顺序如下:
org.springframework.security.web.context.SecurityContextPersisten
这个过滤器做的事情是创建SecurityContextHolder里的SecurityContext,SecurityContext就像我们做登陆时候用的session一样,用来存储当前登陆的用户信息。SecurityContextHolder使用的threadlocal,当前请求使用一个SecurityContextHolder。一直疑惑每个请求一个的话,如何保证下一个请求,还能保留住上次的session,其实这个过滤器在清除SecurityContextHolder之前,先把SecurityContext拷贝出来放到session中,使用session的名称是HttpSessionSecurityConte
之前想过自己写登陆程序。写了一个filter,并放到spring security前面。登录成功之后设置了SecurityContextHolder.getContext
执行登出操作。如果请求的地址是登出的url,这个可以配置。默认的地址是/j_spring_security_logout。SecurityContextHolder会清空,并且session会invalidate。然后过滤器链终止,直接跳转到登出后的页面。
org.springframework.security.web.authentication.UsernamePasswordAuthenti
执行登录的过滤器。自定义登录的主要重写这个类。这个类先判断当前请求是不是执行登录的请求,默认是/j_spring_security_check。是这个请求才做处理,否则跳到下一个过滤器。
先执行attemptAuthentication方法。这个方法会从request中读出用户名、密码,组成UsernamePasswordAuthenti
AuthenticationManager实际是ProviderManager,ProviderManager实现了AuthenticationManager接口。将认证的工作交给AuthenticationProvider,AuthenticationManager都会包含多个AuthenticationProvider对象,有任何一个AuthenticationProvider验证通过,都属于认证通过。AuthenticationManager和AuthenticationProvider在哪里来的呢?在spring-security.xml的配置中来的。如:
我们一般使用的provider是DaoAuthenticationProvide
继续前面内容,provider获得UserDetail之后先验证userdetail的有效性,是否被封禁,是否过期。。。userdetail的那一系列接口。如果可用,然后会跟从request中获取的用户密码进行比对,密码比对的时候,会根据用户的设置,是否才用md5加密。这跟咱们自己做登录完全一样。认证成功之后会重新生成一个UsernamePasswordAuthenti
验证通过之后attemptAuthentication方法就结束了,也就是认证结束,将认证的用户信息Authentication返回出来了。接着是处理登录后的一些事情。
1、把Authentication放到SecurityContextHolder.getContext().setAuthentication(),留着后面一直使用。
2、如果用户在登录的时候选择了记住密码(也就是传递了_spring_security_remember_me这个参数),系统会将用户名密码写到cookie中。cookie的名字是SPRING_SECURITY_REMEMBER_ME_COOKIE,设置的规则是默认2周,对用户名、过期时间和加密串 用“:”串在一起base64加密,然后放到cookie里。加密串是用来下次自动登录的时候进行校验的。规则是md5(用户名:过期时间:密码:私钥),私钥是在spring-security.xml中配置的。比如 这里的key。这样就将cookie安全的设置到浏览器里,以后登录的时候,会从cookie中解析出用户名来,然后调用上面的loadUserByUsername方法,把用户从数据库读出来,验证成功后,就直接登录了。
然后登录就执行成功了。执行完登录之后,过滤器链也结束了,跳转到登录成功后的页面。关于登录后的页面跳转这里有点意思。可以指定登录成功后始终跳转到一个固定的页面,配置isAlwaysUseDefaultTarget
这里需要提下,类似的过滤器还有CasAuthenticationFilter的用来集成cas单点登录,OpenIDAuthenticationFilt
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
觉得这个和上面跳转到前一个请求类似。先从HttpSessionRequestCache把之前放进去的request读出来,看看是否和当前的request一样,如果一样就将两个request合在一起。如果不一样就什么操作也不做,继续往下一个链条。
org.springframework.security.web.servletapi.SecurityContextHolderAwa
这个方法是将用户的信息放到request中,重写getRemoteUser,
org.springframework.security.web.authentication.rememberme.RememberMeAuthentication
使用记住密码记住的用户信息进行登录。当登录的时候选择了记住密码,上面已经介绍了,会写如cookie。以后没有登录就访问保护的资源时候,这里就起作用了。因此这里需要先验证用户是不是没有登录过,如果已经是登录状态的就不用执行,直接跳下一个链条。如果没有登录过,才去读cookie进行登录。验证是否登录使用的是SecurityContextHolder.getContext().getAuthentication() == null
从cookie读用户的过程,上面也介绍了,解析cookie,然后从数据库里读出用户信息,然后验证md5是否合法,然后就执行登录了。放到SecurityContextHolder里面。
org.springframework.security.web.authentication.AnonymousAuthenticationF
如果用户没有登录,也就是SecurityContextHolder.getContext().getAuthentication() == null。这个过滤器就会起作用,创建一个AnonymousAuthenticationT
org.springframework.security.web.session.SessionManagementFilter
定义session规则的好像。基本不用。
org.springframework.security.web.access.ExceptionTranslationFilt
这个作用是对后面的过滤器链抛出的所有的异常进行捕获并且执行相应的操作。设计的很巧妙,过滤器中什么都不执行,只是用了try catch。在catch中执行内容。这个需要好好学习。后面执行的对异常两种操作,先判断当前是因为用户没有登录造成的异常,还是玩家登录过,但是没有权限操作当前请求造成的异常。对于第一种处理是将当前request放到HttpSessionRequestCache里,然后调用authenticationEntryPoint
如果没有异常,继续下面的过滤器链
org.springframework.security.web.access.intercept.FilterSecurityIntercepto
这部分是spring security执行授权的核心部分。如果我们自己定义权限,会自己定义一个FilterSecurityIntercepto
其中secureResourceFilterInvo
咱们先看看重写了什么东西。主要重写Collection getAttributes(final Object filter),boolean supports(Class arg0)。supports作用是判断是否使用当前的授权判断。getAttributes中传的对象是filter实际是FilterInvocation 对象,这个对象将request,response,chain都包含了,这个方法做的事情是将request中请求uri读取出来,并从数据库中装载当前uri需要的角色有哪些,然后将角色放到集合中返回。这些角色怎么用呢。大致想想应该是和当前session中的用户的角色进行比对,如果角色满足要求,就让通过,不满足的话就抛出异常跳到403或者登陆框去。uri可能有多个角色,用户也可能有多个角色,这就存在一个策略问题。用户的角色有一个在uri的角色中就算通过,还是所有的角色都满足才通过。这就是上面accessDecisionManager做的事情了。咱们细致分析FilterSecurityIntercepto
调用AffirmativeBased(accessDecisionManager)的decide方法来决定是否让通过。accessDecisionManager依赖多个AccessDecisionVoter分别对session中用户角色,和uri的角色进行投票,投票的方式是,循环uri的角色,然后看当前角色是否在用户的角色中如果有一个在,则一个voter投票结束。如果有一个成功的,则认为成功,如果有一个投票失败,则使用其他的AccessDecisionVoter投票,所有的都失败,则认为不让通过。
从上面的流程终于明白上面引用的各个参数的原因了。authenticationManager作用是如果当前用户没有登录认证成功过,则重新认证一次。accessDecisionManager用来投票决定是否让通过。securityMetadataSource用来读取当前资源需要的角色。observeOncePerRequest这个属性用来控制一个request是否验证一次就不再验证了。对于多个FilterSecurityIntercepto
org.springframework.security.web.access.intercept.FilterSecurityIntercepto
这里为什么又来了一个FilterSecurityIntercepto
,自定义的如上面的配置。
是先进配置的还是自定义的,需要根绝配置的顺序而定。从上面定义可以看出,自定义的过滤器放到了last的后面,可能是最后一个了。一般我们自定义的都放到最后一个。
终于全部写完了。辛苦是有点,但是收获是巨大的。后面继续研究。