天天看点

[spring security 那点事儿]配置方式

这段时间一直开发的B2C的网购平台已经完成了将近一半的功能,突然觉得自己之前对于权限管理方面的hold决定将给后面带来不小的工作量,所以决定现在就加入权限判断的功能。很容易联想到spring security来做这个事情,先看看一个官方文档的翻译版本:

http://www.family168.com/tutorial/springsecurity/html/springsecurity.html

写的有些简略,但是足以能step by step的执行下去。这里总结一下今天通过命名空间方式的配置过程吧。

既然要做到自动拦截并进行权限判断,很明显,对于spring security而言,需要有一个监听器,这个的配置很容易,类似于spring的监听容器一样,web.xml中加入配置

<!-- spring security configuration -->

<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>

然后剩下的事情更简单了,通常而言,系统中部分url或者代码希望被拦截并检测权限,如这里应用到的后台管理系统,还有一部分是不需要进行拦截的,比如系统的首页。那么这个事情是需要你告诉spring security的,在2.0之后引入的namespace的方式将这个过程简化的非常容易,也就是使用<http/>的方式,这里暂且略过那段简单的代码,你可以在上面的文档第二章看到那个只有短短几行的配置,却实现了一个最初的demo。

这里说说遇到的问题,那就是关于auto-config的问题,auto-config设置为true,spring security默认加载了过滤器并且修正其过滤器的执行顺序,避免了由于过滤器执行顺序不合适导致的异常。但是这里一旦配置成默认的true方式,那么如果你要指定多个验证数据来源也就是provider,会出现异常:

SecurityConfigurationException: More than one UserDetailsService registered. Please use a specific Id in your configuration

这个异常出现的原因就是因为你使用了auto-config=true,同时又配置了多个UserDetailsService的bean,导致auto-config中默认加载的<remember-me>在装配UserDetailsService的时候找不到合适的bean,注意这里的装配是byType的。比如下面的代码就配置了两个provider,一个是通过数据库来获取数据,一个在内存中常驻一个默认的超级用户的配置:

<!-- DAO provider service -->

<beans:bean id="dbUserDetailsService"

class="org.springframework.security.userdetails.jdbc.JdbcDaoImpl">

<beans:property name="dataSource" ref="datasource" />

</beans:bean>

<!-- memory provider service -->

<beans:bean id="memoryUserDetailService" class="org.springframework.security.userdetails.memory.InMemoryDaoImpl">

<beans:property name="userMap">

<beans:value>

mikko=120a91479f8d471ff50a501e150b7737,ROLE_USER,ROLE_ADMIN,ROLE_SUPER

</beans:value>

</beans:property>

</beans:bean>

上面的JdbcDaoImpl和InMemoryDaoImpl都是实现了接口UserDetailsService,分别制定了datasource和usermap作为用户数据的来源。

这个类似于你配置了两个相同名称的bean,却制定了autoWired=byName一样的道理。解决方法也很简单,你只需要去掉auto-config=true,然后手动填写需要的过滤器类型,如果你需要<remember-me/>那么就手动指定它的userDetailService,如下:

<remember-me user-service-ref="dbUserDetailsService"/>

我希望来自数据库的用户的数据库可以被写入cookie从而实现页面上的诸如“2周内自动登录”的功能,因为内存的那些账户是预留的默认最高权限用户。当然可能应用场景不同于这样的简单,但是解决方案的原理也是相同的。

正如上面所示,我们制定了两个userDetailService,一个定位于数据库,一个定位于内存,所以同样的需要配置两个provider来告诉spring security这两个的数据提供者:

<security:authentication-provider user-service-ref="dbUserDetailsService">

<security:password-encoder hash="md5">

<security:salt-source user-property="username"/>

</security:password-encoder>

</security:authentication-provider>

<security:authentication-provider user-service-ref="memoryUserDetailService">

<security:password-encoder hash="md5">

<security:salt-source user-property="username"/>

</security:password-encoder>

</security:authentication-provider>

好了,工作已经完成了,剩下的你可能会想两方面的问题:

1、页面的权限验证是怎么执行的?

2、数据库的数据哪里来的?我能否自定义表结构来整合自己的项目特殊需求?

第一个问题,权限验证如何执行的?看看这里的http的配置:

<http>

<intercept-url pattern="/index.do*" filters="none" />

<intercept-url pattern="/freemarker/**" filters="none" />

<intercept-url pattern="/dwr/**" filters="none" />

<intercept-url pattern="/login.do*" filters="none" />

<intercept-url pattern="/**" access="ROLE_USER" />

<form-login login-page='/login.do' default-target-url='/index.do' />

<http-basic />

<logout logout-success-url="/index.do"/>

<remember-me user-service-ref="dbUserDetailsService"/>

<concurrent-session-control max-sessions="1" expired-url="/login.do"/>

</http>

很简单,指明了登录页面,默认登录成功的定位地址,注销后的定位地址,特别注意的是这里我们必须配置对于图片和css、js等资源的请求不能被拦截掉,否则页面会丢失样式,这里需要手动的设置对于资源文件的请求放行,即过滤器不执行拦截,同时由于我们系统使用了dwr来实现异步交互,所以同样的这里也是放行,如果需要进行权限判断,则通过其他方式如方法权限判断等实现。

 这里我们自定义了登录页面,而不是使用spring security那个没有任何样式的页面,那么在自己的登录页面的登录表单中,要怎么去整合spring security呢,很容易,只要将input的名字符合其要求即可:

<#local loginDIV>

${Session["SPRING_SECURITY_LAST_EXCEPTION"]?default('')}

<form action="./j_spring_security_check" method="POST">

用户名

<input type="text" name="j_username" id="j_username" value="${Session['SPRING_SECURITY_LAST_USERNAME']?default('')}"/>

密码

<input type="password" name="j_password" id="j_password" value=""/>

<input type="checkbox" name="_spring_security_remeber_me" /> 两周内自动登录

<button type="submit">登录</button>

<button type="reset">重置</button>

</form>

</#local>

要注意一下action的url。上面的从session中取出的,是权限校验失败的情况下的错误异常信息,这些信息由于使用的是namespace的方式,并不能像以前普通bean放下的配置显示具体的错误类型,但是这个也没什么关系,由于那个message.properties是一个英文文件,如果要将详细的信息显示出来,需要执行国际化,但是这里其实错误并无太紧急的需求显示详细,所以后续可以通过其他的机制,来显示一个通用性的信息即可,或者研究一下在这种namespace的方式下,如何去设置这个地方。

如果既配置http又配置了providerManager会怎样?http会有效,其中的NamespaceAuthenticationManager方法providerBeanNames参数保存了namespace方式的配置的默认的provider列表,如果这个参数为空,才回去获取配置的providerManager配置的provider,但是矛盾的是,如果providerBeanNames为空,却会抛出

No authentication providers were found in the application context异常!

所以,这也是行不通的。这里的应用场景较为初级的使用了spring security,下面要进行的,是针对于本系统的一些特性,尤其是后台的富客户端情况下,无法简单的通过url拦截的方式来实现权限控制的情况。

第二个问题,处理起来也很简单,看看官方文档,写的很清楚了,不罗嗦了

http://www.family168.com/tutorial/springsecurity/html/appendix-schema.html