Web认证框架之Shiro实战

前面简单介绍了shiro这个框架的一些基本知识,包括其架构模型,主要的功能,关键名称的含义,以及核心模块和对应的接口定义。

开始的话

​ 今天我们从一个简单示例,先了解使用shiro实现Web应用认证时,一名开发者需要做些什么。同样秉持着技术学习的原则,我们仅仅使用shiro框架以及一些其他简化开发的工具库,不是涉及到一些IOC容器,这样在进行模块配置以及依赖关系梳理时,通过手动的配置的方式,让我们更加容易理解…

文章介绍

通过这篇文章,你可以有以下几个方面的收获:

  • 基于maven创建一个项目,养成项目依赖统一管理的习惯
  • 了解shiro在项目中的使用过程以及相关的配置
  • 了解如何实现通过shiro完成认证以及授权
  • 了解shiro认证流程
  • 对shiro从理论的认识升华到基础实践
  • 获得一个演示示例

示例实现

创建项目

1) 你可以选择通过你的IDE快速创建一个项目,比如通过Intellij Idea,通过File->New->Project选择Maven Archetype创建一个空项目,这里archetype可以选择quickstart

创建项目

这里你很可能遇到一个idea的bug,按上图提交后,发现idea卡死了,项目创建失败且无法打开,如果没有就恭喜你了

2)最终我们需要得到一个文件夹,里面有个pom.xml文件,结构可以如下(如果有其他的内容建议删除,比如src,因为这个pom我们作为项目parent维护)

创建结构

添加依赖

细心的你会注意到,在根目录下有个pom.xml,同时还有个ui-mvc目录下也有个pom.xml文件,根目录下的我一般习惯作为整个项目的父级依赖配置文件,用来管理所有依赖、插件版本以及属性值,ui-mvc下的pom主要通过parent实现属性继承,这样来实现配置集中化管理

./pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  <project>
  	//...
  	  <groupId>com.sucls.security</groupId>
      <artifactId>auth-shiro</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>pom</packaging>
      
      <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <maven.compiler.source>1.8</maven.compiler.source>
       <maven.compiler.target>1.8</maven.compiler.target>
       <shiro.version>1.9.1</shiro.version>
       // ...
    </properties>
      
   <dependencyManagement>
    <dependencies>
      <!-- 核心 -->
      <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>${shiro.version}</version>
      </dependency>

      <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>${shiro.version}</version>
      </dependency>
      // ...
      </dependencies>
   </dependencyManagement>
     // ...
  </project>

./ui-mvc/pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  <parent>
    <groupId>com.sucls.security</groupId>
    <artifactId>auth-shiro</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../../pom.xml</relativePath>
  </parent>
  <artifactId>auth-shiro-ui-mvc</artifactId>
  <packaging>war</packaging>
  <name>ui-mvc</name>

  <dependencies>
    <!-- 核心 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
    </dependency>
  </dependencies>
  

大部分内容都省略了,可以参考文末源代码,主要引入了shiro相关的依赖,以及一个提高开发效率的工具包

编写shiro相关配置

我们所有代码都会写到ui-mvc模块下,看到名称就知道我们通过mvc的结构,因此前端是通过jsp这个老技术,后面会讲到如何通过前后端分离来实现认证功能。

下面主要从以下几个方面进行配置:
web.xml 以前说过,认证基本都是基于Filter实现,同样shiro有一个核心的过滤器(该过滤器会将我们的配置解析成一个个过滤器链)

1
2
3
4
5
6
7
8
9
 <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

配置监听,在系统启动时基于ServletContextListener调用初始化参数完成一些基本的系统初始化工作

1
2
3
4
5
6
7
8
  <context-param>
    <param-name>shiroEnvironmentClass</param-name>
    <param-value>org.apache.shiro.web.env.DefaultWebEnvironment</param-value>
  </context-param>
  
 <listener>
    <listener-class>com.sucls.security.security.SimpleEnvironmentLoaderListener</listener-class>
  </listener>

这里通过自定义的监听,SimpleEnvironmentLoaderListener类承载了shiro基本上所有的配置,在这里我们会完整地构建SecurityManager对象以及Filter的配置,如果读过以前的文章你就会知道,SecurityManager主要的工作包括:

  • 代理实现用户身份的认证
  • 对API结果进行缓存
  • 实现了用户多种形式登录(多Realm)
  • 实现用户会话管理
  • 实现记住我功能

ShiroFilter配置

1
2
3
4
5
6
7
8
FormAuthenticationFilter authcFilter = (FormAuthenticationFilter) filterChainResolver.getFilterChainManager().getFilters().get(DefaultFilter.authc.name());
        authcFilter.setLoginUrl("/login.jsp");
        authcFilter.setSuccessUrl("/index.html");

