美文网首页
SpringBoot+Shiro+Redis

SpringBoot+Shiro+Redis

作者: 虾米咬小米 | 来源:发表于2021-01-07 15:01 被阅读0次

    1、实现共享Session

    1、重新设置 session 及 cookie 去除 httpOnly 浏览器 脚本 都能取到 cookie

    最终采用:

    package com.daoshu.involved.shared.core.shiro;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.CookieRememberMeManager;
    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.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.Properties;
    
    
    @Configuration
    @Slf4j
    public class ShiroConfig {
    
        private static final int redis_expire = 1000 * 60 * 60 * 2;
    
        private static final long session_expire = 1000 * 60 * 60 * 24;
    
    
        @Value("${spring.redis.host}")
        private String redisHost;
        @Value("${spring.redis.port}")
        private String redisPort;
    
        /**
         * 设置过滤规则
         *
         * @param securityManager
         * @return
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
    
            /**
             * bean.setLoginUrl("/login") 是在项目启动后,如果没有登录的情况下,会被shiro强制请求的路径,即为/unauth ;
             */
            shiroFilterFactoryBean.setLoginUrl("/auth/unauth");
    
            /**
             * authc,表示拦截的路径,anon,表示不拦截的路径
             * 注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
             * 所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
             */
    
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            /**
             * swagger 免验证
             */
            filterChainDefinitionMap.put("/swagger-ui.html", "anon");
            filterChainDefinitionMap.put("/swagger-resources", "anon");
            filterChainDefinitionMap.put("/swagger-resources/**", "anon");
            filterChainDefinitionMap.put("/v2/api-docs", "anon");
            filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
    
            filterChainDefinitionMap.put("/static/**", "anon");
            filterChainDefinitionMap.put("/auth/login", "anon");
            filterChainDefinitionMap.put("/**.jpg", "anon");
            filterChainDefinitionMap.put("/**.ico", "anon");
            filterChainDefinitionMap.put("/**", "authc");
    
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
    
    
     /**
         * 
         * 
         *  可以分别采用  单例,及集群
         * RedisClusterManager   redis 集群管理
         * RedisManager   redis 集群管理
         * 
         * @return
         */
        @Bean
        public RedisManager redisManager() {
            RedisManager redisManager = new RedisManager();
            redisManager.setDatabase(2);
            redisManager.setHost(redisHost + ":" + redisPort);
            return redisManager;
        }
    
        /**
         * 基于Redis实现共享Session
         * @return
         */
        @Bean
        public RedisSessionDAO redisSessionDAO(RedisManager redisManager) {
            RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
            redisSessionDAO.setRedisManager(redisManager);
            //  Session ID 生成器
            redisSessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
            return redisSessionDAO;
        }
    
        @Bean
        public SimpleCookie cookie() {
            //  cookie的name,对应的默认是 JSESSIONID
            SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID");
            /**
             * 浏览器 脚本  都能取到 cookie
             */
            cookie.setHttpOnly(false);
            //  path为 / 用于多个系统共享JSESSIONID
            cookie.setPath("/");
            return cookie;
        }
    
        @Bean
        public RedisCacheManager redisCacheManager(RedisManager redisManager) {
            RedisCacheManager redisCacheManager = new RedisCacheManager();
            redisCacheManager.setRedisManager(redisManager);
            // 设置redis超时
            redisCacheManager.setExpire(redis_expire);
            redisCacheManager.setPrincipalIdFieldName("userId");
            return redisCacheManager;
        }
    
        /**
         * 凭证匹配器
         *
         * @return
         */
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("md5");
            //散列的次数
            hashedCredentialsMatcher.setHashIterations(1);
            return hashedCredentialsMatcher;
        }
    
        /**
         * 自定义realm
         *
         * @return
         */
        @Bean
        public UserRealm userRealm() {
            UserRealm userRealm = new UserRealm();
            userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            return userRealm;
        }
    
        /**
         * 安全管理器
         * 注:使用shiro-spring-boot-starter 1.4时,返回类型是SecurityManager会报错,直接引用shiro-spring则不报错
         *
         * @return
         */
        @Bean
        public SecurityManager securityManager(RedisSessionDAO redisSessionDAO, RedisCacheManager redisCacheManager) {
    
    
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            // 设置session超时
            sessionManager.setGlobalSessionTimeout(session_expire);
            // 删除无效session
            sessionManager.setDeleteInvalidSessions(true);
            // 设置JSESSIONID
            sessionManager.setSessionIdCookie(cookie());
            // 设置sessionDAO
            sessionManager.setSessionDAO(redisSessionDAO);
    
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setSessionManager(sessionManager);
            securityManager.setCacheManager(redisCacheManager);
            securityManager.setRememberMeManager(cookieRememberMeManager());
            securityManager.setRealm(userRealm());
            return securityManager;
        }
    
    
    
    
    
    
    
    
        /**
         * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理
         *
         * @return
         */
        @Bean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
            proxyCreator.setProxyTargetClass(true);
            return proxyCreator;
        }
    
        /**
         *    开启shiro aop注解支持.
         *    使用代理方式;所以需要开启代码支持;
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(securityManager);
            return advisor;
        }
    
        @Bean
        public SimpleMappingExceptionResolver resolver() {
            SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
            Properties properties = new Properties();
            properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "error/403");
            exceptionResolver.setExceptionMappings(properties);
            return exceptionResolver;
        }
    
        @Bean
        public SimpleCookie rememberMeCookie() {
            log.info("ShiroConfiguration.rememberMeCookie()");
            //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
            SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
    
            //<!-- 记住我cookie生效时间 ,单位秒;-->
            simpleCookie.setMaxAge(1800);
            return simpleCookie;
        }
    
        @Bean
        public CookieRememberMeManager cookieRememberMeManager() {
            log.info("ShiroConfiguration.rememberMeManager()");
            CookieRememberMeManager manager = new CookieRememberMeManager();
            manager.setCookie(rememberMeCookie());
            return manager;
        }
    
    
    
    
    
    
    
    }
    
    

    项目发布到微服务k8s里,发现容器数量为1的时候 能正常登录,而当容器数量调整到多个的时候就会发现登录不了。 经排查是多个容器的时候Session会话没保持,就需要在多个应用的时候共享session会话。

    上一篇2020-04-01-Shiro Session集群共享存入Redis中SimpleSession的transient 属性不能序列化已经提到了集成redis实现共享session的坑!! 这里我就不用自己去 RedisManager、SessionDAO了,而是使用shiro-redis 框架。

    下面通过实现一个小Demo,来说明如何使用并集成shiro-redis!

    一、实现步骤

    • 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.2.3</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;
        }
    
    }
    
    
    • UserServer.java
    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();
    
    }
    
    
    • UserServiceImpl.java
    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;
        }
    }
    
    
    • IndexController.java
    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 IndexController {
    
        @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;
        }
    }
    
    
    • applicatin.properties
    server.port=8080 
    
    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="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> 
    <script th:src="@{js/jquery-3.3.1.min.js}"></script>
    <script th:src="@{js/index.js}"></script>
    </body>
    </html>
    
    

    二、本地测试

    通过nginx 启动两个不同端口的jar(8081、8082)

    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;
            }
    }
    
    

    项目启动会,访问登录 就会发现cookie里存在SHAREJSESSIONID了 redis里也有对应的SessionId了。

    三、生产环境&ShiroConfig配置

    很多时候生产环境的redis都是集群化,这里的配置就有一点不同。

    下面贴一下我公司项目的ShiroConfig配置,跟上面的demo不是一回事 大体思想还是一致的。

    package cn.pconline.pcloud.admin.config;
    
    import cn.pconline.pcloud.admin.service.RoleService;
    import cn.pconline.pcloud.admin.service.UserService;
    import cn.pconline.pcloud.base.entity.system.Resource;
    import cn.pconline.pcloud.base.entity.system.Role;
    import cn.pconline.pcloud.base.entity.system.User;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.web.mgt.CookieRememberMeManager;
    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.RedisClusterManager;
    import org.crazycake.shiro.RedisManager;
    import org.crazycake.shiro.RedisSessionDAO;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
    
    import java.util.*;
    
    /**
     * @Description Shiro配置 支持session集群
     * @Author jie.zhao
     * @Date 2020/3/31 13:54
     */
    @Configuration
    public class ShiroConfig {
    
        private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
    
        @Value("${app.domain:/}")
        private String domain;
    
        @Value("${spring.redis.cluster.nodes}")
        private String redisClusterNodes;
    
        @Autowired
        private UserService userService;
    
        @Autowired
        private RoleService roleService;
    
        private static final int redis_expire = 1000 * 60 * 60 * 2;
    
        private static final long session_expire = 1000 * 60 * 60 * 2;
    
        /**
         * 授权凭证(启动项目时加载)
         * 对应 realm.doGetAuthorizationInfo()
         *
         * @param securityManager
         * @return
         */
        @Bean(name = "shiroFilter")
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            shiroFilter.setSecurityManager(securityManager);
            shiroFilter.setLoginUrl(domain + "admin/login");
            shiroFilter.setSuccessUrl(domain + "admin/index");
            // 没权限时跳转至该页面
            shiroFilter.setUnauthorizedUrl(domain + "admin/permission/died");
            // anon、authc、user对应realm.doGetAuthenticationInfo(..)登录认证
            // perms、roles、ssl、est、port对应realm.doGetAuthorizationInfo(..)授权认证
            // 设置过滤器链接集合 注意:Map要支持顺序,授权配置后出
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilter;
        }
    
        @Bean
        public RedisClusterManager redisClusterManager() {
            RedisClusterManager redisManager = new RedisClusterManager();
            redisManager.setHost(redisClusterNodes);
            return redisManager;
        }
    
        @Bean
        public RedisSessionDAO redisSessionDAO(RedisClusterManager redisClusterManager) {
            RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
            redisSessionDAO.setRedisManager(redisClusterManager);
            //  Session ID 生成器
            redisSessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
            return redisSessionDAO;
        }
    
        @Bean
        public SimpleCookie cookie() {
            //  cookie的name,对应的默认是 JSESSIONID
            SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID");
            cookie.setHttpOnly(true);
            //  path为 / 用于多个系统共享JSESSIONID
            cookie.setPath("/");
            return cookie;
        }
    
        @Bean
        public RedisCacheManager redisCacheManager(RedisClusterManager redisClusterManager) {
            RedisCacheManager redisCacheManager = new RedisCacheManager();
            redisCacheManager.setRedisManager(redisClusterManager);
            redisCacheManager.setExpire(redis_expire);
            redisCacheManager.setPrincipalIdFieldName("userId");
            return redisCacheManager;
        }
    
        @Bean
        public SecurityManager securityManager(AuthorizingRealm myShiroRealm, RedisSessionDAO redisSessionDAO, RedisCacheManager redisCacheManager) {
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            // 设置session超时
            sessionManager.setGlobalSessionTimeout(session_expire);
            // 删除无效session
            sessionManager.setDeleteInvalidSessions(true);
            // 设置JSESSIONID
            sessionManager.setSessionIdCookie(cookie());
            // 设置sessionDAO
            sessionManager.setSessionDAO(redisSessionDAO);
    
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setSessionManager(sessionManager);
            securityManager.setCacheManager(redisCacheManager);
            securityManager.setRememberMeManager(cookieRememberMeManager());
            securityManager.setRealm(myShiroRealm);
            return securityManager;
        }
    
        @Bean
        public AuthorizingRealm myShiroRealm() {
            AuthorizingRealm myShiroRealm = new AuthorizingRealm() {
    
                @Override
                protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
                    logger.info("认证 --> MyShiroRealm.doGetAuthenticationInfo()");
                    String username = (String) token.getPrincipal();
                    User user = userService.findByAccount(username);
                    String password = new String((char[]) token.getCredentials());
                    // 账号不存在
                    if (user == null) {
                        throw new UnknownAccountException("账号不存在!");
                    }
                    // 密码错误
                    /*if (!MD5Utils.md5(password).equals(user.getPassword())) {
                        throw new IncorrectCredentialsException("账号或密码不正确");
                    }*/
                    // 账号锁定
                    if (user.getIsLock() == 1) {
                        throw new LockedAccountException("账号已被锁定,请联系管理员!");
                    }
                    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
                    return info;
                }
    
                @Override
                protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
                    logger.info("权限配置 --> MyShiroRealm.doGetAuthorizationInfo()");
                    User user = (User) principals.getPrimaryPrincipal();
                    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
                    Set<String> shiroPermissions = new HashSet<>();
                    Set<String> roleSet = new HashSet<String>();
                    // 加载你的角色
                    List<Role> roles = roleService.list4Login(user);
                    if (roles != null) {
                        for (Role role : roles) {
                            // 添加角色
                            roleSet.add(role.getRoleKey());
                            // 添加角色关联的资源
                            if (role.getRelResourceList() != null) {
                                for (Resource resource : role.getRelResourceList()) {
                                    shiroPermissions.add(resource.getSourceKey());
                                }
                            }
                        }
                    }
                    authorizationInfo.setRoles(roleSet);
                    authorizationInfo.setStringPermissions(shiroPermissions);
                    return authorizationInfo;
                }
            };
            myShiroRealm.setCachingEnabled(true);
            myShiroRealm.setAuthorizationCachingEnabled(true);
            return myShiroRealm;
        }
    
        /**
         * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理
         *
         * @return
         */
        @Bean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
            proxyCreator.setProxyTargetClass(true);
            return proxyCreator;
        }
    
        /**
         * 开启shiro aop注解支持.
         * 使用代理方式;所以需要开启代码支持;
         *
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    
        @Bean
        public SimpleMappingExceptionResolver resolver() {
            SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
            Properties properties = new Properties();
            properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "error/403");
            exceptionResolver.setExceptionMappings(properties);
            return exceptionResolver;
        }
    
        @Bean
        public SimpleCookie rememberMeCookie() {
            logger.info("ShiroConfiguration.rememberMeCookie()");
            //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
            SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
    
            //<!-- 记住我cookie生效时间 ,单位秒;-->
            simpleCookie.setMaxAge(1800);
            return simpleCookie;
        }
    
        @Bean
        public CookieRememberMeManager cookieRememberMeManager() {
            logger.info("ShiroConfiguration.rememberMeManager()");
            CookieRememberMeManager manager = new CookieRememberMeManager();
            manager.setCookie(rememberMeCookie());
            return manager;
        }
    }
    

    参考:
    https://my.oschina.net/u/4351575/blog/4134146

    相关文章

      网友评论

          本文标题:SpringBoot+Shiro+Redis

          本文链接:https://www.haomeiwen.com/subject/ulyzoktx.html