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

xiaoxiao2021-02-28  90

               前面文章介绍了利用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

              

转载请注明原文地址: https://www.6miu.com/read-50498.html

最新回复(0)