• 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>
    View Code

     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

    解压到无中文目录,修改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

  • 相关阅读:
    EdgeDB 1.0 Alpha 4 发布了
    静态文件请求路径 rewrite nginx && openresty 实现
    Giving Application Pools Event Log Access
    一张图看懂ASP.NET MVC5认证和授权过滤器的执行顺序
    git check-ignore
    What does “Challenge” term stand for?
    Implementing MVC 5 IAuthenticationFilter
    HttpContext.Current.User is null even though Windows Authentication is on
    When should the volatile keyword be used in C#?
    HttpApplicationState.Remove(String) Method
  • 原文地址:https://www.cnblogs.com/LUA123/p/9337963.html
Copyright © 2020-2023  润新知