SpringBoot+Shiro+Redis共享Session实例

  • 2019 年 10 月 4 日
  • 筆記

作者:小LUA

来源:http://suo.im/4wM0sx(点击阅读全文前往)

在单机版的Springboot+Shiro的基础上,这次实现共享Session。

这里没有自己写RedisManager、SessionDAO。用的 crazycake 写的开源插件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">      <modelVersion>4.0.0</modelVersion>        <groupId>com.example</groupId>      <artifactId>demo</artifactId>      <version>0.0.1-SNAPSHOT</version>      <packaging>jar</packaging>        <name>demo</name>      <description>Demo project for Spring Boot</description>        <parent>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-parent</artifactId>          <version>2.0.3.RELEASE</version>          <relativePath/> <!-- lookup parent from repository -->      </parent>        <properties>          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>          <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>          <java.version>1.8</java.version>      </properties>        <dependencies>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-data-redis</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-thymeleaf</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-web</artifactId>          </dependency>          <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->          <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-all</artifactId>              <version>1.3.2</version>          </dependency>          <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->          <dependency>              <groupId>com.alibaba</groupId>              <artifactId>fastjson</artifactId>              <version>1.2.47</version>          </dependency>          <!-- https://mvnrepository.com/artifact/org.crazycake/shiro-redis -->          <dependency>              <groupId>org.crazycake</groupId>              <artifactId>shiro-redis</artifactId>              <version>3.1.0</version>          </dependency>            <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-devtools</artifactId>              <scope>runtime</scope>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-test</artifactId>              <scope>test</scope>          </dependency>      </dependencies>        <build>          <plugins>              <plugin>                  <groupId>org.springframework.boot</groupId>                  <artifactId>spring-boot-maven-plugin</artifactId>              </plugin>              <plugin>                  <groupId>org.apache.maven.plugins</groupId>                  <artifactId>maven-compiler-plugin</artifactId>                  <version>3.7.0</version>                  <configuration>                      <source>1.8</source>                      <target>1.8</target>                  </configuration>              </plugin>          </plugins>      </build>      </project>

redis配置文件

package com.example.demo.conf;    import org.springframework.beans.factory.annotation.Value;  import org.springframework.context.annotation.Configuration;  import org.springframework.context.annotation.PropertySource;      @Configuration  @PropertySource("classpath:conf/redis.properties")  public class RedisConfig {        @Value("${shiro.redis.host}")      private String host;        @Value("${shiro.redis.timeout}")      private int timeout;        public String getHost() {          return host;      }        public void setHost(String host) {          this.host = host;      }        public int getTimeout() {          return timeout;      }        public void setTimeout(int timeout) {          this.timeout = timeout;      }  }

Shiro配置文件

