Spring security深入杂谈


spring security 是一个用于身份验证和访问控制的成熟框架,主要用于web的url访问,当然她也可以用于更加细粒度的访问控制(方法控制)但前者更具普遍意义,本文论述前者。
一 Quick start,一个简单的例子

step1: 在web.xml中添加spring security的代理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>

step2: spring-security-context.xml
    <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"
        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.0.xsd">
    
        <http>
            <intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR" />
            <intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_FULLY" />
            <intercept-url pattern="/login.htm" access="IS_AUTHENTICATED_ANONYMOUSLY" />
            <intercept-url pattern="/images/*" filters="none" />
            <intercept-url pattern="/**" access="ROLE_USER" />
            <form-login login-page="/login.htm" default-target-url="/home.htm" />
            <logout logout-success-url="/logged_out.htm" />
        </http>
    
        <authentication-manager>
            <authentication-provider>
                <password-encoder hash="md5"/>
                <user-service>
                    <user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />
                    <user name="sam" password="d1a5e26d0558c455d386085fad77d427" authorities="ROLE_USER" />
                </user-service>
            </authentication-provider>
        </authentication-manager>
 
    </beans:beans>
好了,这是一个来自spring官网的例子,我们居然没写代码!好吧,我们看看这些配置都干了些什么.
首先是在web.xml中定义了一个名为:“springSecurityFilterChain”的过滤器。然后就是在spring的上下文文件中:
    <intercept-url pattern="/**" access="ROLE_USER" />
表示访问任何url的用户都必须拥有"ROLE_USER"的权限,而用户权限的定义是在
    <user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />定义的。
仔细看看这些定义,你是不是觉得有点迷惑:
    <intercept-url pattern="/images/*" filters="none" />
表明对"/images/*"的访问是不需要任何权限的为什么"/**"又需要"ROLE_USER"的权限呢,对了,顺序很重要,url的权限判断是根据从上到下的顺序匹配第一个的。
    <form-login login-page="/login.htm" default-target-url="/home.htm" />
定义了登录页面“login-page”, 并且登录成功展示的页面“default-target-url”, 等等。
到这儿其实就是一个完整的spring security 的配置了,已经能够work了,是不是毫无成就感,这一切都是怎么发生的呢,一点都没展示!

好吧,我们开始抽丝剥茧,一点一点的讲明白这件事情。


二 这一切都是怎么发生的。
1. 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的保护是通过过滤器完成的,没错,就是你想到的那个“javax.servlet.Filter”。
这个名为“springSecurityFilterChain”    的filter会拦截所有的访问请求进行权限校验和访问控制。
“springSecurityFilterChain”是个代理,它将拦截到的请求委托给它代理的若干个filter进行校验。这些过滤器按照一定的顺序组成一个链对请求进行处理,这也体现在
代理的名字中“Chain”。这些filter的顺序和数量都是可配置的(在3.0中不可配置了,但是仍旧可以添加新的filter到任何位置),对这种需求最好的解决方法就是依赖注入(IOC)可惜在web.xml中时没有IOC的,这就是为什么是个代理的原因:为了获取IOC的支持,所以真正的配置是在spring的上下文中。
其实org.springframework.web.filter.DelegatingFilterProxy,这个类所代理的类是名为springSecurityFilterChain的类,是在springcontext中定义的,在spring security3.0中这个类被框架自动创建了,是个org.springframework.security.web.FilterChainProxy的instance。这一点在后面会另外讲。

2. spring-security-contect.xml
    <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"
        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.0.xsd">
    
        <http>
            <intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR" />
            <intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_FULLY" />
            <intercept-url pattern="/login.htm" access="IS_AUTHENTICATED_ANONYMOUSLY" />
            <intercept-url pattern="/images/*" filters="none" />
            <intercept-url pattern="/**" access="ROLE_USER" />
            <form-login login-page="/login.htm" default-target-url="/home.htm" />
            <logout logout-success-url="/logged_out.htm" />
        </http>
    
        <authentication-manager>
            <authentication-provider>
                <password-encoder hash="md5"/>
                <user-service>
                    <user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />
                    <user name="sam" password="d1a5e26d0558c455d386085fad77d427" authorities="ROLE_USER" />
                </user-service>
            </authentication-provider>
        </authentication-manager>
配置很简单,因为在spring security3.0中引入了命名空间,也就是说使用了命名空间框架就会帮你做大量的默认工作,好处就是配置简单,坏处就是细节
被掩盖了,你不明白为什么。呵呵。
按照之前的思路,在spring的上下文中应该有叫做:“springSecurityFilterChain”的类,它定义了很多的filter来处理请求,你想的没错,只是这个细节被
<http>这个标签掩盖了。
<http>的重要使命?
创建了filter链(被掩盖的细节终于出现了)
    <alias name="filterChainProxy" alias="springSecurityFilterChain"/>
    
    <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
        <sec:filter-chain-map path-type="ant">
            <sec:filter-chain pattern="/images/*" filters="none"/>
            <sec:filter-chain pattern="/**" filters="securityContextFilter, logoutFilter, formLoginFilter, requestCacheFilter,servletApiFilter, anonFilter, sessionMgmtFilter, exceptionTranslator, filterSecurityInterceptor" />
        </sec:filter-chain-map>
    </bean>
