spring-session的配置
1.dependency
2.applicationContext.xml
3.web.xml
4.分布式
5.遇到的一些问题
1. dependency
spring-session提供了一个集成的jar包,只需要导入这一个就可以了.
<!-- spring session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.2.1.RELEASE</version>
</dependency>
<!-- ./spring session -->
该pom配置对应的jar包是空的,会导入以下一些依赖,你也可以直接引用以下依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.1.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.2.1.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
<scope>compile</scope>
</dependency>
有这些依赖就可以安心进行下一步了
2. applicationContext.xml
首先贴出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 security="none" pattern="/login"/>-->
<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:session-management invalid-session-url="/t1.html" >
<!– 第二个人登录,第一个人的认证会失效 –>
<s:concurrency-control max-sessions="1" />
<!– 第一个人登录后,第二个人无法用相同账户登录 –>
<!–<s:concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>–>
</s:session-management>-->
<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"/>
<!-- Simple namespace-based configuration -->
<s:authentication-manager>
<s:authentication-provider ref='secondLdapProvider' />
</s:authentication-manager>
<!-- This bean points at the embedded directory server created by the ldap-server element above -->
<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" />
<!-- <context:annotation-config/>-->
<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="password" value="${redis.pass}" />-->
<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="domainName" value="example.com"></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,而权限则是从数据库里取的.
2.1数据库获取权限数据 (customAuthoritiesPopulator)
这一步和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设置权限<s:intercept-url pattern="/admin/**" access="hasRole('admin')" />
在这里可以不用ROLE_前缀.
@Secured("ROLE_ADMIN")
public Object test(String v)
{
return "v="+v;
}
通过给方法加@Secured注解,也可以实现方法级别的权限控制.
自定义的两个类
public class CustomJdbcUserDetailsService extends JdbcDaoImpl
{
@Override
protected List<GrantedAuthority> 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);
}
}
2.2redis连接配置
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="password" value="${redis.pass}" />-->
<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>
2.3定制cookie
因为后端是有多个应用的,默认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="domainName" value="example.com"></property> -->
<property name="domainNamePattern" value="^.+?\.(\w+\.[a-z]+)$"></property>
</bean>
3.web.xml
web.xml里面只要增加一个filter,这里需要注意的是security和session使用的是同一个filter-class的类,但是作用不一样,所以都要写,而且springSecurityFilterChain的filter-mapping要在下.最后是org.springframework.web.context.ContextLoaderListener
加载文件
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
4.分布式
在其他项目中也配置security和session,未登录跳转到与之前同一个登录页面. web.xml也与第一个项目一样配置即可.applicationContext.xml大同小异.<authentication-manager>
不用再配置具体的验证,程序会自己去redies获取session.下面是其他应用的applicationContext.xml.
<?xml version="1.0" encoding="UTF-8"?>
<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:session-management invalid-session-url="/t1.html" >
<!– 第二个人登录,第一个人的认证会失效 –>
<s:concurrency-control max-sessions="1" />
<!– 第一个人登录后,第二个人无法用相同账户登录 –>
<!–<s:concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>–>
</s:session-management>-->
<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="password" value="${redis.pass}" />-->
<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="domainName" value="example.com"></property> -->
<property name="domainNamePattern" value="^.+?\.(\w+\.[a-z]+)$"></property>
</bean>
</beans>
5.遇到的一些问题
ERR_TOO_MANY_REDIRECTS
在配置过程中,难免需要参考一些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>session</servlet-name>
<servlet-class>com.xx.xx.security.SessionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>session</servlet-name>
<url-pattern>/session</url-pattern>
</servlet-mapping>
spring3.1.1.release
由于有多个应用,其中部分使用的springframework版本是3.1.1.release.这里使用的spring-security也是3.1.1.release.这个版本和4.0.3.release的登录接口不一样.(目前还无法将security-3.1.1和session整合)
3.1.1的登录接口和参数名
<form action="xxxx/j_spring_security_check" method="post">
用户名:<input type="text" name="j_username" />
密码:<input type="password" name="j_password" />
<button type="submit">登录</button>
</form>