集群与负载均衡系列(3)——spring-session实现共享session

               前面文章介绍了利用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 urlPatterns = new ArrayList();  
        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

              

你可能感兴趣的:(java,web,redis)