在以前的项目中,我们的项目基本都是单机部署的,session都是存储在内存里,

但随着使用springCloud等微服务技术,多节点部署成为了一种趋势,所以session共享也是必然的;


那么,我们直接开始了


在pom.xml直接饮用

<dependency>

<groupId>org.crazycake</groupId>

<artifactId>shiro-redis</artifactId>

<version>3.2.3</version>

<exclusions>

<exclusion>

<artifactId>shiro-core</artifactId>

<groupId>org.apache.shiro</groupId>

</exclusion>

</exclusions>

</dependency>

因为我的项目已饮用shiro-core,所以这里我要排除掉

<dependency>

    <groupId>org.apache.shiro</groupId>

    <artifactId>shiro-core</artifactId>

    <version>${shiro.version}</version>

</dependency>


然后配置ShiroConfig

import java.util.LinkedHashMap;

import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.mgt.SecurityManager;

import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;

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 com.dimple.framework.shiro.realm.UserRealm;

import com.dimple.framework.shiro.web.filter.KickoutSessionControlFilter;

import com.dimple.framework.shiro.web.filter.LogoutFilter;

import com.dimple.framework.shiro.web.filter.captcha.CaptchaValidateFilter;


/**

 * 

 * @author liusheng

 *

 */

@Configuration

public class ShiroConfig {

    public static final String PREMISSION_STRING = "perms[\"{0}\"]";


    // Session超时时间,单位为毫秒(默认30分钟)

    @Value("${shiro.session.expireTime}")

    private int expireTime;


    // 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟

    @Value("${shiro.session.validationInterval}")

    private int validationInterval;


    // 验证码开关

    @Value("${shiro.user.captchaEnabled}")

    private boolean captchaEnabled;


    // 验证码类型

    @Value("${shiro.user.captchaType}")

    private String captchaType;


    // 设置Cookie的域名

    @Value("${shiro.cookie.domain}")

    private String domain;


    // 设置cookie的有效访问路径

    @Value("${shiro.cookie.path}")

    private String path;


    // 设置HttpOnly属性

    @Value("${shiro.cookie.httpOnly}")

    private boolean httpOnly;


    // 设置Cookie的过期时间,秒为单位

    @Value("${shiro.cookie.maxAge}")

    private int maxAge;


    // 登录地址

    @Value("${shiro.user.loginUrl}")

    private String loginUrl;


    // 权限认证失败地址

    @Value("${shiro.user.unauthorizedUrl}")

    private String unauthorizedUrl;

    

    @Value("${spring.redis.host}")

    private String host;


    @Value("${spring.redis.port}")

    private int port;


//    @Value("${spring.redis.timeout}")

//    private int timeout;


    @Value("${spring.redis.password}")

    private String password;


    @Bean

    public UserRealm userRealm(RedisCacheManager redisCacheManager) {

        UserRealm userRealm = new UserRealm();

        userRealm.setCacheManager(redisCacheManager);

        return userRealm;

    }


    @Bean

    public SecurityManager securityManager(UserRealm userRealm) {

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        securityManager.setRealm(userRealm);

        securityManager.setSessionManager(sessionManager());

        // 自定义缓存实现 使用redis

        securityManager.setCacheManager(redisCacheManager());

        return securityManager;

    }


