springboot集成shiro-redis

springboot集成shiro-redis

springboot集成shiro-redis

近期公司项目中需要用到 shrio做登录认证,认证的token用redis存储,在集成过程中出现了一些问题,写这篇博客就是为了以后在调用的时候避坑,也希望可以帮助到其他遇到同样的问题。

项目架构

项目是前后端分离的项目,主要运用到以下的技术:
后端+后台管理:

  1. springboot;
  2. mybatise;
  3. maven;
  4. shrio;
  5. redis;
  6. angular.js;
  7. jdk1.8
  8. mysql;
  9. 阿里云 oss存储(视频和图片);

前端:

  1. uni app (一套代码多端运行);
  2. 微信 js-sdk(分享功能);

redis安装

公司用的是阿里云的服务器,系统是 CentOS 6.8 64位,使用putty+winSCP工具操作和上传,安装流程如下:
1、使用putty登录服务器 (这里方便操作,我直接使用虚拟机的centos 7,安装流程是不变的)

2、切换到 /usr/local目录下
在这里插入图片描述
3、在local下新建一个redis目录
在这里插入图片描述
4、使用上传工具WinSCP将redis的包上传到redis目录下(我的redis的安装包是提前下载好的,也可以在线安装)
在这里插入图片描述
5、解压redis tar -zxvf 解压包名
在这里插入图片描述
解压成功后,redis目录下会多出一个redis解压后的文件
在这里插入图片描述
6、在redis目录下创建两个目录,目录名随意,我这里用 bin和 config 表示
在这里插入图片描述
7、 安装 gcc 环境

redis是由C语言编写的,它的运行需要C环境,所以编译前需安装 gcc (安装过程比较长)
yum install gcc-c++ 遇到[y / d / n] 输入 y
在这里插入图片描述
在这里插入图片描述
8、安装完成进入 redis-5.0.5下 执行编译 make
在这里插入图片描述
在这里插入图片描述
9、进入到src目录下,执行 make install

在这里插入图片描述
编译工作已经完成。

10、切换到redis-5.0.5目录下,将redis-conf文件移动到config目录下

在这里插入图片描述
在这里插入图片描述
11、将redis-5.0.5 下 src下的绿色文件全部移动到 redis目录下的bin中

mv mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel  redis-server redis-trib.rb /usr/local/redis/bin/

在这里插入图片描述
在这里插入图片描述
12、修改配置文件 (config/redis-conf)
1)、 将 bind 127.0.0.1 注释掉 或 将127.0.0.1改成0.0.0.0
在这里插入图片描述
在这里插入图片描述
2)、将 yes 改为 no
在这里插入图片描述
在这里插入图片描述
3)、将 daemonize 属性改为 yes (这样启动时就在后台启动)

在这里插入图片描述
4)、设置密码 随意设置=====建议设置密码(注意设置密码后很可能在集成时会入坑,后面有介绍)
密码:testredis
在这里插入图片描述
:wq 保存退出

13、启动redis

./bin/redis-server config/redis.conf

在这里插入图片描述
14、连接redis
出现如下界面说明已经连接
在这里插入图片描述
在这里插入图片描述
连接失败的情况: (本人直接在阿里云上操作的,入了一点小坑)
按照我给出的修改配置文件的步骤,99%是没有问题的,但是,虚拟机连接不上需要关闭防火墙即可。
如果是阿里云服务器:
是需要在控制台设置安全组规则的,开放6379端口。
本人入坑记录来啦:
所有的配置和安全组开放后,一直不能连上,最后查看防火墙的开放端口,服务器防火墙并没有开放6379端口,我也很迷茫为什么没有开放改端口,正常来说 安全组开放端口后都是可以的。最后解决办法:
查看tcp端口:
在这里插入图片描述
这里虽然是有6379端口,在防火墙的配置文件可能是不存在的。
编辑防火墙的配置文件:

vi /etc/sysconfig/iptables    #编辑防火墙配置文件

在文件中添加

-A INPUT -m state --state NEW -m tcp -p tcp --dport 6379 -j ACCEPT

:wq  #保存并退出

service iptables start #开启

这时在连接,完美 ok。

redis安装成功后、直接上集成的代码

一、导包,在pom/xml中导入 shrio和 shrio-redis的包

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
         <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>
          <!-- redis缓存-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>