filterChainResolver.getFilterChainManager().addToChain("/webjars/**", DefaultFilter.anon.name());
filterChainResolver.getFilterChainManager().addToChain("/assets/**", DefaultFilter.anon.name());

filterChainResolver.getFilterChainManager().addToChain("/**", DefaultFilter.authc.name());

这里主要通过配置FilterChainResolver对象来完成ShiroFilter对象的构建,后面讲到源代码时会细说。

SecurityManager配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void configureWebSecurityManager(DefaultWebSecurityManager securityManager) {
//
        securityManager.setAuthenticator(newAuthenticator());

        securityManager.setRealms(Arrays.asList(initRealm()));
    }

    private Authenticator newAuthenticator() {
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); // 至少一个Realm
        authenticator.setRealms(Arrays.asList(initRealm()) );
        return authenticator;
    }

 private Realm initRealm(){
        return RealmBuilder.create()
                .inMemoryRealm()                .user("admin").password("123456").role("ROLE_ADMIN").permissions(Arrays.asList("read","edit","create","delete"))
                .and()
                    .user("user").password("123456").role("ROLE_USER").permission("read")
                .build();
    }

这里仅根据需要配置了Realm以及认证规则Authenticator

引入页面

前端模板来源bootstrap quicl,在此基础上修改了远程资源(js、css)为本地引用,以及引入jsp文件头对应的配置

启动项目

通过引入tomcat插件来启动项目:

1
2
3
4
5
6
7
8
9
<plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.2</version>
    <configuration>
        <port>8080</port>
        <path>/</path>
    </configuration>
</plugin>

这样不需要我们单独弄个tomcat,通过插件即可实现项目热启动,方便调试与静态页面的修改 启动项目

执行认证

1)进入系统 http://localhost:8080/ 由于没有登录,跳转到登录页http://localhost:8080/login.jsp 启动项目 2)输入用户名密码,按以上的配置admin/123456,登录成功,进入主页 启动项目 3)用户鉴权,上面可以看到admin配置了ROLE_ADMIN角色,user配置了ROLE_USER角色,所以

admin登录访问http://localhost:8080/system/getProperties.json 正常返回数据 admin登录访问http://localhost:8080/subject/getSubject.json 进入未授权页面

关于shiro鉴权部分主要是基于Filter以及AOP完成,其中请求交易基于Filter,在请求时基于登录权限信息进行交易拦截,而AOP则可以针对方法的调用阶段,更加灵活和通用。具体的实现过程后面会细说,同时可以看到示例简单的实现过程

示例分析

在整个示例中,会议我们做了什么,有什么用?

  1. 引入shiro相关的依赖包,这点没什么说的
  2. 配置web.xml的Filter以及Listener Listener则是基于Servlet的ServletContextListener规则,在web容器启动后,调用contextInitialized方法完成容器初始化工作。这里的初始化包括:
    • 对SecurityManager的配置,后面我们会看到关于shiro的配置基本都是针对这个对象。包括Realm、多Realm认证策略、RememberMe、SessionManager等等
    • 对FilterChainResolver的配置,目的是为了完善ShiroFilter对象,主要针对请求路径对应的过滤器,下面每一行都是一个根据请求路径匹配的过滤器链,一段请求匹配,则进入对应过滤器链,所有注意配置顺序
      1
      2
      3
      4
      5
      /assets/** = anon
      /login = authc
      /admin/** = roles[ROLE_ADMIN]
      /admin/add* = perms[add:*]   //[action:type:instance]
      /** = authc 
      
  3. 引入静态页面,这里基于jsp实现,当然还有其他前端模板引擎可以使用,其中静态资源通过引入webjars来加载
  4. 由于没有引入springmvc,在资源映射(请求到页面或交易)以及依赖管理、AOP装配等等都是手动通过代码完成,增加了代码复杂度,但是结构会更清晰,更容易理解

要知道,我们的目的是为SecurityManager注入属性以及配置ShiroFilter过滤规则,并不一定需要基于ServletContextListener,这只是一个选择,比如还可以在Filter的init方法中完成,只要保证系统启动后对应的配置加载或处理完成即可。

示例代码

​ 参考 https://github.com/sucls/auth-shiro

结束语

​ 这篇文章主要通过示例了解如何使用shiro完成web应用的认证,但是具体怎么实现的,有哪些配置,具体配置的作用,由于篇幅有限,下一篇将结合源代码具体分析其内部实现原理。

Java Geek Tech wechat
欢迎订阅 Java 技术指北,这里分享关于 Java 的一切。