今天 项目需要用户退出的时候记录登出日志,修改用户的登录状态
<?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"/> |