spring security3 笔记

来源:互联网 发布:淘宝客服快递快捷短语 编辑:程序博客网 时间:2024/05/29 04:52

1、使用代理。

web.xml中配置的org.springframework.web.filter.DelegatingFilterProxy,是一个代理bean,并且依赖org.springframework.security.web.FilterChainProxy对执行所有的过滤器。在启动的时候,spring会解析spring-security.xml中的所有配置,初始化FilterChainProxy这个对象,并且将用到的过滤器放到FilterChainProxy的列表中。这样FilterChainProxy就使用责任链的模式,一个个的调用用到的过滤器。

 

2、责任链调用过滤器,这里注意有些过滤器在执行doFilter()之后还有有代码,则会在后面的过滤器执行完之后,再回来执行。比如SecurityContextPersistenceFilter有一个finnaly的地方,用来清空SecurityContextHolder,ExceptionTranslationFilter则在catch的地方将其后面的异常捕获并处理。这个思路非常好,需要好好领会。

 

3springsecurity包含登陆和授权两方面内容。登陆主要集中在UsernamePasswordAuthenticationFilter,授权主要集中在FilterSecurityInterceptor。所以这两部分如果需要自定义,则去重写这两部分内容。

 

4、我们使用的springsecurity3 一般用到的过滤器按顺序如下:

org.springframework.security.web.context.SecurityContextPersistenceFilter

这个过滤器做的事情是创建SecurityContextHolder里的SecurityContextSecurityContext就像我们做登陆时候用的session一样,用来存储当前登陆的用户信息。SecurityContextHolder使用的threadlocal,当前请求使用一个SecurityContextHolder。一直疑惑每个请求一个的话,如何保证下一个请求,还能保留住上次的session,其实这个过滤器在清除SecurityContextHolder之前,先把SecurityContext拷贝出来放到session中,使用session的名称是HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY(“SPRING_SECURITY_CONTEXT”)。下次请求来的时候,会先读session中这个值,如果没有的话才会创建新的,否则就使用session中的。不懂为什么这么设计。

之前想过自己写登陆程序。写了一个filter,并放到spring security前面。登录成功之后设置了SecurityContextHolder.getContext ().setAuthentication(),但是到后面还是会让再登录。原因就是没有将SecurityContext设置到session中。

 

 org.springframework.security.web.authentication.logout.LogoutFilter

执行登出操作。如果请求的地址是登出的url,这个可以配置。默认的地址是/j_spring_security_logoutSecurityContextHolder会清空,并且sessioninvalidate。然后过滤器链终止,直接跳转到登出后的页面。

 

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

执行登录的过滤器。自定义登录的主要重写这个类。这个类先判断当前请求是不是执行登录的请求,默认是/j_spring_security_check。是这个请求才做处理,否则跳到下一个过滤器。

先执行attemptAuthentication方法。这个方法会从request中读出用户名、密码,组成UsernamePasswordAuthenticationToken对象,这个对象也是Authentication对象,UsernamePasswordAuthenticationToken是实现了Authentication。然后将Authentication对象传给AuthenticationManager进行登录认证。AuthenticationManager的认证过程是springsecurity认证的核心。大致记录下。

AuthenticationManager实际是ProviderManagerProviderManager实现了AuthenticationManager接口。将认证的工作交给AuthenticationProviderAuthenticationManager都会包含多个AuthenticationProvider对象,有任何一个AuthenticationProvider验证通过,都属于认证通过。AuthenticationManagerAuthenticationProvider在哪里来的呢?在spring-security.xml的配置中来的。如:

   

        

          

        

   

我们一般使用的providerDaoAuthenticationProvider,也有别的providerDaoAuthenticationProvider是可以查询数据库,读用户名密码的一种方式。provider会引用UserDetailsService来从数据库查询出用户来。这个地方一般是我们重写的接口,实现根据用户名读取用户信息,以及用户的角色信息(Authority)比如上面,我们引用的是securityUserServiceUserDetailsServiceloadUserByUsername,关键是两步,第一步根据用户名将用户信息读出来,然后构造一个UserDetail对象。一般情况,我们会将自己项目中User对象实现UserDetail接口,这样从数据库读出来的就是一个UserDetail对象了,否则就得自己手动构造一个。第二步就是UserDetail必须设置Collection getAuthorities();获取出角色来。我们常用的方式是将User对象关联的角色非延迟加载,获取user对象的同时,就已经给赋值了角色。

继续前面内容,provider获得UserDetail之后先验证userdetail的有效性,是否被封禁,是否过期。。。userdetail的那一系列接口。如果可用,然后会跟从request中获取的用户密码进行比对,密码比对的时候,会根据用户的设置,是否才用md5加密。这跟咱们自己做登录完全一样。认证成功之后会重新生成一个UsernamePasswordAuthenticationTokenAuthentication),原因是使用UsernamePasswordAuthenticationToken的构造方法将Authentication的认证状态(authenticated)设置成true,后面授权的时候会使用到。

验证通过之后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方法,把用户从数据库读出来,验证成功后,就直接登录了。

然后登录就执行成功了。执行完登录之后,过滤器链也结束了,跳转到登录成功后的页面。关于登录后的页面跳转这里有点意思。可以指定登录成功后始终跳转到一个固定的页面,配置isAlwaysUseDefaultTargetUrl就可以。也可以保留默认的,跳转到登录之前的请求地址。这是怎么实现跳到之前的地址呢?登录之前请求的时候,在过滤器链的最后,会发现没有登录,抛出异常,这时候会将当前的request放到HttpSessionRequestCache中,其实就是将request放到了session中,session的名字是“SPRING_SECURITY_SAVED_REQUEST”,等下次执行了登录之后,会先从HttpSessionRequestCache将上一个request拿出来,继续这个request。因此就实现了上一个request继续跳转的功能。

 