    @Bean

    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {

    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

         // Shiro的核心安全接口,这个属性是必须的

         shiroFilterFactoryBean.setSecurityManager(securityManager);

         // 身份认证失败,则跳转到登录页面的配置

         shiroFilterFactoryBean.setLoginUrl(loginUrl);

         // 权限认证失败,则跳转到指定页面

         shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

         // Shiro连接约束配置,即过滤链的定义

         LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

         // 对静态资源设置匿名访问

         filterChainDefinitionMap.put("/favicon.ico**", "anon");

         filterChainDefinitionMap.put("/dimple.png**", "anon");

         filterChainDefinitionMap.put("/css/**", "anon");

         filterChainDefinitionMap.put("/docs/**", "anon");

         filterChainDefinitionMap.put("/fonts/**", "anon");

         filterChainDefinitionMap.put("/img/**", "anon");

         filterChainDefinitionMap.put("/ajax/**", "anon");

         filterChainDefinitionMap.put("/js/**", "anon");

         filterChainDefinitionMap.put("/dimple/**", "anon");

         filterChainDefinitionMap.put("/druid/**", "anon");

         filterChainDefinitionMap.put(SystemConfig.relativeProfile+"/**", "anon");

         filterChainDefinitionMap.put("/public/**", "anon");

         

         filterChainDefinitionMap.put("/**.html", "anon");

         filterChainDefinitionMap.put("/**/**.html", "anon");

         //前台界面设置允许访问

//         setFront(filterChainDefinitionMap);

         filterChainDefinitionMap.put("/f/**", "anon");

         filterChainDefinitionMap.put("/", "anon");

         filterChainDefinitionMap.put("/about**", "anon");

         filterChainDefinitionMap.put("/f/about**", "anon");

         filterChainDefinitionMap.put("/front/**", "anon");

         filterChainDefinitionMap.put("/bbs/front/**", "anon");//博客

         filterChainDefinitionMap.put("/bbs/incClick", "anon");//博客

         filterChainDefinitionMap.put("/kaoshi/**", "anon");//考试

         filterChainDefinitionMap.put("/common/upload", "anon");//上传文件

         filterChainDefinitionMap.put("/scan/**", "anon");

         filterChainDefinitionMap.put("/order/**", "anon");

         filterChainDefinitionMap.put("/form/**", "anon");

         filterChainDefinitionMap.put("/excel/**", "anon");

         filterChainDefinitionMap.put("/king/suggest/add", "anon");

         filterChainDefinitionMap.put("/frame", "anon");

         filterChainDefinitionMap.put("/pages/**", "anon");

         

         filterChainDefinitionMap.put("/actuator/**", "anon");


         filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");

         // 退出 logout地址,shiro去清除session

         filterChainDefinitionMap.put("/logout", "logout");

         filterChainDefinitionMap.put("/frontLogout", "frontLogout");

         // 不需要拦截的访问

         filterChainDefinitionMap.put("/login", "anon,captchaValidate");

         // 系统权限列表

         // filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());


         Map<String, Filter> filters = new LinkedHashMap<>();

//         filters.put("onlineSession", onlineSessionFilter());

//         filters.put("syncOnlineSession", syncOnlineSessionFilter());

         filters.put("captchaValidate", captchaValidateFilter());

         filters.put("kickout", kickoutSessionControlFilter());

         

         shiroFilterFactoryBean.setFilters(filters);


         // 所有请求需要认证

//         filterChainDefinitionMap.put("/**", "user,onlineSession,syncOnlineSession");

         filterChainDefinitionMap.put("/**", "user");

         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);


         return shiroFilterFactoryBean;

    }


    @Bean

    public CaptchaValidateFilter captchaValidateFilter() {

        CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter();

        captchaValidateFilter.setCaptchaEnabled(captchaEnabled);

        captchaValidateFilter.setCaptchaType(captchaType);

        return captchaValidateFilter;

    }



    /**

     * 限制同一账号登录同时登录人数控制

     * @return

     */

    public KickoutSessionControlFilter kickoutSessionControlFilter(){

        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();

        //使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;

        //这里我们还是用之前shiro使用的redisManager()实现的cacheManager()缓存管理

        //也可以重新另写一个,重新配置缓存时间之类的自定义缓存属性

        kickoutSessionControlFilter.setCacheManager(redisCacheManager());

        //用于根据会话ID,获取会话进行踢出操作的;

        kickoutSessionControlFilter.setSessionManager(sessionManager());

        //是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序。

        kickoutSessionControlFilter.setKickoutAfter(false);

        //同一个用户最大的会话数,默认5;比如5的意思是同一个用户允许最多同时五个人登录;

        kickoutSessionControlFilter.setMaxSession(5);

        //被踢出后重定向到的地址;

        kickoutSessionControlFilter.setKickoutUrl("/kickout");

        return kickoutSessionControlFilter;

    }

    /**

     * 退出过滤器

     */

    public LogoutFilter logoutFilter() {

        LogoutFilter logoutFilter = new LogoutFilter();

        logoutFilter.setLoginUrl(loginUrl);

        return logoutFilter;

    }  

    /**

     * RedisSessionDAO shiro sessionDao层的实现 通过redis

     * 使用的是shiro-redis开源插件

     */

    @Bean

    public RedisSessionDAO redisSessionDAO() {

        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();

        redisSessionDAO.setRedisManager(redisManager());

        return redisSessionDAO;

    }

     @Bean

    public DefaultWebSessionManager sessionManager() {

        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();

        sessionManager.setSessionDAO(redisSessionDAO());

        return sessionManager;

    }


    /**

     * cacheManager 缓存 redis实现 使用的是shiro-redis开源插件

    * @return

     */

    @Bean

    public RedisCacheManager redisCacheManager() {

        RedisCacheManager redisCacheManager = new RedisCacheManager();

        redisCacheManager.setRedisManager(redisManager());

        return redisCacheManager;

    }

        /**

     * 配置shiro redisManager

     * 使用的是shiro-redis开源插件

     * @return

     */

    public RedisManager redisManager() {

        RedisManager redisManager = new RedisManager();

//        redisManager.setHost(host);

//        redisManager.setPort(port);

//        redisManager.setExpire(1800);// 配置缓存过期时间

        //shiro-redis 3.x 版本

        redisManager.setHost(host+":"+port);

        redisManager.setTimeout(1800);

        redisManager.setPassword(password);

        return redisManager;

    }

    /**

     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),

     * 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证

     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能

     */

    @Bean

    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {

        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

        advisorAutoProxyCreator.setProxyTargetClass(true);

        return advisorAutoProxyCreator;

    }

    /**

     * 开启Shiro注解通知器

     */

    @Bean

    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(

            @Qualifier("securityManager") SecurityManager securityManager) {

        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();

        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);

        return authorizationAttributeSourceAdvisor;

    }

}