<http>首先建立了"filterChainProxy"的bean并且为每一个url配上了默认的filter链,然后将"filterChainProxy"起了个"springSecurityFilterChain"的别名,很眼熟吧,就是在web.xml中配置的那个filter名。有一个来自spring官方网站的图可以清楚的看到<http>都内置了哪些filter:这些filter有的是可选的,有的是必须的。

这些filter被<http>事先生成,并且顺序也是确定的。spring security不允许覆盖或者改变已经内置的filter,所以如果你想添加一些更加特殊化的处理只能在某个位置追加自己定义的filter,这一点spring security是支持的。
在spring security中对web资源的保护共有17种过滤器,大部分一般都用不上,必须的有四种:
    1.HttpSessionIntegrationFilter: 记住用户信息。
    2.authenticationProcessingFilter: 配置用户登录页面,提示用户登录,并且对用户的身份进行验证,在该filter中有一个属性叫做                authenticationEntryPoint,这个bean中可以配置登录页面。
    3.exceptionTranslationFilter: 更友善的对用户展示异常。
    4.filterSecurityInterceptor: 根据用户所拥有的权限和资源所要求的权限进行最终的判定。
好了,到这里filter这件事情基本上讲完了,这些filter都干了些什么呢?

3. filter中都做了什么
在这个框架中,主要做了两件事情:用户验证和访问控制,需要做这两件事情的filter会注入authenticationManager(用户验证)和accessDecsionManager(访问控制),先说authenticationManager,它负责用户验证,但是它本身不干活,把活派给了authenticationProvider,spring security是个贴心的框架,本身提供了大量的provider供用户使用,如数据库认证,JAAS认证等等,
如果还没有适合的随时可以实现authentication provider去创建自己的provider。其实provider本身也不干活,它把活派发给了另外一个苦逼接口,UserDetailService,
它是真正的干活的,它有一个方法:public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException{}
用来获取用户信息,如果信息存在则返回一个UserDetail对象,如果不存在则抛出UsernameNotFoundException。这个方法是需要用户自己实现的。如果验证成功,
provider 会生成一个Authentication对象并将这个对象放入org.springframework.security.core.context.SecurityContext这个当前应用的安全上下文中,这样的话当前应用在任何时候都可以获判断当前用户是否已经通过认证,以及获取当前用户的有关信息,如用户名,密码,权限等。
再说说accessDecsionManager,它负责判断当前用户是否有权限访问某个资源,它也不干活,派给了若干个AccessDecisionVoter, 这些个接口负责投票,会有三种票:赞成,反对,弃权,而accessDecsionManager有三种不同的实现,分别实现了三种策略:只要有一个赞成票就允许访问;只要大多数赞成就允许访问;都是赞成票才允许访问。这样就决定了一个用户是否有权访问一个资源。一般来说spring security提供了一个默认的实现:org.springframework.security.vote.RoleVoter就足以满足大多数的需求了,这个类只要被访问的资源需要role_作为前缀的权限它就会通过比较user拥有的权限和资源需要的权限来进行投票,否则就投弃权票。知道了这个特性就可以针对它来设置自己的用户权限和资源权限(使用role_作为前缀),一般没有必要自己实现。当然这个“role_”前缀的规则是可以通过设置来改变的。

讲到这基本上都差不多了,说一个工作中的bug吧:
用户反映登录页面在出现异常情况时,如数据库down掉或者网络不通时总是报错:“该用户没有权限”,这个确实不妥。经过查找在UserDetailService的实现类中方法loadUserByUsername中无论发生了任何异常都会catch而后抛出UsernameNotFoundException,而在filter:AuthenticationProcessingFilter中如果一旦认证失败,它会获取这个失败的异常并将这个exception写入session中,key值为:“SPRING_SECURITY_LAST_EXCEPTION”,这样在jsp中就可以从session中获取这个exception,判断后告诉用户到底是什么错误。无论什么错误只抛UsernameNotFoundException就导致jsp只看到这个异常也就只好老是报错“该用户没有权限”了。改正的方法很简单,实现若干个继承了org.springframework.security.core.AuthenticationException(UsernameNotFoundException的父类)的异常,在catch到不同的错误时抛出然后在jsp中进行判断就可以了。

讲到这基本上都差不多了,最后再说一点,spring security提供了一个JSP标签库来方便在JSP页面中最安全信息进行获取。感兴趣可以自己看看,这里不详细说明了。

写的比较乱,错误之处请不吝指出,谢谢。


















  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值