1. 程式人生 > >spring secrity 登出做一些操作

spring secrity 登出做一些操作

今天 專案需要使用者退出的時候記錄登出日誌,修改使用者的登入狀態


<?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.2.xsd">


	<global-method-security pre-post-annotations="enabled" />
	<!-- HTTP安全配置 -->
	<http auto-config="false" entry-point-ref="authenticationEntryPoint"  access-denied-page="/denied.html">
		<intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<intercept-url pattern="/index.html" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<intercept-url pattern="/m/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<intercept-url pattern="/commons/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<intercept-url pattern="/upload/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <intercept-url pattern="/cms/**" access="ROLE_USER"/>
       <!--  <intercept-url pattern="/adminIndex.html" access="ROLE_USER"/> -->
        <intercept-url pattern="/pages/*.html" access="ROLE_USER"/>
       <!--  logout-success-url="/login.html" -->
        <logout logout-url="/j_spring_security_logout" invalidate-session="true" 
            delete-cookies="JSESSIONID" success-handler-ref="myLogoutSuccessHandler"/>
        
        <custom-filter ref="corsFilter" after="PRE_AUTH_FILTER"/>
		<custom-filter ref="myLoginFilter" position="FORM_LOGIN_FILTER" />
		<custom-filter ref="mySecurityFilter" before="FILTER_SECURITY_INTERCEPTOR" />
	</http>
	
	<beans:bean id="corsFilter" class="com.threeti.danfoss.base.filter.SecurityCorsFilter" />
	
	<beans:bean id="sas"
		class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
		<beans:property name="maximumSessions" value="1"></beans:property>
		<beans:property name="exceptionIfMaximumExceeded"
			value="true"></beans:property>
		<beans:constructor-arg name="sessionRegistry"
			ref="sessionRegistry"></beans:constructor-arg>
	</beans:bean>
	
	<beans:bean id="sessionRegistry"
		class="org.springframework.security.core.session.SessionRegistryImpl"></beans:bean>

	<beans:bean id="myLoginFilter"
		class="com.threeti.danfoss.base.filter.MyUsernamePasswordAuthenticationFilter">
		<beans:property name="authenticationManager" ref="myAuthenticationManager"/>  
        <beans:property name="authenticationFailureHandler" ref="failureHandler"/>  
        <beans:property name="authenticationSuccessHandler" ref="successHandler"/>  
		<beans:property name="sessionAuthenticationStrategy"
			ref="sas"></beans:property>
	</beans:bean>

	
	  
    <beans:bean id="successHandler" class="com.threeti.danfoss.base.handler.MyAuthenticationSuccessHandler">  
        <beans:property name="defaultTargetUrl" value="/pages/menu.html#current/wind/surface/level/anim=off/overlay=misery_index/orthographic=39.08,42.42,294/loc=96.475,39.357" />  
    </beans:bean>  
   
    <beans:bean id="failureHandler" class="com.threeti.danfoss.base.handler.MySimpleUrlAuthenticationFailureHandler">  
        <beans:property name="defaultFailureUrl" value="/index.html"/>  
    </beans:bean> 
    
    <beans:bean id="myLogoutSuccessHandler" class="com.threeti.danfoss.base.handler.MyLogoutSuccessHandler">
       	<beans:property name="defaultTargetUrl" value="/login.html"/>
       	<!-- 下面的 是通過在url引數進行跳轉 -->
       	<!-- <property name="targetUrlParameter" value="target-url"/>
      	<property 
name="redirectStrategy" ref="safeRedirectStrategy"/> --> </beans:bean> <!-- 安全的RedirectStrategy,主要是判斷跳轉地址是否在白名單中 public class SafeRedirectStrategy implements RedirectStrategy --> <!-- <beans:bean id="safeRedirectStrategy" class="com.snsxiu.job.web.security.SafeRedirectStrategy"/> --> <!-- 1.URL過濾器或方法攔截器:用來攔截URL或者方法資源對其進行驗證,其抽象基類為AbstractSecurityInterceptor 2.資源許可權獲取器:用來取得訪問某個URL或者方法所需要的許可權,介面為SecurityMetadataSource 3.訪問決策器:用來決定使用者是否擁有訪問許可權的關鍵類,其介面為AccessDecisionManager 呼叫順序為:AbstractSecurityInterceptor呼叫SecurityMetadataSource取得資源的所有可訪問許可權, 然後再呼叫AccessDecisionManager來實現決策,確定使用者是否有許可權訪問該資源。 --> <!-- 自定義的filter, 必須包含authenticationManager, accessDecisionManager, securityMetadataSource三個屬性 --> <beans:bean id="mySecurityFilter" class="com.threeti.danfoss.base.security.XaFilterSecurityInterceptor"> <beans:property name="authenticationManager" ref="myAuthenticationManager" /> <beans:property name="accessDecisionManager" ref="myAccessDecisionManager" /> <beans:property name="securityMetadataSource" ref="mySecurityMetadataSource" /> </beans:bean> <!-- 取HTTP配置中的authenticationManager 設定alias別名 --> <authentication-manager alias="myAuthenticationManager"> <authentication-provider user-service-ref="userDetailsManager"> <password-encoder hash="md5"/> </authentication-provider> </authentication-manager> <!-- 使用者詳細資訊管理:資料來源、使用者快取(通過資料庫管理使用者、角色、許可權、資源) --> <beans:bean id="userDetailsManager" class="com.threeti.danfoss.base.security.XaUserDetailsService"> </beans:bean> <!-- 訪問決策器,決定某個使用者具有的角色,是否有足夠的許可權去訪問某個資源。 --> <beans:bean id="myAccessDecisionManager" class="com.threeti.danfoss.base.security.XaAccessDecisionManagerService" /> <!-- 資源源資料定義,將所有的資源和許可權對應關係建立起來,即定義某一資源可以被哪些角色去訪問。 --> <beans:bean id="mySecurityMetadataSource" init-method="loadResourceDefine" class="com.threeti.danfoss.base.security.XaSecurityMetadataSourceService"> </beans:bean> <beans:bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <beans:property name="loginFormUrl" value="/login.html" /> </beans:bean> </beans:beans>

java 程式碼


package com.threeti.danfoss.base.handler;


import java.io.IOException;


import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;


import com.threeti.danfoss.base.constant.XaConstant;
import com.threeti.danfoss.base.entity.XaCmsUser;
import com.threeti.danfoss.base.repository.XaCmsUserRepository;
import com.threeti.danfoss.base.security.XaUserDetails;


public class MyLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler 
 

implements LogoutSuccessHandler{
@Autowired
private XaCmsUserRepository xaCmsUserRepository;

@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
XaUserDetails user = (XaUserDetails) authentication.getPrincipal();

XaCmsUser u = 
xaCmsUserRepository.findByUserName(user.getUsername(),XaConstant.Status.valid);
u.setOnline(0);
xaCmsUserRepository.save(u);
super.onLogoutSuccess(request, response, authentication);
}


}



還有另外一種方式:下面的就是複製前面的東西。沒有進行測試,先記錄下來,有空進行測試:

這裡是前輩的文章地址:http://www.111cn.net/jsp/J2ME/60099.htm

spring security的form-login提供了default-target-url作為登入成功後的跳轉地址,唯獨沒有允許傳遞一個redirectUrl引數來作為成功後的跳轉地址。
同樣的logout標籤提供了logout-success-url作為退出成功後的跳轉地址,也沒有提供允許傳遞redirectUrl引數來進行跳轉。
本來打算自己實現和AdminAuthSuccessHandler和LogoutSuccessHandler來接收redirectUrl引數進行跳轉的,結果檢視spring security的程式碼無意間發現spring security居然提供了targetUrlParameter作為跳轉地址的引數,只是Security Namespace沒有相關的標籤或屬性。於是在讀完跳轉相關的程式碼之後自己寫了以下配置。
注:如果直接允許傳遞redirectUrl作為跳轉地址,會有一定的安全風險。在使用之前,確保redirectUrl可信。下面的這段配置有一定安全隱患。

 程式碼如下 複製程式碼
<sec:http auto-config="true">
    <sec:intercept-url pattern="/admin/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
    <sec:form-login login-page="/admin/login" authentication-failure-handler-ref="adminAuthFailureHandler" authentication-success-handler-ref="adminAuthSuccessHandler"/>
    <sec:logout success-handler-ref="adminLogoutSuccessHandler"/>
    <sec:remember-me data-source-ref="jobDataSource"/>
    <sec:session-management>
        <sec:concurrency-control error-if-maximum-exceeded="true" max-sessions="1"/>
    </sec:session-management>
</sec:http>
 
<bean id="adminAuthFailureHandler" class="com.snsxiu.job.handler.AdminAuthFailureHandler">
      <property name="authenticationFailureUrl" value="/admin/login?error=true"/>
</bean>
   
<bean id="adminAuthSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
    <property name="targetUrlParameter" value="target-url"/>
    <property name="defaultTargetUrl" value="/admin/index"/>
</bean>
   
<bean id="adminLogoutSuccessHandler" class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler">
    <property name="targetUrlParameter" value="target-url"/>
    <property name="defaultTargetUrl" value="/admin/login"/>
</bean>

通過檢視原始碼我們發現,SimpleUrlLogoutSuccessHandler繼續了AbstractAuthenticationTargetUrlRequestHandler,同樣的SavedRequestAwareAuthenticationSuccessHandler也是繼承了AbstractAuthenticationTargetUrlRequestHandler,而在AbstractAuthenticationTargetUrlRequestHandler的handle方法裡,我們發現它是通過determineTargetUrl()方法來決定Redirect地址的。

 程式碼如下 複製程式碼
public class SimpleUrlLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler
        implements LogoutSuccessHandler {
 
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        super.handle(request, response, authentication);
    }
 
}

通過分析determineTargetUrl我們得出以下結論:
①如果alwaysUseDefaultTargetUrl為true,則跳轉地址始終為defaultTargetUrl
②如果targetUrlParameter不為null,則從request中targetUrlParameter指定的引數名獲取跳轉地址
③如果useReferer為true,並且前兩步沒有獲取到跳轉地址,則從請求頭中的Referer獲取跳轉地址
④如果以上幾步都為空,則使用設定的defaultTargetUrl作為跳轉地址
⑤defaultTargetUrl的預設值是"/"
 
在最終跳轉時,spring security使用一個RedirectStrategy策略來進行跳轉,一般都是使用DefaultRedirectStrategy來進行跳轉,你也可以實現自己的RedirectStrategy並配置在adminAuthSuccessHandler的bean定義中。
 
在剛開始的時候我們說過“如果直接允許傳遞redirectUrl作為跳轉地址,會有一定的安全風險”。如果別人通過你的login或logout傳遞redirectUrl引數誤導使用者跳到了病毒木馬網站,那這肯定是你的安全做的不夠,為了安全我們,我們需要實現自己的SafeRedirectStrategy,如下所示,在跳轉之前對URL進行了白名單校驗。

 程式碼如下 複製程式碼
/**
 * 安全的RedirectStrategy,主要是判斷跳轉地址是否在白名單中
 * @author guoweiwei [email protected]
 *
 */