这里需要提下,类似的过滤器还有CasAuthenticationFilter的用来集成cas单点登录,OpenIDAuthenticationFilter用来集成openid登录。后面要继续研究cas登录。

 

org.springframework.security.web.savedrequest.RequestCacheAwareFilter

觉得这个和上面跳转到前一个请求类似。先从HttpSessionRequestCache把之前放进去的request读出来,看看是否和当前的request一样,如果一样就将两个request合在一起。如果不一样就什么操作也不做,继续往下一个链条。

 

org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter

这个方法是将用户的信息放到request中,重写getRemoteUser getUserPrincipalisUserInRolerequest的方法。这个需要借鉴,一直不知道HttpServletRequestWrapper干嘛用的,这里明白了,像是一个装饰模式,将一些信息塞到request里面,留着后面调用。

 

org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter

使用记住密码记住的用户信息进行登录。当登录的时候选择了记住密码,上面已经介绍了,会写如cookie。以后没有登录就访问保护的资源时候,这里就起作用了。因此这里需要先验证用户是不是没有登录过,如果已经是登录状态的就不用执行,直接跳下一个链条。如果没有登录过,才去读cookie进行登录。验证是否登录使用的是SecurityContextHolder.getContext().getAuthentication() == null

cookie读用户的过程,上面也介绍了,解析cookie,然后从数据库里读出用户信息,然后验证md5是否合法,然后就执行登录了。放到SecurityContextHolder里面。

 

org.springframework.security.web.authentication.AnonymousAuthenticationFilter

如果用户没有登录,也就是SecurityContextHolder.getContext().getAuthentication() == null。这个过滤器就会起作用,创建一个AnonymousAuthenticationToken也是Authentication对象,放到SecurityContextHolder里面。留着后面做权限验证。保护的资源一般不会给匿名用户权限,所以没有登录后面都会抛异常,认为没有权限访问,跳转到登录框进行登录。

 

org.springframework.security.web.session.SessionManagementFilter

定义session规则的好像。基本不用。

 

org.springframework.security.web.access.ExceptionTranslationFilter

这个作用是对后面的过滤器链抛出的所有的异常进行捕获并且执行相应的操作。设计的很巧妙,过滤器中什么都不执行,只是用了try catch。在catch中执行内容。这个需要好好学习。后面执行的对异常两种操作,先判断当前是因为用户没有登录造成的异常,还是玩家登录过,但是没有权限操作当前请求造成的异常。对于第一种处理是将当前request放到HttpSessionRequestCache里,然后调用authenticationEntryPointcommence方法,默认跳转到登录框,一般我们会重写这个方法,判断请求是不是ajax,如果ajax返回json的信息,如果是普通的请求,则直接跳转到登录框就可以了。第二种的处理是调用AccessDeniedHandlerImplaccessDeniedHandler)的handle方法进行处理,默认的处理方式就是将response的状态设置成403,然后 forward到错误页面。

如果没有异常,继续下面的过滤器链

 

org.springframework.security.web.access.intercept.FilterSecurityInterceptor

这部分是spring security执行授权的核心部分。如果我们自己定义权限,会自己定义一个FilterSecurityInterceptor并重写相应的方法。配置如:

 

 

 

       

       

       

       

   

其中secureResourceFilterInvocationDefinitionSource是需要自己重写的。

咱们先看看重写了什么东西。主要重写Collection getAttributes(final Object filter)boolean supports(Class arg0)supports作用是判断是否使用当前的授权判断。getAttributes中传的对象是filter实际是FilterInvocation 对象,这个对象将requestresponsechain都包含了,这个方法做的事情是将request中请求uri读取出来,并从数据库中装载当前uri需要的角色有哪些,然后将角色放到集合中返回。这些角色怎么用呢。大致想想应该是和当前session中的用户的角色进行比对,如果角色满足要求,就让通过,不满足的话就抛出异常跳到403或者登陆框去。uri可能有多个角色,用户也可能有多个角色,这就存在一个策略问题。用户的角色有一个在uri的角色中就算通过,还是所有的角色都满足才通过。这就是上面accessDecisionManager做的事情了。咱们细致分析FilterSecurityInterceptor的过程。

调用AffirmativeBasedaccessDecisionManager)的decide方法来决定是否让通过。accessDecisionManager依赖多个AccessDecisionVoter分别对session中用户角色,和uri的角色进行投票,投票的方式是,循环uri的角色,然后看当前角色是否在用户的角色中如果有一个在,则一个voter投票结束。如果有一个成功的,则认为成功,如果有一个投票失败,则使用其他的AccessDecisionVoter投票,所有的都失败,则认为不让通过。

从上面的流程终于明白上面引用的各个参数的原因了。authenticationManager作用是如果当前用户没有登录认证成功过,则重新认证一次。accessDecisionManager用来投票决定是否让通过。securityMetadataSource用来读取当前资源需要的角色。observeOncePerRequest这个属性用来控制一个request是否验证一次就不再验证了。对于多个FilterSecurityInterceptor我们一般将这个属性设置成false

 

org.springframework.security.web.access.intercept.FilterSecurityInterceptor

这里为什么又来了一个FilterSecurityInterceptor,因为这个过滤器会创建两个。一个是spring-security.xml中配置的,一个是自定义的。 配置信息如

,自定义的如上面的配置。

是先进配置的还是自定义的,需要根绝配置的顺序而定。从上面定义可以看出,自定义的过滤器放到了last的后面,可能是最后一个了。一般我们自定义的都放到最后一个。

 

终于全部写完了。辛苦是有点,但是收获是巨大的。后面继续研究。

0 0
原创粉丝点击