前面文章介绍了利用nginx的ip_hash和redis实现共享session,这里继续讨论session,其实spring全家桶里面已经用redis给你实现了一个共享session,其项目名为spring-session。它不单单解决了共享session,在其它场景也可以使用,比如webSocket等等,这里只是简单介绍其作为共享session的使用。
原理
其实现的原理是通过实现一个filter SessionRepositoryFilter。在该filter中把request和response包装到SessionRepositoryRequestWrapper 和SessionRepositoryResponseWrapper 之中,其中提供了把session存入redis的操作。其中通过httpSessionStrategy接口指定session传递的方式,目前有通过http 头信息或者Cookie两种实现。分别是HeaderHttpSessionStrategy和CookieHttpSessionStrategy。
最后通过filter链,把被包装好的request和response往下传递。
可以看到,必须把SessionRepositoryFilter设置为第一个filter
SessionRepositoryFilter的源码
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
wrappedRequest, response);
HttpServletRequest strategyRequest = this.httpSessionStrategy
.wrapRequest(wrappedRequest, wrappedResponse);
HttpServletResponse strategyResponse = this.httpSessionStrategy
.wrapResponse(wrappedRequest, wrappedResponse);
try {
filterChain.doFilter(strategyRequest, strategyResponse);
}
finally {
wrappedRequest.commitSession();
}
HttpServletRequest strategyRequest = this.httpSessionStrategy
.wrapRequest(wrappedRequest, wrappedResponse);
HttpServletResponse strategyResponse = this.httpSessionStrategy
.wrapResponse(wrappedRequest, wrappedResponse);
}
使用
知道原理后,使用方式就很容易理解了。这里以http头信息的方式进行介绍,客户端每次请求服务时,需要在请求头中添加token,spring-session将通过该token在redis去查找,如果存在该token,那么服务端通过request可以获得已经存在的session,此时在response的头信息中不会对客户端返回任何spring-session相关的内容。否则,将重新创建一个新的session,并把新的token添加到response的头信息中。用户可以保存该token,并且在下次请求中把该token放到request的头信息中。该头信息的键为x-auth-token
spring-boot整合spring-session
1、spring-boot需要先整合redis,这里不做多介绍
2、配置spring-session
可以通过java-config的方式,其中可以设置session传递的策略
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 60*60, redisNamespace = "applicationA")
public class HttpSessionConfig {
@Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
使用spring-boot,spring-session就这样很容易的整合进去了
例子
这里提供一个例子,用户通过http://localhost:8888/login/admin/123456地址登陆,如果成功,将返回一个token。用户用该token可以进行其他操作,比如通过http://localhost:8888/rest/user获得用户登录名。如果session过期或者token不正确,将跳转到主界面。
AuthFilter.java
进行权限判断
public class AuthFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req=(HttpServletRequest) request;
HttpServletResponse resp=(HttpServletResponse) response;
HttpSession session = req.getSession();
if(session.isNew()){
System.out.println("不是登录状态,跳转到登录页面!");
resp.sendRedirect("/index.html");
}else{
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
UserController.java
提供登录和获取用户登录名服务
@RestController
public class UserController {
@RequestMapping(value = "/rest/user",
method = RequestMethod.GET,
produces = MediaType.TEXT_PLAIN_VALUE+";charset=UTF-8")
public void getUser(HttpServletRequest request,HttpServletResponse response){
HttpSession session = request.getSession();
String loginname = (String) session.getAttribute("loginname");
writeJson(loginname + " : " + session.getId(),response);
}
@RequestMapping(value = "/login/{loginname}/{password}",
method = RequestMethod.POST,
produces = MediaType.TEXT_PLAIN_VALUE+";charset=UTF-8")
public void getUser(@PathVariable final String loginname,@PathVariable final String password,HttpServletRequest request,HttpServletResponse response){
if(loginname.equals("admin") && password.equals("123456")){
HttpSession session = request.getSession();
session.setAttribute("loginname", loginname);
writeJson("登录成功!",response);
}else{
System.out.println("用户信息验证失败!");
writeJson("无效的用户信息!",response);
}
}
public void writeJson(Object object,HttpServletResponse response)
{
try
{
//DisableCircularReferenceDetect避免$ref问题
String json = JSON.toJSONStringWithDateFormat(object, "yyyy-MM-dd HH:mm:ss",SerializerFeature.DisableCircularReferenceDetect);
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(json);
response.getWriter().flush();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
ClientTest.java
测试代码,loginTest进行登录,登录后把获得的token放到getSessionTest方法中的header中,从而获得用户的登录名
public class ClientTest {
@Test
public void getSessionTest() throws Exception{
String url="http://localhost:8888/rest/user";
CloseableHttpClient httpclient=HttpClients.createDefault();
HttpGet httpget=null;
httpget=new HttpGet(url);
httpget.addHeader("x-auth-token","7cf9c14b-4fa2-4893-9563-b66db306cc2d");
CloseableHttpResponse response=httpclient.execute(httpget);
Header[] header=response.getHeaders("x-auth-token");
String token="";
if(header.length>0){
token=header[0].getValue();
System.out.println("获得token:"+token);
}else{
System.out.println("没有返回token");
}
String rs=parseResponse(response);
System.out.println(rs);
}
//@Test
public void loginTest() throws Exception{
String url="http://localhost:8888/login/admin/123456";
CloseableHttpClient httpclient=HttpClients.createDefault();
HttpPost httppost=new HttpPost(url);
CloseableHttpResponse response=httpclient.execute(httppost);
Header[] header=response.getHeaders("x-auth-token");
String token="";
if(header.length>0){
token=header[0].getValue();
System.out.println("获得token:"+token);
}else{
System.out.println("没有返回token");
}
String rs=parseResponse(response);
System.out.println(rs);
}
public static String parseResponse(HttpResponse response) throws UnsupportedOperationException, IOException{
String rs="";
HttpEntity entity=response.getEntity();
if(entity!=null){
InputStream instream=entity.getContent();
rs=convertStreamToString(instream);
}
return rs;
}
public static String convertStreamToString(InputStream is){
StringBuilder sb=new StringBuilder();
byte[] bytes=new byte[4096];
int size=0;
try{
while((size=is.read(bytes))>0){
String str=new String(bytes,0,size,"UTF-8");
sb.append(str);
}
}catch(IOException e){
e.printStackTrace();
}finally{
try{
is.close();
}catch(IOException e){
e.printStackTrace();
}
}
return sb.toString();
}
}
最后附上配置信息
server:
port: 8888
spring:
redis:
dbIndex: 1
hostName: 192.168.58.140
#password: nmamtf
port: 6379
timeout: 0
poolConfig:
maxIdle: 10
minIdle: 0
maxActive: 10
maxWait: -1
Application.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
AuthFilter authFilter = new AuthFilter ();
registrationBean.setFilter(authFilter);
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/rest/*");
registrationBean.setUrlPatterns(urlPatterns);
return registrationBean;
}
}
RedisConfig.java
@Configuration
@EnableAutoConfiguration
public class RedisConfig {
@Bean
@ConfigurationProperties(prefix="spring.redis.poolConfig")
public JedisPoolConfig getRedisConfig(){
JedisPoolConfig config = new JedisPoolConfig();
return config;
}
@Bean
@ConfigurationProperties(prefix="spring.redis")
public JedisConnectionFactory getConnectionFactory(){
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setUsePool(true);
JedisPoolConfig config = getRedisConfig();
factory.setPoolConfig(config);
return factory;
}
@Bean
public RedisTemplate<?, ?> getRedisTemplate(){
RedisTemplate<?,?> template = new StringRedisTemplate(getConnectionFactory());
return template;
}
}
HttpSessionConfig.java
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 60*60, redisNamespace = "applicationA")
public class HttpSessionConfig {
@Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
例子的下载地址:https://github.com/wulinfeng2/spring-session-demo