public class SafeRedirectStrategy implements RedirectStrategy {
 
    protected final Log logger = LogFactory.getLog(getClass());
 
    private boolean contextRelative;
 
    /**
     * Redirects the response to the supplied URL.
     * <p>
     * If <tt>contextRelative</tt> is set, the redirect value will be the value after the request context path. Note
     * that this will result in the loss of protocol information (HTTP or HTTPS), so will cause problems if a
     * redirect is being performed to change to HTTPS, for example.
     */
    public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
        String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
         
 
        try {
            if(UrlUtils.isAbsoluteUrl(redirectUrl)){
                        redirectUrl = UrlUtil.buildRedirectLink(redirectUrl, false);
                    }
        } catch (Exception e) {
            throw new IOException("error redirect url", e);
        }
 
         
        redirectUrl = response.encodeRedirectURL(redirectUrl);
 
        if (logger.isDebugEnabled()) {
            logger.debug("Redirecting to '" + redirectUrl + "'");
        }
 
        response.sendRedirect(redirectUrl);
    }
 
    private String calculateRedirectUrl(String contextPath, String url) {
        if (!UrlUtils.isAbsoluteUrl(url)) {
            if (contextRelative) {
                return url;
            } else {
                return contextPath + url;
            }
        }
 
        // Full URL, including http(s)://
 
        if (!contextRelative) {
            return url;
        }
 
        // Calculate the relative URL from the fully qualified URL, minus the scheme and base context.
        url = url.substring(url.indexOf("://") + 3); // strip off scheme
        url = url.substring(url.indexOf(contextPath) + contextPath.length());
 
        if (url.length() > 1 && url.charAt(0) == '/') {
            url = url.substring(1);
        }
 
        return url;
    }
 
    /**
     * If <tt>true</tt>, causes any redirection URLs to be calculated minus the protocol
     * and context path (defaults to <tt>false</tt>).
     */
    public void setContextRelative(boolean useRelativeContext) {
        this.contextRelative = useRelativeContext;
    }
 
}

上面程式碼中UrlUtil為自己實現的一個Url處理工具,buildRedirectLink會對url做白名單校驗。完了別忘了在servlet.xml的bean配置中加入safeRedirectStrategy。如下所示:

 程式碼如下 複製程式碼
<bean id="adminAuthSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
    <property name="targetUrlParameter" value="target-url"/>
    <property name="defaultTargetUrl" value="/admin/index"/>
    <property name="redirectStrategy" ref="safeRedirectStrategy"/>
  </bean>
   
  <bean id="adminLogoutSuccessHandler" class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler">
      <property name="targetUrlParameter" value="target-url"/>
      <property name="defaultTargetUrl" value="/admin/login"/>
      <property name="redirectStrategy" ref="safeRedirectStrategy"/>
  </bean>
   
  <bean id="safeRedirectStrategy" class="com.snsxiu.job.web.security.SafeRedirectStrategy"/>