2021年你还不会Shiro?----6.Shiro与SpringBoot的集成


前言:
当下最流行的java框架就是SpringCloud与SpringBoot了,这篇文章总结了下Shiro与SpringBoot的集成使用,做了一个简单的登录功能。但是登录功能依然未使用数据库,数据是模拟的,下篇文章里会总结Shiro+JSP+SpringBoot+MyBatis+mysql来实现认证授权的真实场景。

一.整合过程

首先需要明确使用Shiro的目的,就是为了让系统中的部分资源不可以被随便访问,或者是接口,或者是静态资源,这类资源的访问我们要对请求进行拦截,怎么拦截呢,Shiro中提供了ShiroFilter用来拦截这些访问受限资源的请求,我们只需要将ShiroFilter注入到Spring的容器中即可,然后配置下ShiroFilter需要过滤哪些请求就可以正常使用了。既然很多请求需要被Shiro过滤后才能访问,那么也有部分资源时不需要被拦截的,比如我们的登录页,注册页,亦或者商城的商品浏览页,商品详情页等等这些都是不能拦截的,这部分就是公共资源了。对于公共资源我们应该进行放行。下面我们就来看下怎么一步步实现Shiro与SpringBoot的整合吧。
在这里插入图片描述

1.使用Spring initializr创建SpringBoot工程

如下图所示,我们FIle–>New–>Project下选择该功能,然后可以快速创建一个SpringBoot的工程,若是默认的仓库中没有所需jar,就会使用图片中选中的地址进行下载模板。
在这里插入图片描述

2.选择jdk版本,选择启动器

创建项目时,注意这两块的选择就行,项目名随便起,看心情吧,JDK选择自己开发环境已经安装了的就行,不过使用Sping initializr功能,JDK7可能已经不支持了,最好是JDK8或者JDK11,这俩都是目前仍在更新的JDK的大版本,其余的JDK16以下的版本都已经停止更新了。
在这里插入图片描述
然后就是选择我们需要的启动器了,启动器这里只需要选择支持JSP的启动器,顺便选择lombok,这是支持类实体注解的依赖,此外还需要选择web开发的启动器就行,如下图。

在这里插入图片描述

3.创建jsp页面启动工程

到这里其实一个web工程我们就创建完成了,如果网络不好,我们需要静静等待一会,让项目将jar包下载完成。依赖下载完成后,我们需要创建几个jsp页面用来与后台接口进行交互。我们在main的下面创建一个webapp文件夹用来存放jsp页面文件。
在这里插入图片描述
在这里插入图片描述
我们新建一个login.jsp的页面,页面内容如下,这也是jsp的标准模板

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>标题</title>
    <style type="text/css">
        *{
    
    margin: 0;padding: 0;}
        form{
    
    margin: 0 auto;padding:15px; width: 300px;height:300px;text-align: center;}
        #submit{
    
    padding: 10px}
        #submit input{
    
    width: 50px;height: 24px;}
    </style>
</head>
<body>
    <h1>登录页</h1>

    <form action="${pageContext.request.contextPath}/user/login" method="post">
        用户名:<input type="text" name ="username"/><br/>
        密 码 :<input type="text" name ="password"/><br/>
        <input type="submit" value="登录"><br/>
    </form>
</body>
</html>

再创建一个index.jsp页面,供登录成功后进行展示。如下图:

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>标题</title>
    <style type="text/css">
        *{
    
    margin: 0;padding: 0;}
        form{
    
    margin: 0 auto;padding:15px; width: 300px;height:300px;text-align: center;}
        #submit{
    
    padding: 10px}
        #submit input{
    
    width: 50px;height: 24px;}
    </style>
</head>
<body>
    <h1>系统主页</h1>
    <ul>
        <a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
        <li><a href="">用户管理</a></li>
        <li><a href=""></a>商品管理</li>
        <li><a href=""></a>商户管理</li>
        <li><a href=""></a>内容管理</li>
    </ul>
</body>
</html>

jsp页面创建完成后,我们来配置下application.yml配置文件,配置下服务端口、服务名、配置前端文件支持jsp等,如下所示:

# 服务端口
server.port=8888
# 项目访问路径
server.servlet.context-path=/shiro
# 项目名
spring.application.name=shiro

