SpringBoot+Shiro+Redis共享Session入门小栗子
在单机版的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/
刷新看看..
..
随便在一个节点上注册,登录,然后刷新到另外一个节点,发现不用登录就可以访问权限资源
..
小LUA
面对敌人的严刑逼供,我一个字也没说,而是一五一十写了下来。