注意:这里的redis不用在导入jedis的包,这里的redis直接使用
@Autowired
private RedisTemplate redisTemplate;

  springboot中集成shiro相对简单,只需要两个类:一个是shiroConfig类,一个是CustonRealm类,如果用redis相对发杂一些。
二、创建自定义realm 类
自定义的CustomRealm继承AuthorizingRealm。并且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。
/**
     * 自定义realm
     */
    public class CustomRealm extends AuthorizingRealm {
        @Autowired
        private AdminService aminService;

        public void setName(String name){
            super.setName("customRealm");
        }
        //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            return authorizationInfo;
        }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("==========认证===========");
        //获取登录信息
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //获取password
        char[] pwd = token.getPassword();
        //将password转成string
        String password=new String(pwd);
        System.out.println("=======token.getUsername========="+token.getUsername());
        System.out.println("=======token.password========="+password);
        //查询数据库
        Admin admin = aminService.adminLogin(token.getUsername());
        //判断用户名和密码是否一致
        if(admin!=null && admin.getPwd().equals(password)){
            //如果一致返回安全数据
            /**
             * 参数一:安全数据
             * 参数二:密码
             * 参数三:realm域名
             */
            SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo(admin,admin.getPwd(),this.getName());
            return simpleAuthenticationInfo;
        }
        return  null;
    }
}

注意:很容易入坑,char[] pwd = token.getPassword(); 这里必须是char[]。
之前我的是 String pwd = token.getPassword(); 然后一直报这行错误,然后检查代码没什么毛病,因为在controller中我的pwd就是String类型的。
在这里插入图片描述
然后寻求源代码的帮助,查看到了这样的代码。
在这里插入图片描述
是不是瞬间清爽啦。

三、自定义的sessionManager

public class CustomSessionManager extends DefaultWebSessionManager implements WebSessionManager {
    private final String X_AUTH_TOKEN = "x-auth-token";

    @Override
    protected void onStart(Session session, SessionContext context) {
        if (!WebUtils.isHttp(context)) {

        } else {
            HttpServletRequest request = WebUtils.getHttpRequest(context);
            HttpServletResponse response = WebUtils.getHttpResponse(context);
            Serializable sessionId = session.getId();
            this.storeSessionId(sessionId, request, response);
            request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
        }
        super.onStart(session, context);
    }
    /**
     * 把sessionId 放入 response header 中
     * onStart时调用
     * 没有sessionid时 会产生sessionid 并放入 response header中
     */
    private void storeSessionId(Serializable currentId, HttpServletRequest ignored, HttpServletResponse response) {
        if (currentId == null) {
            String msg = "sessionId cannot be null when persisting for subsequent requests.";
            throw new IllegalArgumentException(msg);
        } else {
            String idString = currentId.toString();
            response.setHeader(this.X_AUTH_TOKEN, idString);
            System.out.println("======idString======"+idString);
        }
    }

    /**
     * 头信息中具有sessionid
     *      请求头:Authorization : sessionId
     * 指定seesionid获取方式
     */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {

        //获取请求头中的Authorization的数据
        String id=WebUtils.toHttp(request).getHeader("x-auth-token");
        if(StringUtils.isEmpty(id)){
            //如果没有携带,生成新的sessionid 从cookie中获取
            System.out.println("前端传来的sessid="+id);
            return super.getSessionId(request,response);
        }else {
            //返回sessionId   从header中获取
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        }
    }
}

四、shrio配置类


/**
 * shrio配置类
 * 1.实现   ShiroFilterFactoryBean
 * 2.DefaultWebSecurityManager或SecurityManager
 * 3.CustomRealm 自定义
 */