# 配置mvc的视图解析器支持jsp,默认不支持
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

4.启动工程查看登录页面,登录成功配置依赖

在这里插入图片描述
从图中看出,我们的项目已经正常启动了,如果没有正常启动,可以编辑下该工程的配置信息更改下Working directory.将该值改成如下图所示,然后重启就会正常了。
在这里插入图片描述
这是我们项目已经启动正常,然后加入Shiro与JSP的依赖。如下所示:

<!--引入支持jsp的依赖-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <!--引入支持jstl的依赖-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <!--引入支持shiro的启动器-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>

5.创建controller,自定义Realm,并将Realm等其他对象注入到Spring容器中

我们需要提供一个简单的控制器,里面有登录和退出登录的方法。供jsp的调用。代码如下:

@Controller
@RequestMapping("/user")
public class LoginController {
    
    

    @PostMapping("/login")
    public String login(String username,String password){
    
    
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username,password);
        Subject subject = SecurityUtils.getSubject();
        try {
    
    
            subject.login(usernamePasswordToken);
        } catch (UnknownAccountException e) {
    
    
            e.printStackTrace();
            System.out.println("用户名错误");
            return "redirect:/login.jsp";
        } catch(IncorrectCredentialsException e){
    
    
            e.printStackTrace();
            System.out.println("密码错误");
            return "redirect:/login.jsp";
        }
        return "redirect:/index.jsp";
    }

    @RequestMapping("logout")
    public String logout(){
    
    
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login.jsp";
    }
}

控制器写好了,我们还需要写一个自己的Realm,我们都知道,Shiro的数据来源都是Realm,所以我们实现自己的登录必须自己实现一个Realm用来获取认证和授权的信息,这里先不实现授权的部分,先只是进行认证的流程操作,不过在认证时依然使用MD5+盐+hash散列的方式对密码进行加密(前面的文章已经说过怎么实现这里就直接写了)。

