SpringBoot项目通过shiro安全框架和redis缓存实现多个相同项目session共享

为了减轻用户访问量增大带来的服务器压力,可以把多个相同的应用部署到多个服务器,然后通过nginx的负载均衡能力将用户请求根据各个应用实际运行情况来决定分发给哪个服务器,这样多个应用就形成了一个集群;不过这样又产生了一个新问题,多个应用之间如何实现session共享呢?

可以通过shiro框架中的会话管理功能配合redis缓存来实现,下面是我做的一个小Demo;

Demo使用的是SpringBoot,pom.xml文件中的依赖如下:


        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
            2.1.2.RELEASE
            
                
                    io.lettuce
                    lettuce-core
                
            
        
        
            redis.clients
            jedis
        
        
            org.apache.shiro
            shiro-all
            1.3.2
        
        
            org.crazycake
            shiro-redis
            3.1.0
                
    

Shiro的Realm如下,这里我都简写了授权方法没有,身边验证方法都是代码写死的:

public class MyRealm  extends AuthorizingRealm {

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
、        return null;    //授权无
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String)token.getPrincipal();  //得到用户名
        if(!username.equals("admin")){
            throw new UnknownAccountException(); //如果用户名错误
        }
        String encryptPassword=new Md5Hash("root",username,1).toString();   //加密后密码,此处模拟从数据库取得,加密规则md5 盐是登录名 散列次数1次
        SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo("admin",encryptPassword,getName());
        simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(username));
        return simpleAuthenticationInfo;
    }
}

用到的RedisInfo 如下:

public class RedisInfo {

    private String host;

    private int timeout;

    private int port;

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

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}

Shiro配置类如下:

@Configuration
public class ShiroConfig {

    @Bean   //验证密码
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        hashedCredentialsMatcher.setHashIterations(1);    //此处加密规则要和MyRealm中一致
        return hashedCredentialsMatcher;
    }

    @Bean
    public MyRealm myRealm(){
        MyRealm myRealm=new MyRealm();
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myRealm;
    }

    @Bean
    public SimpleCookie simpleCookie(){
        SimpleCookie simpleCookie=new SimpleCookie("SHAREJSESSIONID"); //SHARE
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(180000);
        simpleCookie.setPath("/");
        return simpleCookie;
    }
    
    @Bean   //会话监听器    不是必需的
    public SessionListener sessionListener(){
        return  new SessionListener();
    }

    @Bean   //Session工厂    不是必需的
    public SessionFactory sessionFactory(){
        return new MySessionFactory();
    }

    @Bean(name = "sessionIdGenerator")   //会话ID生成器
    public SessionIdGenerator sessionIdGenerator(){
        return new JavaUuidSessionIdGenerator();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.redis")
    public RedisInfo redisInfo(){
        return new RedisInfo();
    }

    @Bean
    public RedisManager redisManager(){
        RedisManager redisManager=new RedisManager();
        redisManager.setHost(redisInfo().getHost());
        redisManager.setPort(redisInfo().getPort());
        redisManager.setTimeout(redisInfo().getTimeout());
        return redisManager;
    }

    @Bean
    public RedisSessionDAO sessionDAO(){
        RedisSessionDAO sessionDAO = new RedisSessionDAO(); // crazycake 实现
        sessionDAO.setRedisManager(redisManager());
        sessionDAO.setSessionIdGenerator(sessionIdGenerator()); //  Session ID 生成器
        return sessionDAO;
    }

    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(redisInfo().getTimeout()*1000);
        sessionManager.setSessionListeners(Arrays.asList(sessionListener()));
        sessionManager.setSessionFactory(sessionFactory());
        sessionManager.setSessionDAO(sessionDAO());
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);  //是否开启会话验证器,默认是开启的
        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionIdCookie(simpleCookie());
        return sessionManager;
    }

    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        securityManager.setSessionManager(sessionManager());
        securityManager.setRealm(myRealm());
        return securityManager;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());

        LinkedHashMap map = new LinkedHashMap<>();
        map.put("/testLogin","anon");
        map.put("/**", "authc");    
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        // 配置登录url
        shiroFilterFactoryBean.setLoginUrl("/goLogin");
        // 配置无权限路径
        shiroFilterFactoryBean.setUnauthorizedUrl("/goLogin");
        return shiroFilterFactoryBean;
    }

    @Bean
    public FilterRegistrationBean delegatingFilterProxy(){
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
        registration.addInitParameter("targetFilterLifecycle", "true");
        registration.setEnabled(true);
        registration.addUrlPatterns("/*");
        return registration;
    }

}

Controller如下:

@RestController
public class MyController{

    @RequestMapping("/testLogin")
    public String testLogin(String username,String password){
        String result="模拟登陆成功";
        Subject subject= SecurityUtils.getSubject();
        UsernamePasswordToken token=new UsernamePasswordToken(username,password);
        try {
            subject.login(token);
            Session session=subject.getSession();
            //由于会话通过shiro转给redis管理,此处相当于将用户信息存入redis缓存中
            session.setAttribute("username",username);    
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            result="用户名不存在";
        } catch (IncorrectCredentialsException e){
            e.printStackTrace();
            result="密码错误";
        }
        return result;
    }

    @RequestMapping("/goLogin")
    public String goLogin(){
        return "请先登录!";
    }


    @RequestMapping("/welcome")
    public String welcome(){
        Subject subject=SecurityUtils.getSubject();
        Session session=subject.getSession();
        return session.getAttribute("username")+",欢迎访问!";
    }

}

SpringBoot配置文件如下:

#server
server:
  port: 8081
#  port:8082
#spring
spring:
  application:
    name: demo
  redis:
    jedis:
      pool:
        min-idle: 5
        max-active: 10
        max-idle: 10
        max-wait: 2000

    host: 127.0.0.1
    port: 6379
    timeout: 1800





分别打成两个jar包,两个jar包唯一区别就是配置文件中的端口不一样,分别为8081和8082;

通过java -jar命令运行两个jar包;

运行访问两个端口的/welcome,由于没有登录都会跳到/goLogin,如下:

SpringBoot项目通过shiro安全框架和redis缓存实现多个相同项目session共享_第1张图片

SpringBoot项目通过shiro安全框架和redis缓存实现多个相同项目session共享_第2张图片

此时8081登录,浏览器访问http://localhost:8081/testLogin?username=admin&password=root

SpringBoot项目通过shiro安全框架和redis缓存实现多个相同项目session共享_第3张图片

此时查看浏览器中的cookie内容和redis的keys,如下:

SpringBoot项目通过shiro安全框架和redis缓存实现多个相同项目session共享_第4张图片

redis已经保存了session信息

再次访问8082的/welcome,结果如下:

SpringBoot项目通过shiro安全框架和redis缓存实现多个相同项目session共享_第5张图片

session通过redis实现共享成功!

你可能感兴趣的:(SpringBoot项目通过shiro安全框架和redis缓存实现多个相同项目session共享)