1.dependency
2.applicationContext.xml
3.web.xml
4.分布式
5.遇到的一些问题
spring-session提供了一个集成的jar包,只需要导入这一个就可以了.
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
<version>1.2.1.RELEASEversion>
dependency>
该pom配置对应的jar包是空的,会导入以下一些依赖,你也可以直接引用以下依赖
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
<version>2.4.2version>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<version>1.7.1.RELEASEversion>
<scope>compilescope>
<exclusions>
<exclusion>
<artifactId>slf4j-apiartifactId>
<groupId>org.slf4jgroupId>
exclusion>
<exclusion>
<artifactId>jcl-over-slf4jartifactId>
<groupId>org.slf4jgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-sessionartifactId>
<version>1.2.1.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.8.1version>
<scope>compilescope>
dependency>
有这些依赖就可以安心进行下一步了
首先贴出applicationContext-security.xml的代码
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<s:http entry-point-ref="restAuthenticationEntryPoint" >
<s:csrf disabled="true"/>
<s:form-login authentication-success-handler-ref="mySuccessHandler" authentication-failure-handler-ref="myFailureHandler" default-target-url="/t3.html" />
<s:logout delete-cookies="JSESSIONID"/>
<s:intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<s:intercept-url pattern="/secure/**" access="permitAll"/>
<s:intercept-url pattern="/admin/**" access="hasRole('admin')" />
<s:intercept-url pattern="/**" access="authenticated"/>
s:http>
<bean id="mySuccessHandler" class="com.xx.xx.security.RestAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/index.html"/>
bean>
<bean id="myFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"/>
<s:authentication-manager>
<s:authentication-provider ref='secondLdapProvider' />
s:authentication-manager>
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldap://127.0.0.1:389/dc=xxx,dc=com"/>
<property name="userDn" value="xxx">property>
<property name="password" value="xxx">property>
bean>
<bean id="customJdbcUserDetailsService" class="com.xx.xx.security.CustomJdbcUserDetailsService">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="customAuthoritiesPopulator" class="com.xx.xx.security.CustomAuthoritiesPopulator">
<constructor-arg ref="customJdbcUserDetailsService">constructor-arg>
bean>
<bean id="secondLdapProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg>
<bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
<constructor-arg ref="contextSource" />
<property name="userSearch">
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value="OU=xxx"/>
<constructor-arg index="1" value="(sAMAccountName={0})"/>
<constructor-arg index="2" ref="contextSource" />
bean>
property>
bean>
constructor-arg>
<constructor-arg ref="customAuthoritiesPopulator" />
bean>
<s:global-method-security secured-annotations="enabled" />
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
<bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"/>
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" />
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<property name="hostName" value="${redis.host}"/>
<property name="port" value="${redis.port}" />
<property name="poolConfig" ref="poolConfig" />
bean>
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600">property>
bean>
<bean class="org.springframework.session.web.http.DefaultCookieSerializer">
<property name="cookieName" value="JSESSIONID">property>
<property name="cookiePath" value="/">property>
<property name="domainNamePattern" value="^.+?\.(\w+\.[a-z]+)$">property>
bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://1127.0.0.1:3306/xxx"/>
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
bean>
beans>
整合spring-security与spring-session之前,原security的配置保留.因为上一篇security的内容只用登录认证,并没有权限,而在实际项目中,认证使用的ldap,而权限则是从数据库里取的.
这一步和spring-session关系不大,算是spring-security的延伸.在认证成功之后,LdapAuthenticationProvider
会尝试通过调用配置过的LdapAuthoritiesPopulator
加载一系列的用户权限.DefaultLdapAuthoritiesPopulator
是它的一个实现类.
你可以通过实现 UserDetailsService
接口来定制认证, JdbcDaoImpl
是它的一个jdbc的实现.在本项目,因为要从数据库获取角色.所以创建了 JdbcDaoImpl
的子类CustomJdbcUserDetailsService
.这里重写了加载用户权限的方法loadUserAuthorities(String username)
.
本例中,由于要从数据库加载权限,所以用CustomAuthoritiesPopulator
实现该接口的getGrantedAuthorities(DirContextOperations user, String username)
方法.该方法返回 customJdbcUserDetailsService.loadUserAuthorities(username)
的返回值.这样就可以获取数据库里的权限数据了.这里需要注意的是loadUserAuthorities(username)
是protected
的方法,所以自己定义的这两个类要在同一个包下才有权限调用.
数据库的权限表,authority内容默认要带ROLE_,不然应用会不认
REATE TABLE `authorities` (
`username` varchar(255) DEFAULT NULL,
`authority` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
因为sql都是在JdbcDaoImpl里,其实也可以自己定义,只要把对应的方法写对就行了
通过设置access的值,给url设置权限
在这里可以不用ROLE_前缀.
@Secured("ROLE_ADMIN")
public Object test(String v)
{
return "v="+v;
}
通过给方法加@Secured注解,也可以实现方法级别的权限控制.
自定义的两个类
public class CustomJdbcUserDetailsService extends JdbcDaoImpl
{
@Override
protected List loadUserAuthorities(String username)
{
return super.loadUserAuthorities(username);
}
}
public class CustomAuthoritiesPopulator implements LdapAuthoritiesPopulator
{
private static Logger logger = LoggerFactory.getLogger(CustomAuthoritiesPopulator.class);
private CustomJdbcUserDetailsService customJdbcUserDetailsService;
public CustomAuthoritiesPopulator(CustomJdbcUserDetailsService customJdbcUserDetailsService)
{
this.customJdbcUserDetailsService = customJdbcUserDetailsService;
}
public Collection extends GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username)
{
return customJdbcUserDetailsService.loadUserAuthorities(username);
}
}
redis连接把对应参数配置好,在定义一个最大闲置时间间隔,就可以用了
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" />
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<property name="hostName" value="${redis.host}"/>
<property name="port" value="${redis.port}" />
<property name="poolConfig" ref="poolConfig" />
bean>
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600">property>
bean>
因为后端是有多个应用的,默认cookie的path是app/
,所以要修改成/
.这里还可以定制认证cookie的名字.spring-session默认的是session,我们这里依据spring-security,还是继续使用JESSIONID.
<bean class="org.springframework.session.web.http.DefaultCookieSerializer">
<property name="cookieName" value="JSESSIONID">property>
<property name="cookiePath" value="/">property>
<property name="domainNamePattern" value="^.+?\.(\w+\.[a-z]+)$">property>
bean>
web.xml里面只要增加一个filter,这里需要注意的是security和session使用的是同一个filter-class的类,但是作用不一样,所以都要写,而且springSecurityFilterChain的filter-mapping要在下.最后是org.springframework.web.context.ContextLoaderListener
加载文件
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter>
<filter-name>springSessionRepositoryFilterfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilterfilter-name>
<url-pattern>/*url-pattern>
<dispatcher>REQUESTdispatcher>
<dispatcher>ERRORdispatcher>
filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChainfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
在其他项目中也配置security和session,未登录跳转到与之前同一个登录页面. web.xml也与第一个项目一样配置即可.applicationContext.xml大同小异.
不用再配置具体的验证,程序会自己去redies获取session.下面是其他应用的applicationContext.xml.
<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.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
">
<s:http entry-point-ref="restAuthenticationEntryPoint" >
<s:csrf disabled="true"/>
<s:form-login authentication-success-handler-ref="mySuccessHandler" authentication-failure-handler-ref="myFailureHandler" default-target-url="/t3.html" />
<s:logout delete-cookies="JSESSIONID"/>
<s:intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<s:intercept-url pattern="/secure/**" access="permitAll"/>
<s:intercept-url pattern="/admin/**" access="hasRole('admin')" />
<s:intercept-url pattern="/**" access="authenticated"/>
s:http>
<bean id="mySuccessHandler" class="com.xxx.security.RestAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/index.html"/>
bean>
<bean id="myFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"/>
<s:authentication-manager>
s:authentication-manager>
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" />
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<property name="hostName" value="${redis.host}"/>
<property name="port" value="${redis.port}" />
<property name="poolConfig" ref="poolConfig" />
bean>
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600">property>
bean>
<bean class="org.springframework.session.web.http.DefaultCookieSerializer">
<property name="cookieName" value="JSESSIONID">property>
<property name="cookiePath" value="/">property>
<property name="domainNamePattern" value="^.+?\.(\w+\.[a-z]+)$">property>
bean>
beans>
在配置过程中,难免需要参考一些sample,当时在github上找到一个,它自己写了一个SessionServlet.把请求重定向到contextPath+"/"
,结果就是程序访问请求都被重定向 ‘app/’,最后就出不来了.它这个具体干嘛的,其实没搞清楚,但是还是值得记录一下.
@WebServlet("/session")
public class SessionServlet extends HttpServlet
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String attributeName = req.getParameter("attributeName");
String attributeValue = req.getParameter("attributeValue");
req.getSession().setAttribute(attributeName, attributeValue);
resp.sendRedirect(req.getContextPath() + "/");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String attributeName = req.getParameter("attributeName");
String attributeValue = req.getParameter("attributeValue");
req.getSession().setAttribute(attributeName, attributeValue);
resp.sendRedirect(req.getContextPath() + "/");
}
private static final long serialVersionUID = 2878267318695777395L;
}
<servlet>
<servlet-name>sessionservlet-name>
<servlet-class>com.xx.xx.security.SessionServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>sessionservlet-name>
<url-pattern>/sessionurl-pattern>
servlet-mapping>
由于有多个应用,其中部分使用的springframework版本是3.1.1.release.这里使用的spring-security也是3.1.1.release.这个版本和4.0.3.release的登录接口不一样.(目前还无法将security-3.1.1和session整合)
3.1.1的登录接口和参数名