public class FirstRealm extends AuthorizingRealm {
    
    

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        System.out.println("进入认证方法");
        String username = (String)authenticationToken.getPrincipal();
        Md5Hash md5Hash = new Md5Hash("123","234@#$",1024);
        if(username.equals("luban")){
    
    
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("luban",md5Hash.toString(), ByteSource.Util.bytes("234@#$"),this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

Realm定义完成以后,我们需要将自定义的Realm注入到Spring容器中,将对象注入到容器中我们可以在xml文件中实现,也可以使用注解实现,最常用的还是通过配置类来实现,即我们自己定义一个配置类通过@Bean注解将对象注入到Spring容器中,如下所示:

@Configuration
public class ShiroConfig {
    
    

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
    
    
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        Map<String,String> map = new HashMap<>();
        map.put("/user/login","anon");//表示该资源无需认证授权,无需授权的应该写在上面
        map.put("/user/logout","anon");//表示该资源无需认证授权
        map.put("/login.jsp","anon");//表示该资源无需认证授权
       
        map.put("/**","authc");//表示所有资源都需要经过认证授权
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        //设置授权失败返回的页面
        shiroFilterFactoryBean.setLoginUrl("login.jsp");//这也是默认值
        return shiroFilterFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(FirstRealm firstReaml){
    
    
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(firstReaml);
        return defaultWebSecurityManager;
    }

    @Bean
    public FirstRealm getRealm(){
    
    
        FirstRealm firstRealm = new FirstRealm();
        Md5CredentialsMatcher md5CredentialsMatcher = new Md5CredentialsMatcher();
        md5CredentialsMatcher.setHashIterations(1024);
        firstRealm.setCredentialsMatcher(md5CredentialsMatcher);
        return firstRealm;
    }

}

如上所示,ShiroFilter是通过ShiroFilterFactoryBean来获取,这个ShiroFilter的工厂,然后为他注入SecurityManager,这里的SecurityManager是DefaultWebSecurityManager,这个是适用于web版本的安全管理器,web开发就使用它就对了,此外在FirstRealm这个自定义的Realm中我们需要自定义一个密码匹配器,用来匹配MD5+盐+hash散列来加密的密码,并告诉该匹配器我们的密码经过了多少次散列,然后就可以了。
下面列出了,ShiroFilter中支持的配置参数,以及对应的含义,通常都是使用最上面的两种。
在这里插入图片描述

6.启动工程,测试登录功能。

到这里我们的采用MD5+盐+hash散列的Shiro+SpringBoot实现的登录功能就完成了,接下来我们来测试下,我们使用的是账号是luban:123,我们先使用luban:1234来登录测试下

在这里插入图片描述
然后发现登录失败又跳回了登录页,这是因为我们在配置ShiroFilter时,配置了认证失败的返回页就是login.jsp,所以又跳回了这里。
后端报错如下,很明显这是密码错误。
在这里插入图片描述
那我们在使用账号luban:123登录测试下,结果如下:
在这里插入图片描述

我们可以发现已经正常进入到系统内部了,这样就验证了我们使用Shiro+SpringBoot实现的登录功能就完全正常了。

二.整合中的问题与思考

在整合中我们可能会碰到各种问题,这里整理了下,我在整合过程中碰到的一些问题以及思考。

1.ShiroFilter过滤路径配置问题

在配置我们需要过滤的路径与需要排除的路径时,有的人说需要将公共资源的路径配置在上面,受限资源配置在下面,就像这种

Map<String,String> map = new HashMap<>();
map.put("/user/login","anon");//表示该资源无需认证授权,无需授权的应该写在上面
map.put("/user/logout","anon");//表示该资源无需认证授权
map.put("/login.jsp","anon");//表示该资源无需认证授权
map.put("/**","authc");//表示所有资源都需要经过认证授权
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

但是经测试将过滤资源的代码,放在公共资源的上方是可以正常使用的,当然不排除版本变更修复了这个问题,但是从目前测试使用的Shiro1.5.3以后配置公共资源与受限资源肯定不需要考虑顺序了。

2.配置了login.jsp为受限资源未登录的展示页,还需要配login.jsp为公共资源吗

有人说配置了受限资源访问不成功就重定向到登录页,然后就不需要为登录页设置公共资源的标识了,在ShiroFilter中我们可以看到,当前我写的demo是配置了登录页为公共资源的,在我测试该场景时,如果仅仅指定受限资源未登录的展示页,然后我们访问受限资源index.jsp就会报这个问题,如下所示:
在这里插入图片描述
这个错误图中已经说了,就是因为系统重定向次数过多所导致,访问index.jsp我们没有登录系统就将我们重定向到login.jsp然后,发现login.jsp也不是公共资源继续重定向login.jsp就陷入死循环了,所以这里我们得配置login.jsp为公共资源。然后就正常了。如下:

map.put("/login.jsp","anon");//表示该资源无需认证授权

3.登录成功后非正常情况退出,重新登录系统,用户名和密码错误也会进入系统

当然这里展示的代码并没有这个问题,在我发现这个问题后已经修改了代码,主要是这块代码

 @PostMapping("/login")
  public String login(String username,String password){
    
    
      UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username,password);
      Subject subject = SecurityUtils.getSubject();
      try {
    
    
          subject.login(usernamePasswordToken);
      } catch (UnknownAccountException e) {
    
    
          e.printStackTrace();
          System.out.println("用户名错误");
          return "redirect:/login.jsp";
      } catch(IncorrectCredentialsException e){
    
    
          e.printStackTrace();
          System.out.println("密码错误");
          return "redirect:/login.jsp";
      }
      return "redirect:/index.jsp";
  }

说下这个错误的原因,等用户登录的用户名或者密码错误时,都会被程序catch住,然后抛出异常,程序继续执行下去就会返回inde.jsp页面,又因为刚刚没有正常退出,第一次登录后,Shiro会缓存登录信息,所以我们可以正常进入到index.jsp页面,我们只需要在catch住错误时,直接返回到login.jsp就可以正常了。

三.总结

这篇文章里总结了Shiro与SpringBoot的整合,思路也很明确,先引入Shiro的依赖,然后自定义Realm,然后通过配置类注入Realm与Filter、SecurityManager等的对象,再提高登录的接口,登录的页面,与登录的首页等,最后分析了可能会碰到的几个问题,希望对路过的朋友有所帮助。

猜你喜欢

转载自blog.csdn.net/m0_46897923/article/details/115052438