接着定义relalm


import java.util.HashSet;

import java.util.Set;

import org.apache.shiro.SecurityUtils;

import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.AuthenticationInfo;

import org.apache.shiro.authc.AuthenticationToken;

import org.apache.shiro.authc.ExcessiveAttemptsException;

import org.apache.shiro.authc.IncorrectCredentialsException;

import org.apache.shiro.authc.LockedAccountException;

import org.apache.shiro.authc.SimpleAuthenticationInfo;

import org.apache.shiro.authc.UnknownAccountException;

import org.apache.shiro.authc.UsernamePasswordToken;

import org.apache.shiro.authz.AuthorizationInfo;

import org.apache.shiro.authz.SimpleAuthorizationInfo;

import org.apache.shiro.realm.AuthorizingRealm;

import org.apache.shiro.subject.PrincipalCollection;

import org.springframework.beans.factory.annotation.Autowired;

import com.dimple.common.exception.user.CaptchaException;

import com.dimple.common.exception.user.RoleBlockedException;

import com.dimple.common.exception.user.UserBlockedException;

import com.dimple.common.exception.user.UserNotExistsException;

import com.dimple.common.exception.user.UserPasswordNotMatchException;

import com.dimple.common.exception.user.UserPasswordRetryLimitExceedException;

import com.dimple.common.utils.security.ShiroUtils;

import com.dimple.framework.shiro.service.LoginService;

import com.dimple.project.system.menu.service.IMenuService;

import com.dimple.project.system.role.service.IRoleService;

import com.dimple.project.system.user.domain.User;

import lombok.extern.slf4j.Slf4j;


/**

 * 

 * @author liusheng

 *

 */

@Slf4j

public class UserRealm extends AuthorizingRealm {

    @Autowired

    private IMenuService menuService;

    @Autowired

    private IRoleService roleService;

    @Autowired

    private LoginService loginService;

    /**

     * 授权

     */

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {

        User user = ShiroUtils.getSysUser();

        // 角色列表

        Set<String> roles = new HashSet<String>();

        // 功能列表

        Set<String> menus = new HashSet<String>();

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 管理员拥有所有权限

        if (user.isAdmin()) {

            info.addRole("admin");

            info.addStringPermission("*:*:*");

        } else {

            roles = roleService.selectRoleKeys(user.getUserId());

            menus = menuService.selectPermsByUserId(user.getUserId());

            // 角色加入AuthorizationInfo认证对象

            info.setRoles(roles);

            // 权限加入AuthorizationInfo认证对象

            info.setStringPermissions(menus);

        }

        return info;

    }


    /**

     * 登录认证

     */

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        UsernamePasswordToken upToken = (UsernamePasswordToken) token;

        String username = upToken.getUsername();

        String password = "";

        if (upToken.getPassword() != null) {

            password = new String(upToken.getPassword());

        }

        User user = null;

        try {

            user = loginService.login(username, password);

        } catch (CaptchaException e) {

            throw new AuthenticationException(e.getMessage(), e);

        } catch (UserNotExistsException e) {

            throw new UnknownAccountException(e.getMessage(), e);

        } catch (UserPasswordNotMatchException e) {

            throw new IncorrectCredentialsException(e.getMessage(), e);

        } catch (UserPasswordRetryLimitExceedException e) {

            throw new ExcessiveAttemptsException(e.getMessage(), e);

        } catch (UserBlockedException e) {

            throw new LockedAccountException(e.getMessage(), e);

        } catch (RoleBlockedException e) {

            throw new LockedAccountException(e.getMessage(), e);

        } catch (Exception e) {

        e.printStackTrace();

            log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());

            throw new AuthenticationException(e.getMessage(), e);

        }

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());

        return info;

    }

    /**

     * 清理缓存权限

     */

    public void clearCachedAuthorizationInfo() {

        this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());

    }

}


这就完成我们的配置了


接下来我们验证一下

这里我使用nginx进行本地的分发

nginx.conf配置如下

#user  nobody;

worker_processes  1;

#error_log  logs/error.log;

#error_log  logs/error.log  notice;

#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {

    worker_connections  1024;

}

http {

    include       mime.types;

    default_type  application/octet-stream;

    #access_log  logs/access.log  main;

    sendfile        on;

    #tcp_nopush     on;

    #keepalive_timeout  0;

    keepalive_timeout  65;

    #gzip  on;

    upstream tomcatCluster{ 

#这里指定多个源服务器,ip:端口,80端口的话可写可不写

server 127.0.0.1:8080; 

server 127.0.0.1:8081; 

}

    server {

        listen       8060;

        server_name  www.xiaoxigua.com;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {

            proxy_pass        http://tomcatCluster;

        }

    }

}

这里配置比较简单,其实就是在访问 8060端口是转到8080和8081两个端口
我已查看到日志登录后请求已经进入到后台了 ,随便停止一台服务器,也能正常使用了