package com.example.demo.conf;    import com.example.demo.auth.PermissionRealm;  import com.example.demo.common.entity.User;  import org.apache.shiro.authc.credential.HashedCredentialsMatcher;  import org.apache.shiro.realm.AuthorizingRealm;  import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;  import org.apache.shiro.spring.LifecycleBeanPostProcessor;  import org.apache.shiro.spring.web.ShiroFilterFactoryBean;  import org.apache.shiro.web.mgt.DefaultWebSecurityManager;  import org.apache.shiro.web.servlet.SimpleCookie;  import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;  import org.crazycake.shiro.RedisCacheManager;  import org.crazycake.shiro.RedisManager;  import org.crazycake.shiro.RedisSessionDAO;  import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;  import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.Configuration;  import org.springframework.context.annotation.DependsOn;  import org.springframework.data.redis.connection.RedisConnectionFactory;  import org.springframework.data.redis.core.RedisTemplate;  import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;  import org.springframework.data.redis.serializer.StringRedisSerializer;    import java.util.LinkedHashMap;    @Configuration  public class ShiroConfig {        @Bean      public RedisConfig redisConfig(){          return new RedisConfig();      }        @Bean      public RedisManager redisManager(){          RedisManager redisManager = new RedisManager(); // crazycake 实现          redisManager.setHost(redisConfig().getHost());          redisManager.setTimeout(redisConfig().getTimeout());          return redisManager;      }        @Bean      public JavaUuidSessionIdGenerator sessionIdGenerator(){          return new JavaUuidSessionIdGenerator();      }        @Bean      public RedisSessionDAO sessionDAO(){          RedisSessionDAO sessionDAO = new RedisSessionDAO(); // crazycake 实现          sessionDAO.setRedisManager(redisManager());          sessionDAO.setSessionIdGenerator(sessionIdGenerator()); // Session ID 生成器          return sessionDAO;      }        @Bean      public SimpleCookie cookie(){          SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID"); // cookie的name,对应的默认是 JSESSIONID          cookie.setHttpOnly(true);          cookie.setPath("/"); // path为 / 用于多个系统共享JSESSIONID          return cookie;      }        @Bean      public DefaultWebSessionManager sessionManager(){          DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();          sessionManager.setGlobalSessionTimeout(redisConfig().getTimeout()); // 设置session超时          sessionManager.setDeleteInvalidSessions(true); // 删除无效session          sessionManager.setSessionIdCookie(cookie()); // 设置JSESSIONID          sessionManager.setSessionDAO(sessionDAO()); // 设置sessionDAO          return sessionManager;      }        /**       * 1. 配置SecurityManager       * @return       */      @Bean      public DefaultWebSecurityManager securityManager(){          DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();          securityManager.setRealm(realm()); // 设置realm          securityManager.setSessionManager(sessionManager()); // 设置sessionManager  // securityManager.setCacheManager(redisCacheManager()); // 配置缓存的话,退出登录的时候crazycake会报错,要求放在session里面的实体类必须有个id标识          return securityManager;      }        /**       * 2. 配置缓存       * @return       */  // @Bean  // public CacheManager cacheManager(){  // EhCacheManager ehCacheManager = new EhCacheManager();  // ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");  // return ehCacheManager;  // }        @Bean      public RedisCacheManager redisCacheManager(){          RedisCacheManager cacheManager = new RedisCacheManager(); // crazycake 实现          cacheManager.setRedisManager(redisManager());          return cacheManager;      }        /**       * 3. 配置Realm       * @return       */      @Bean      public AuthorizingRealm realm(){          PermissionRealm realm = new PermissionRealm();          HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();          // 指定加密算法          matcher.setHashAlgorithmName("MD5");          // 指定加密次数          matcher.setHashIterations(10);          // 指定这个就不会报错          matcher.setStoredCredentialsHexEncoded(true);          realm.setCredentialsMatcher(matcher);          return realm;      }        /**       * 4. 配置LifecycleBeanPostProcessor,可以来自动的调用配置在Spring IOC容器中 Shiro Bean 的生命周期方法       * @return       */      @Bean      public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){          return new LifecycleBeanPostProcessor();      }        /**       * 5. 启用IOC容器中使用Shiro的注解,但是必须配置第四步才可以使用       * @return       */      @Bean      @DependsOn("lifecycleBeanPostProcessor")      public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){          return new DefaultAdvisorAutoProxyCreator();      }        /**       * 6. 配置ShiroFilter       * @return       */      @Bean      public ShiroFilterFactoryBean shiroFilterFactoryBean(){          LinkedHashMap<String, String> map = new LinkedHashMap<>();          // 静态资源          map.put("/css/**", "anon");          map.put("/js/**", "anon");            // 公共路径          map.put("/login", "anon");          map.put("/register", "anon");          //map.put("/*", "anon");            // 登出,项目中没有/logout路径,因为shiro是过滤器,而SpringMVC是Servlet,Shiro会先执行          map.put("/logout", "logout");            // 授权          map.put("/user/**", "authc,roles[user]");          map.put("/admin/**", "authc,roles[admin]");            // everything else requires authentication:          map.put("/**", "authc");            ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();          // 配置SecurityManager          factoryBean.setSecurityManager(securityManager());          // 配置权限路径          factoryBean.setFilterChainDefinitionMap(map);          // 配置登录url          factoryBean.setLoginUrl("/");          // 配置无权限路径          factoryBean.setUnauthorizedUrl("/unauthorized");          return factoryBean;      }        /**       * 配置RedisTemplate,充当数据库服务       * @return       */      @Bean      public RedisTemplate<String,User> redisTemplate(RedisConnectionFactory connectionFactory){          RedisTemplate<String,User> redisTemplate = new RedisTemplate<>();          redisTemplate.setConnectionFactory(connectionFactory);          redisTemplate.setKeySerializer(new StringRedisSerializer());          redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<User>(User.class));          return redisTemplate;      }    }

UserService

package com.example.demo.service;    import com.example.demo.common.entity.User;    import java.util.List;      public interface UserService {        void addUser(User user);        User login(User user);        List<User> getUsers();    }

impl

package com.example.demo.service.impl;    import com.example.demo.common.PasswordUtils;  import com.example.demo.common.entity.User;  import com.example.demo.service.UserService;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.data.redis.core.RedisTemplate;  import org.springframework.stereotype.Service;    import java.util.ArrayList;  import java.util.List;    @Service  public class UserServiceImpl implements UserService {        @Autowired      private RedisTemplate<String, User> redisTemplate;        @Override      public void addUser(User user) {          user.setPassword(PasswordUtils.saltAndMd5(user.getUsername(),user.getPassword())); // 加密          redisTemplate.boundHashOps("users").put(user.getUsername(), user);      }        @Override      public User login(User user) {          user.setPassword(PasswordUtils.saltAndMd5(user.getUsername(),user.getPassword())); // 加密          User u = (User) redisTemplate.boundHashOps("users").get(user.getUsername());          if (u == null || !check(user, u)){              return null;          }          return u;      }        @Override      public List<User> getUsers() {          List<Object> list = redisTemplate.boundHashOps("users").values();          List<User> users = new ArrayList<>();          list.forEach(u->{              users.add((User) u);          });          return users;      }        private boolean check(User a, User b){          if (a.getUsername().equals(b.getUsername()) && a.getPassword().equals(b.getPassword())){              return true;          }          return false;      }  }

controller

package com.example.demo.controller;    import com.example.demo.common.entity.User;  import com.example.demo.common.response.BaseResponse;  import com.example.demo.service.UserService;  import org.apache.shiro.SecurityUtils;  import org.apache.shiro.authc.UsernamePasswordToken;  import org.apache.shiro.subject.Subject;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.web.bind.annotation.RequestBody;  import org.springframework.web.bind.annotation.RequestMapping;  import org.springframework.web.bind.annotation.RestController;  import org.springframework.web.servlet.ModelAndView;      @RestController  public class SimpleController {        @Autowired      private UserService userService;        @RequestMapping("/")      public ModelAndView index(){          return new ModelAndView("index");      }        @RequestMapping("/login")      public BaseResponse<String> login(@RequestBody User user){          BaseResponse<String> response = new BaseResponse<>(0,"登陆成功");          Subject subject = SecurityUtils.getSubject();          UsernamePasswordToken token = new UsernamePasswordToken(                  user.getUsername(), user.getPassword());          subject.login(token);          response.setData("/home");          return response;      }        @RequestMapping("/register")      public BaseResponse register(@RequestBody User user){          userService.addUser(user);          return new BaseResponse(0,"注册成功");      }        @RequestMapping("/home")      public ModelAndView home(){          ModelAndView mv = new ModelAndView("home");          mv.addObject("users", userService.getUsers());          return mv;      }  }

redis.properties

shiro.redis.host=localhost:6379  shiro.redis.timeout=1800000

applicatin.properties

#server.port=8080  server.port=8081  #server.port=8082    spring.redis.host=127.0.0.1  spring.redis.port=6379

index.html

<!DOCTYPE html>  <html lang="en" xmlns:th="http://www.thymeleaf.org">  <head>      <meta charset="UTF-8">      <title>Index</title>      <link th:href="@{css/index.css}" rel="stylesheet" type="text/css">  </head>  <body>      <div class="container">          <div class="header">              <h2>初级SpringBoot+Shiro小栗子 Node-One</h2>              <!--<h2>初级SpringBoot+Shiro小栗子 Node-Two</h2>-->          </div>          <div class="main">              <div class="left">                  <div class="form-group">                      <input type="text" name="username" placeholder="请输入用户名">                  </div>                  <div class="form-group">                      <input type="password" name="password" placeholder="请输入密码">                  </div>                  <div class="form-group">                      <a href="javascript:;" id="login">登录</a>                  </div>                  <div class="form-group">                      <a href="/home">点我!不登录进不去</a>                  </div>              </div>              <div class="right">                  <div class="form-group">                      <input type="text" name="username" placeholder="请输入用户名">                  </div>                  <div class="form-group">                      <input type="password" name="password" placeholder="请输入密码">                  </div>                  <div class="form-group">                      <input type="text" name="show" placeholder="自我介绍">                  </div>                  <div class="form-group">                      <a href="javascript:;" id="register">注册</a>                  </div>              </div>          </div>      </div>      <!--<div class="tip-wrap">-->          <!--<div class="tip">似懂非懂</div>-->      <!--</div>-->  <script th:src="@{js/jquery-3.3.1.min.js}"></script>  <script th:src="@{js/index.js}"></script>  </body>  </html>

home.html

<!DOCTYPE html>  <html lang="en" xmlns:th="http://www.thymeleaf.org">  <head>      <meta charset="UTF-8">      <title>Home</title>      <link th:href="@{css/index.css}" rel="stylesheet" type="text/css">  </head>  <body>  <div class="container">      <div class="header">          <h2>初级SpringBoot+Shiro小栗子 Node-One</h2>          <!--<h2>初级SpringBoot+Shiro小栗子 Node-Two</h2>-->          <a href="/logout">退出登录</a>      </div>      <div class="main">          <table class="table">              <thead>              <tr>                  <th>Username</th>                  <th>Password</th>                  <th>Show</th>              </tr>              </thead>              <tbody>              <tr th:each="u : ${users}">                  <td>[[${u.username}]]</td>                  <td>[[${u.password}]]</td>                  <td>[[${u.show}]]</td>              </tr>              </tbody>          </table>      </div>  </div>  </body>  </html>

以上两种配置各打包一次(记得留着打包好的jar包)

解压到无中文目录,修改Nginx配置文件

upstream myapp{          server 127.0.0.1:8081 weight=1;          server 127.0.0.1:8082 weight=1;      }        server{              listen 80;              server_name myapp;                location / {                  proxy_pass http://myapp;                  proxy_set_header Host $host;                  proxy_set_header X-Real-IP $remote_addr;                  proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;              }      }

到此,先启动两个jar包(分别是8081,Node-One;8082,Node-Two)

然后启动Nginx

浏览器访问:http://localhost/

刷新看看..

随便在一个节点上注册,登录,然后刷新到另外一个节点,发现不用登录就可以访问权限资源

..

github地址:https://github.com/zhiyongzhao/boot-shiro-session

END