使用SecurityContextHolder来偷窥登入帐号密码,手段还真是不够文雅。 Spring-Security3是有提供取得登入资讯塞到Session的实践,不过写起来很烦,很烦也大概不易被破解^^。 Google这方面的资讯,不是缺漏,就是讲述古早的版本,还有中文网站,资讯虽新,却常出现文章一大抄的谬误,我目前是用3.0.2版,和3.0.1、3.0 .0差异何在也不知,不过至少我这方面有踹通,实践方式和Google内容还是有些关键性的差异:
第一个应该是security-context.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"
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 use-expressions="true" auto-config="true">
<intercept-url pattern="/*.do" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/login.jsp" access="isAnonymous()"/>
<intercept-url pattern="/**" access="permitAll"/>
<form-login login-processing-url="/j_spring_security_check"
login-page="/login.jsp" default-target-url="/echo.do"
authentication-failure-url="/login.jsp?error=1"/>
<logout logout-url="/j_spring_security_logout" logout-success-url="/login.jsp"/>
<custom-filter before="FORM_LOGIN_FILTER" ref="authenticationProcessingFilter"/>
<session-management invalid-session-url="/timeout.html">
<concurrency-control max-sessions="1" />
</session-management>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="securityManager"/>
</authentication-manager>
<beans:bean
class="com.foo.dao.impl.UserDetailsServiceImpl"/>
<beans:bean
class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="authenticationSuccessHandler">
<beans:bean >
<beans:property name="defaultTargetUrl" value="/echo.do"/>
</beans:bean>
</beans:property>
<beans:property name="filterProcessesUrl" value="/j_spring_security_check"/>
</beans:bean>
</beans:beans>
红字是之前SecurityContextHolder饭粒多出来的设定,真正是靠腰的多。 <http>标签多出<custom-filter>指向authenticationProcessingFilter这个bean,而Google的before后所接的定字几乎都错,都是这么写:
<custom-filter before="AUTHENTICATION_PROCESSING_FILTER" ref="authenticationProcessingFilter"/>
可是Filter的列举(枚举)型态里,根本没这有AUTHENTICATION_PROCESSING_FILTER这号人物,其它的参考下列表,我原本用CONCURRENT_SESSION_FILTER过不了关,后来改成FORM_LOGIN_FILTER才Pass,Spring-Seucrity 3这部份很像Struts2的Interceptor Stack,从下表来看,应该是有顺序,LOGOUT_FILTER排在FORM_LOGIN_FILTER之前…嗯~~也蛮合理的。
Enumerated Values :
- FIRST
- CHANNEL_FILTER
- CONCURRENT_SESSION_FILTER
- SECURITY_CONTEXT_FILTER
- LOGOUT_FILTER
- X509_FILTER
- PRE_AUTH_FILTER
- CAS_FILTER
- FORM_LOGIN_FILTER
- OPENID_FILTER
- BASIC_AUTH_FILTER
- SERVLET_API_SUPPORT_FILTER
- REMEMBER_ME_FILTER
- ANONYMOUS_FILTER
- EXCEPTION_TRANSLATION_FILTER
- SESSION_MANAGEMENT_FILTER
- FILTER_SECURITY_INTERCEPTOR
- SWITCH_USER_FILTER
- LAST
<custom-filter>选择FORM_LOGIN_FILTER时机委给Bean-authenticationProcessingFilter使用,这一动作出现频道盖台,<form-login>标签应该失效,委给authenticationProcessingFilter处理。 authenticationProcessingFilter显然是指定org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter来处理的,此处在Google上又会得到错误类别名,正名是UsernamePasswordAuthenticationFilter,而Google的类别名是UsernamePasswordAuthenticationProcessingFilter,多出了Processing,所以我才猜是不是前版的遗留,至少在3.0.2版没这个类别。
authenticationProcessingFilter要指定三类properties,第一个是指定Authentication Manager。若非这次的需求,SecurityContextHolder会找到<authentication-manager>这个标签作为预设值。但既然委托给Spring Bean处理,所以要有的bean reference的name,所以后来才在<authentication-manager>加个alias属性,这样<authentication-manager>才能被authenticationProcessingFilter找到。
authenticationProcessingFilter要指定的第二类properties是本篇文章目的主程式所在,储存username至session。最多有四种bean,最常用的是认证成功(AuthenticationSuccessHandler)和认证失败(AuthenticationFailureHandler),尚有未认证的访问及已认证访问受保护的URL。而储存至session的动作在第一种,是故AuthenticationSuccessHandler属性指向MyAuthenticationSuccessHandler这个类别,其实作如下:
public class MyAuthenticationSuccessHandler extends
SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
HttpSession session = request.getSession();
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
session.setAttribute("username", userDetails.getUsername());
super.onAuthenticationSuccess(request, response, authentication);
}
}
和前一篇SecurityContextHolder饭粒原理是一样,只是比较文雅,而MyAuthenticationSuccessHandler下设了一个defaultTargetUrl属性,其实也恰恰覆盖<form-login>的default-target-url属性。先前有言,<customer-filter>若设置为CONCURRENT_SESSION_FILTER过不了关,是session值在onAuthenticationSuccess的设定无法带给defaultTargetUrl,所以改成FORM_LOGIN_FILTER就可以work。
第三类是filterProcessesUrl属性,指authenticationProcessingFilter会对哪个URL Pattern产生作用,本例是指/j_spring_security_check,就因为custom-filter设置before="FORM_LOGIN_FILTER"抢在<form-login>拦截,致使<form-login>失效。
以下是成功配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:s="http://www.springframework.org/schema/security" 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"
default-lazy-init="true">
<description>SpringSecurity安全配置</description>
<!-- http安全配置 -->
<s:http auto-config="true" use-expressions="true">
<s:intercept-url pattern="/css/**" filters="none" />
<s:intercept-url pattern="/img/**" filters="none" />
<s:intercept-url pattern="/js/**" filters="none" />
<s:intercept-url pattern="/index.jsp" filters="none" />
<s:intercept-url pattern="/login.action"
access="hasAnyRole('ROLE_ANONYMOUS')" />
<s:intercept-url pattern="/logout.jsp"
access="hasAnyRole('ROLE_ANONYMOUS')" />
<s:intercept-url pattern="/main/**" access="hasAnyRole('ROLE_通用')" />
<s:intercept-url pattern="/public*"
access="hasAnyRole('ROLE_ANONYMOUS')" />
<s:intercept-url pattern="/public/test.action"
access="hasAnyRole('ROLE_ANONYMOUS')" />
<s:intercept-url pattern="/**" access="isAuthenticated()" />
<s:intercept-url pattern="/account/user!setSession*"
access="hasAnyRole('ROLE_通用')" />
<s:intercept-url pattern="/account/user!changepwd*"
access="hasAnyRole('ROLE_通用')" />
<s:intercept-url pattern="/account/user!save*" access="hasAnyRole('ROLE_修改用户')" />
<s:intercept-url pattern="/account/user!delete*"
access="hasAnyRole('ROLE_修改用户')" />
<s:intercept-url pattern="/account/user*" access="hasAnyRole('ROLE_浏览用户')" />
<s:intercept-url pattern="/account/role!save*" access="hasAnyRole('ROLE_修改角色')" />
<s:intercept-url pattern="/account/role!delete*"
access="hasAnyRole('ROLE_修改角色')" />
<s:intercept-url pattern="/account/role*" access="hasAnyRole('ROLE_浏览角色')" />
<s:intercept-url pattern="/lab/lab!save*" access="hasAnyRole('ROLE_修改实验室')" />
<s:intercept-url pattern="/lab/lab!delete*" access="hasAnyRole('ROLE_修改实验室')" />
<s:intercept-url pattern="/lab/lab*" access="hasAnyRole('ROLE_浏览实验室')" />
<s:form-login login-page="/login.action"
default-target-url="/main.action" authentication-failure-url="/login.action?error=true" />
<s:logout logout-success-url="/logout.jsp" />
<s:custom-filter before="FORM_LOGIN_FILTER" ref="appSessionProcessingFilter" />
</s:http>
<!-- 自定义成功和失败处理器,AppSessionSuccessHandler中设置了session -->
<bean id="appSessionProcessingFilter"
>
<property name="authenticationFailureHandler">
<bean
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/pages/Login/login.do?error=true" />
</bean>
</property>
<property name="authenticationSuccessHandler">
<bean class="mis.service.account.AppSessionSuccessHandler">
<property name="defaultTargetUrl" value="/" />
</bean>
</property>
<property name="authenticationManager" ref="authenticationManager"/>
<property name="filterProcessesUrl" value="/j_spring_security_check"/>
</bean>
<!-- 认证配置, 使用userDetailsService提供的用户信息 -->
<s:authentication-manager alias="authenticationManager">
<s:authentication-provider user-service-ref="userDetailsService">
<s:password-encoder hash="plaintext" />
</s:authentication-provider>
</s:authentication-manager>
<!-- 项目实现的用户查询服务 -->
<bean id="userDetailsService" class="mis.service.account.UserDetailsServiceImpl" />
</beans>
package mis.service.account;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import mis.dao.account.UserDao;
import mis.entity.account.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
public class AppSessionSuccessHandler extends
SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private UserDao userDao;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response,Authentication authentication)throws ServletException,IOException{
HttpSession session=request.getSession();
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
User currentUser = userDao.findUnique("loginname", userDetails.getUsername().toString());
session.setAttribute("currentUser", currentUser);
System.out.println("do it success");
super.onAuthenticationSuccess(request,response,authentication);}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}}