文件:resource/config/spring/applicationContext-security.xml
<sec:logout invalidate-session="true"
logout-url="/logout.do"
logout-success-url="/" />
invalidate-session:登出時銷毀 Sessionlogout-url:觸發登出的 URLlogout-success-url:登出成功跳轉頁面(此處跳首頁)<sec:session-management session-authentication-strategy-ref="concurrentSessionControlStrategy"/>
<bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<constructor-arg name="sessionRegistry" ref="sessionRegistry"/>
<constructor-arg name="expiredUrl" value="/user/timeout"/>
</bean>
<bean id="concurrentSessionControlStrategy"
class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<constructor-arg name="sessionRegistry" ref="sessionRegistry"/>
<property name="maximumSessions" value="1"/> <!-- 單點登錄 -->
</bean>
<bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>
maximumSessions=1:同一賬號僅允許一個會話,新登入踢舊expiredUrl:會話過期/被踢跳轉頁面SessionRegistryImpl:會話註冊表,存儲會話信息<sec:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>
<sec:custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER"/>
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="authenticationManager" ref="myAuthenticationManager"/>
<property name="securityMetadataSource" ref="resourceSecurityMetadataSource"/>
</bean>
<sec:authentication-manager alias="myAuthenticationManager">
<sec:authentication-provider ref="daoAuthenticationProvider"/>
</sec:authentication-manager>
<bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="messageSource" ref="messageSource"/>
<property name="passwordEncoder" ref="messageDigestPasswordEncoder"/>
<property name="userDetailsService" ref="cachingUserDetailsService"/>
<property name="saltSource" ref="saltSource"/>
<property name="hideUserNotFoundExceptions" value="false"/>
</bean>
passwordEncoder:MD5 加密(見 service 配置)userDetailsService:用戶詳情查詢服務(帶緩存)saltSource:鹽值來源(用戶名)hideUserNotFoundExceptions=false:拋出用戶不存在異常,前端可提示<bean id="cachingUserDetailsService"
class="org.springframework.security.config.authentication.CachingUserDetailsService">
<constructor-arg name="delegate" ref="webUserDetailsService"/>
<property name="userCache">
<bean class="org.springframework.security.core.userdetails.cache.EhCacheBasedUserCache">
<property name="cache" ref="userEhCacheFactory"/>
</bean>
</property>
</bean>
登錄高頻操作,引入 EhCache 緩存用戶詳情,減少數據庫查詢。
文件:applicationContext-service.xml
<context:component-scan base-package="com.magic.rent.service"/>
<bean id="exception" class="com.magic.rent.exception.exhandler.CustomExceptionHandler"/>
<!-- MD5 密碼編碼器 -->
<bean id="messageDigestPasswordEncoder"
class="org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder">
<constructor-arg name="algorithm" value="MD5"/>
</bean>
<!-- 國際化消息源 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>
<bean id="messageSourceAccessor" class="org.springframework.context.support.MessageSourceAccessor">
<constructor-arg ref="messageSource"/>
</bean>
<!-- 鹽值來源:用戶名 -->
<bean id="saltSource" class="org.springframework.security.authentication.dao.ReflectionSaltSource">
<property name="userPropertyToUse" value="username"/>
</bean>
Spring Security 內置中文消息:org/springframework/security/messages_zh_CN.properties
需將中文轉 Unicode,提供工具類 UTF8Util(GBK2Unicode / GBK2Unicode2)。
@Service
public class ResourceSecurityMetadataSource implements FilterInvocationSecurityMetadataSource, InitializingBean {
@Autowired SysResourcesMapper sysResourcesMapper;
// 核心:根據請求 URL 返回所需權限集合
public Collection<ConfigAttribute> getAttributes(Object object) { ... }
// 初始化:從數據庫加載構建 RequestMatcher -> 權限映射
public void afterPropertiesSet() { this.requestMap = bindRequestMap(); }
// 手動刷新權限(無需重啟)
public void refreshResuorceMap() { this.requestMap = bindRequestMap(); }
}
@Service
public class WebUserDetailsService implements UserDetailsService {
@Autowired SysUsersMapper sysUsersMapper;
@Autowired SysRolesMapper sysRolesMapper;
@Autowired SysAuthoritiesMapper sysAuthoritiesMapper;
@Autowired MessageSourceAccessor messageSourceAccessor;
public UserDetails loadUserByUsername(String username) {
SysUsers user = sysUsersMapper.selectByUserName(username);
if (user == null) throw new UsernameNotFoundException(...);
// 查詢角色 -> 查詢權限 -> 封裝 GrantedAuthority
Set<GrantedAuthority> authorities = new HashSet<>();
// 去重邏輯...
user.setAuthorities(authorities);
return user; // SysUsers 實現 UserDetails
}
}
public class CustomGrantedAuthority implements GrantedAuthority, Serializable {
private final String authority;
public CustomGrantedAuthority(String authority) { this.authority = authority; }
public String getAuthority() { return authority; }
// equals/hashCode/toString 基於 authority
}
<bean id="methodSecurityInterceptor"
class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="authenticationManager" ref="myAuthenticationManager"/>
<property name="securityMetadataSource" ref="methodSecurityMetadataSource"/>
</bean>
<aop:config>
<aop:advisor advice-ref="methodSecurityInterceptor"
pointcut="execution(* com.magic.rent.service.*.*(..))" order="1"/>
</aop:config>
對 Service 層所有方法攔截,根據方法簽名匹配權限。
@Service
public class MethodSecurityMetadataSource extends AbstractMethodSecurityMetadataSource implements InitializingBean {
@Autowired SysResourcesMapper sysResourcesMapper;
// MethodKey 封裝 類+方法 為 key
// 從數據庫加載 方法-權限 映射
// 支持手動刷新(待補充)
}

本文完成了 Spring Security 核心配置:登出、單點登錄、資源/方法級權限攔截、自定義認證服務、EhCache 緩存、國際化、動態權限刷新。配合前端 Bootstrap + 插件,構建了完整的權限管理系統基礎框架。