SpringSecurity个人化配置
SpringSecurity个性化配置

要求在此基础上集成SpringSecurity,将表格的数据作为数据源来完成登录和权限校验逻辑
SpringSecurity的配置可通过两种方式呈现,基于自身的namespace配置和传统的基于Bean的配置。通过namespace来配置Security非常简洁,隐藏了很多繁琐的实现细节,但也不便于初学者进行理解,而如果要想对Security进行个性化定制(替换现有功能实现),最好还是采用传统的基于Bean的方式进行配置,虽然结构复杂,但是细节清晰明了
以下是两种方式的配置比较:
1.基于namespace来配置
一、首先需要修改userDetailsService的实现
在上述Demo配置中使用的是Spring内置的InMemoryUserDetailsManager,该类的主要作用是从配置文件加载zhangsan、wangwu等信息来构建用户数据源,而我们的用户数据是存储在数据库里的,因此需要修改实现,实现方式如下:
1.自定义一个Service,实现org.springframework.security.core.userdetails.UserDetailsService接口
在Demo配置中securityMetadataSource属性的配置是静态的,将每一个资源和资源对应的角色封装到<sec:intercept-url>标签里
而我们的需求场景是资源信息存储在数据库里,因此不能通过这种静态的方式去描述,修改方式如下:
1.声明一个Service实现org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource接口
SpringSecurity默认使用AffirmativeBased来进行访问权限控制,该类封装了很多AccessDecisionVoter对象,基于投票的机制来决定访问是否通过
AccessDecisionVoter之间是OR的逻辑(只要有一个AccessDecisionVoter判断权限通过,用户便可访问界面)。
在Demo配置里,使用的是WebExpressionVoter基于表达式的权限认证逻辑(hasRole('admin')),而我们的需求是将用户的角色和访问资源需要的角色进行对比,来判断该用户是否具有访问界面的权限,因此需要进行以下修改:
1.声明一个Service实现org.springframework.security.access.AccessDecisionVoter接口
应用场景
现有的数据库中包含以下几张表格用于权限管理要求在此基础上集成SpringSecurity,将表格的数据作为数据源来完成登录和权限校验逻辑
SpringSecurity的配置可通过两种方式呈现,基于自身的namespace配置和传统的基于Bean的配置。通过namespace来配置Security非常简洁,隐藏了很多繁琐的实现细节,但也不便于初学者进行理解,而如果要想对Security进行个性化定制(替换现有功能实现),最好还是采用传统的基于Bean的方式进行配置,虽然结构复杂,但是细节清晰明了
以下是两种方式的配置比较:
1.基于namespace来配置
<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.1.xsd"> <http pattern="/js/**" security="none" /> <http use-expressions="true" access-denied-page="/error.html"> <intercept-url pattern="/peoplemanage/**" access="hasRole('admin')" /> <form-login login-page='/login.jsp'/> <logout /> </http> <authentication-manager> <authentication-provider> <user-service> <user name="zhangsan" password="zhangsan" authorities="admin,user"/> <user name="wangwu" password="wangwu" authorities="user" /> </user-service> </authentication-provider> </authentication-manager> </beans:beans>2.同样的配置还原成Bean的方式
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:sec="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.1.xsd"> <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy"> <constructor-arg> <list> <sec:filter-chain pattern="/js/**" filters="none"/> <sec:filter-chain pattern="/**" filters="securityContextPersistenceFilter,authenticationFilter,exceptionTranslationFilter,filterSecurityInterceptor"/> </list> </constructor-arg> </bean> <bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager" ref="accessDecisionManager"/> <property name="securityMetadataSource"> <sec:filter-security-metadata-source use-expressions="true"> <sec:intercept-url pattern="/peoplemanage/**" access="hasRole('admin')"/> </sec:filter-security-metadata-source> </property> </bean> <!-- exceptionTranslationFilter --> <bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter"> <property name="authenticationEntryPoint" ref="authenticationEntryPoint"/> <property name="accessDeniedHandler" ref="accessDeniedHandler"/> </bean> <bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <property name="loginFormUrl" value="/login.jsp"/> </bean> <bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl"> <property name="errorPage" value="/error.html"/> </bean> <!-- securityContextPersistenceFilter --> <bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/> <!-- authenticationFilter --> <bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="filterProcessesUrl" value="/j_spring_security_check"/> </bean> <!-- Core Service --> <bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager"> <property name="providers"> <list> <ref local="daoAuthenticationProvider"/> </list> </property> </bean> <bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="inMemoryDaoImpl"/> </bean> <bean id="inMemoryDaoImpl" class="org.springframework.security.provisioning.InMemoryUserDetailsManager"> <constructor-arg name="users"> <props> <prop key="zhangsan">zhangsan,enabled</prop> <prop key="wangwu">wangwu,enabled</prop> </props> </constructor-arg> </bean> <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"> <property name="decisionVoters"> <list> <bean class="org.springframework.security.web.access.expression.WebExpressionVoter"></bean> </list> </property> </bean> </beans>还原成Bean的配置方式之后,在实现个性化的定制就变得清晰明了了。
一、首先需要修改userDetailsService的实现
在上述Demo配置中使用的是Spring内置的InMemoryUserDetailsManager,该类的主要作用是从配置文件加载zhangsan、wangwu等信息来构建用户数据源,而我们的用户数据是存储在数据库里的,因此需要修改实现,实现方式如下:
1.自定义一个Service,实现org.springframework.security.core.userdetails.UserDetailsService接口
public class MyUserDetailsService implements UserDetailsService { public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { /** * TODO 从数据库中加载用户信息,并封装成UserDetails对象 */ } }2.替换Demo中的对应的配置
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="myUserDetailsService"/> </bean> <bean id="myUserDetailsService" class="com.youcompany.MyUserDetailsService"/>二、修改filterSecurityInterceptor中securityMetadataSource属性的注入方式
在Demo配置中securityMetadataSource属性的配置是静态的,将每一个资源和资源对应的角色封装到<sec:intercept-url>标签里
而我们的需求场景是资源信息存储在数据库里,因此不能通过这种静态的方式去描述,修改方式如下:
1.声明一个Service实现org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource接口
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private Map<RequestMatcher, Collection<ConfigAttribute>> requestMap; public MyFilterInvocationSecurityMetadataSource(){ requestMap=new HashMap<RequestMatcher, Collection<ConfigAttribute>>(); loadMetadataInfo();//将数据库中的资源和角色实体封装到requestMap里 } private void loadMetadataInfo() { List<Resource> resources=...//TODO 获取数据库中所有的资源实体 for(Resource res:resources){ Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>(); List<Role> roles=...//TODO 获取该资源对应的访问角色 for(Role role:roles){ allAttributes.add(new SecurityConfig(role.getRoleName())); } RequestMatcher key=new AntPathRequestMatcher(res.getUrl()+"/**"); requestMap.put(key, allAttributes); } } public Collection<ConfigAttribute> getAllConfigAttributes() { Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>(); List<Role> roles...//TODO 获取库中所有的角色实体 for(Role role:roles){ allAttributes.add(new SecurityConfig(role.getRoleName())); } return allAttributes; } public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { HttpServletRequest request = ((FilterInvocation) object).getRequest(); for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) { if (entry.getKey().matches(request)) { return entry.getValue(); } } return null; } public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }2.修改Demo中相应的配置
<bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager" ref="accessDecisionManager"/> <property name="securityMetadataSource" ref="myFilterInvocationSecurityMetadataSource"/> </bean> <bean id="myFilterInvocationSecurityMetadataSource" class="com.youcompany.MyFilterInvocationSecurityMetadataSource"/>三、修改accessDecisionManager中decisionVoters的实现逻辑
SpringSecurity默认使用AffirmativeBased来进行访问权限控制,该类封装了很多AccessDecisionVoter对象,基于投票的机制来决定访问是否通过
AccessDecisionVoter之间是OR的逻辑(只要有一个AccessDecisionVoter判断权限通过,用户便可访问界面)。
在Demo配置里,使用的是WebExpressionVoter基于表达式的权限认证逻辑(hasRole('admin')),而我们的需求是将用户的角色和访问资源需要的角色进行对比,来判断该用户是否具有访问界面的权限,因此需要进行以下修改:
1.声明一个Service实现org.springframework.security.access.AccessDecisionVoter接口
public class MyAccessDecisionVoter implements AccessDecisionVoter<Object> { public boolean supports(ConfigAttribute attribute) { return true; } public boolean supports(Class<?> clazz) { return true; } public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { int result = ACCESS_DENIED; for (ConfigAttribute attribute : attributes) {//可访问该页面的角色 for (GrantedAuthority authority : authentication.getAuthorities()) {//登录用户具备的角色 if (attribute.getAttribute().equals(authority.getAuthority())) {//判断用户是否具有相应角色 return ACCESS_GRANTED; } } } return result; } }2.修改Demo中对应的配置
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"> <property name="decisionVoters"> <list> <bean class="com.youcompany.MyAccessDecisionVoter"></bean> </list> </property> </bean>至此,SpringSecurity个性化定制修改完成。有点长,部分代码加了TODO,有不理解的可与我联系,需要源码的网友可留邮箱
- 1楼u010144602昨天 09:53
- 好