@Configuration
public class ShrioConfig {
    //redis相关配置
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password}")
    private String password;

    //实现shiroFilterFactoryBean
      @Bean
      public ShiroFilterFactoryBean shiroFilterFactory(){
          //创建ShiroFilterFactoryBean
          ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
          //设置管理器
          shiroFilterFactoryBean.setSecurityManager(webSecurityManager());
          shiroFilterFactoryBean.setUnauthorizedUrl("/login/unauth");
          shiroFilterFactoryBean.setLoginUrl("/login/unauth");
          System.out.println("=========shiroFilterFactory==================");
          //设置自定义认证
          Map<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
          //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
          filterChainDefinitionMap.put("/logout", "logout");
          // 配置不会被拦截的链接 顺序判断,在 ShiroConfiguration 中的 shiroFilter 处配置了 /ajaxLogin=anon,意味着可以不需要认证也可以访问
          filterChainDefinitionMap.put("/login/login", "anon");
          filterChainDefinitionMap.put("/**", "authc");

          shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
          return shiroFilterFactoryBean;
      }

      //2.DefaultWebSecurityManager
     @Bean
     public DefaultWebSecurityManager webSecurityManager(){
         DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
         defaultWebSecurityManager.setRealm(customRealm());
         //将自定义的会话管理器注册到安全管理器
         defaultWebSecurityManager.setSessionManager(this.sessionManager());
         //将 自定义的redis缓存管理器注册到安全管理器
         defaultWebSecurityManager.setCacheManager(this.cacheManager());
         return defaultWebSecurityManager;
     }
     //3.
    @Bean
    public CustomRealm customRealm(){
          return new CustomRealm();
    }

    /**
     * 1.redis控制器,操作redis
     */
      public RedisManager redisManager(){
          RedisManager redisManager=new RedisManager();
          redisManager.setHost(host);
          redisManager.setPort(port);
          redisManager.setPassword(password);
          return redisManager;
      }
    /**
     * 2.sessionDao
     */
    public RedisSessionDAO sessionDAO(){
        RedisSessionDAO sessionDAO=new RedisSessionDAO();
        sessionDAO.setRedisManager(this.redisManager());
        return sessionDAO;
    }

    /**
     * 3.自定义会话管理
     */
    public DefaultWebSessionManager sessionManager(){
        CustomSessionManager sessionManager=new CustomSessionManager();
        sessionManager.setSessionDAO(this.sessionDAO());
        return sessionManager;
    }

    /**
     * 缓存管理器
     */
    public RedisCacheManager cacheManager(){
        RedisCacheManager redisCacheManager=new RedisCacheManager();
        redisCacheManager.setRedisManager(this.redisManager());
        return redisCacheManager;
    }
}

注意这里也是很容易入坑的,如果redis设置了密码,并且在属性文件中也定义了,这里一定要把password读取出来,否则调用时会报异常的。
在这里插入图片描述本人之前就是没有添加这段代码,导致入坑了,写了测试代码是完全可以连接redis的,看了代码才知道这里少了密码,必须注意虽然读取出来,还得需要在以下代码中设置密码。

 /**
     * 1.redis控制器,操作redis
     */
    public RedisManager redisManager(){
        RedisManager redisManager=new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setPassword(password);
        return redisManager;
    }

到这里shrio的基本配置以及完成,直接上代码业务:

//登录
@Controller
@RequestMapping("/login")
public class LoginController {
    private static final Logger logger= LoggerFactory.getLogger(LoginController.class);
    //执行登陆
    @PostMapping("/login")
    @ResponseBody
    public Result login(@RequestParam(value = "tel") String tel,@RequestParam(value = "pwd") String pwd) {
        logger.info("========初始密码=========="+pwd);
        try {
             /*pwd
          shrio提供的md5
          密码加密
          参数一:加密内容
          参数二:盐(用登录用户名)
          参数三:加密次数
         */
           pwd= new Md5Hash(pwd,tel,3).toString();
            //获取token
            UsernamePasswordToken token = new UsernamePasswordToken(tel, pwd);
            //获取主体
            Subject subject = SecurityUtils.getSubject();
            System.out.println("=========密码========="+pwd);
            logger.info("========加密密码=========="+pwd);
            //认证成功
            subject.login(token);
            //获取sesssion
            String sid=(String) subject.getSession().getId();
            return new Result(true, "登录成功",sid);
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            return new Result(false, "密码错误",null);
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return new Result(false, "用户不存在",null);
        }
    }

这里用到了给密码加密的方法,需要注意的是在插入的代码块中也是需要对密码进行加密加盐的。
加盐直接使用 password+username的方式进行加盐处理的。
在这里插入图片描述
到此 整合基本完成了。
测试: 未登录
在这里插入图片描述
登录后:
在这里插入图片描述

在这里插入图片描述
以上数据均为测试数据。

总结:shrio框架并不是很难,首先的理解它的执行原理以及它的核心,这样就会很快上手啦。如有遇到相关问题的同行,欢迎打扰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值