使用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;
	}}