Javaweb安全框架之Shiro基础知识

  • Post author:
  • Post category:java



综述:之前做过一些小项目,都是手动进行安全和权限验证,相当的麻烦,而且权限验证又是实际开发时必不可少的步骤,因此我们可以借助一些框架来实现,今天开始学习Shiro权限框架


一、Shiro简介

Apache Shiro 是 Java 的一个安全框架,Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:

在这里插入图片描述


Authentication:

身份认证 / 登录,验证用户是不是拥有相应的身份;


Authorization:

授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;


Session Manager:

会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;


Cryptography:

加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;


Web Support:

Web 支持,可以非常容易的集成到 Web 环境;


Caching:

缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;


Concurrency:

shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;


Testing:

提供测试支持;


Run As:

允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;


Remember Me:

记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。


注意:

Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可。


二、Shiro API

Shiro 的对外 API 核心是 Subject。

Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,可以把它看成SpringMVC中的 DispatcherServlet 前端控制器;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理

Realm:域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个 Shiro 应用:

应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;

我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。

在这里插入图片描述

subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。

securityManager:安全管理器,主体进行认证和授权都 是通过securityManager进行。

authenticator:认证器,主体进行认证最终通过authenticator进行的。

authorizer:授权器,主体进行授权最终通过authorizer进行的。

sessionManager:web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。

SessionDao: 通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。

cache Manager:缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。

realm:域,领域,相当于数据源,通过realm存取认证、授权相关数据。

cryptography:密码管理,提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

比如md5散列算法。

以上内容转载自:


Shiro基础知识——简介


三、Shiro与Spring整合

1、首先在pom.xml中添加依赖

2、配置web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1" metadata-complete="true">

  <display-name>Archetype Created Web Application</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mybatis.xml,classpath:spring-mvc.xml,classpath:springshiro.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-*.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 设置拦截 !-->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>log4jContextName</param-name>
        <param-value>log4jContext</param-value>
    </context-param>
    <!-- 添加shiro的过滤器代理!-->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <async-supported>true</async-supported>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name> <!-- 此处必需写成"/*",如果只写"/" ,过滤器没有作用-->
        <url-pattern>/*</url-pattern> 
    </filter-mapping>
</web-app>

3、第三步就是编写springshrio.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
       default-lazy-init="true">

    <!-- 配置安全管理器securityManager, 缓存技术: 缓存管理 realm:负责获取处理数据 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myShiroRealm" />

        <!-- 加入缓存管理 -->
        <property name="cacheManager" ref="cacheManager" />

        <!-- 加入remember管理 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>

        <!-- 会话管理 -->
        <!--<property name="sessionManager" ref="sessionManager"/>-->

        <!--<property name="sessionMode" value="native"/>-->
    </bean>


    <!-- 缓存管理器 -->
    <!--<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">-->
    <!--    &lt;!&ndash; cache配置文件 &ndash;&gt;-->
    <!--    <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>-->
    <!--</bean>-->

    <!-- 会话cookie模板 -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- 写到cookie的name值 -->
        <constructor-arg value="sid"/>
        <!-- 设置js是否可以访问cookie,true 不能访问 -->
        <property name="httpOnly" value="true"/>
        <property name="name" value="rememberMe"/>
        <!-- 保存时长10分钟,以秒为单位 -->
        <property name="maxAge" value="600"/>
    </bean>
    
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <!-- ipherKey是加密rememberMe cookie 的密钥;默认AES算法 -->
        <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}" />
        <!-- 引入上面定义的cookie模板 -->
        <property name="cookie" ref="rememberMeCookie" />
    </bean>


    <!-- 項目自定义的Realm,从数据库中获取用户的安全数据 -->
    <bean id="myShiroRealm" class="com.stitp.shiro.UserRealm">
        <!-- 配置缓存管理器-->
        <property name="cacheManager" ref="cacheManager" />
        <!-- 配置加密器 -->
        <!--<property name="credentialsMatcher">-->
            <!--<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">-->
                <!--<property name="hashAlgorithmName" value="MD5"/> &lt;!&ndash; 加密算法的名称 &ndash;&gt;-->
                <!--<property name="hashIterations" value="1024"/> &lt;!&ndash; 配置加密的次数 &ndash;&gt;-->
            <!--</bean>-->
        <!--</property>-->
        
    </bean>

    <!-- 用户授权信息Cache -->
    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />

    <!-- 必须配置lifecycleBeanPostProcessor:管理Shiro中常见的对象 -->
    <!-- 保证实现了Shiro内部lifecycle函数的bean执行,同时也把Shiro的说明周期交给spring管理 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

    <!-- shiro的核心配置:  配置shiroFileter id名必须与web.xml中的filtername保持一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 调用我们配置的权限管理器 -->
        <property name="securityManager" ref="securityManager" />

        <!-- 没有登录重定向到登录页面 -->
        <property name="loginUrl" value="/login.jsp" />

        <!--登录成功界面-->
        <property name="successUrl" value="/success.jsp" />

        <!-- 没有权限跳转的页面 -->
        <property name="unauthorizedUrl" value="/index.jsp" />

        <!-- shiro过滤器的具体配置 -->
        <!-- anon 匿名访问,authc-必须要认证以后才能访问的 -->
        <!--authc 必须身份认证通过才能访问-->
        <!-- user 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查-->
        <property name="filterChainDefinitions">
            <value>
                <!-- 具体配置需要拦截哪些 URL, 以及访问对应的 URL 时使用 Shiro 的什么 Filter 进行拦截.  -->
                /login.jsp = anon
                /register.jsp = anon
                /fail.jsp = authc
                /success.jsp = user
            </value>
        </property>
    </bean>
</beans>

各个步骤的用处都已经注释到了上面。

4、下面实现自定义Realm,继承AuthorizingRealm

package com.stitp.shiro;

import com.stitp.service.UserService;
import org.apache.shiro.authc.*;
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;

public class UserRealm extends AuthorizingRealm{

    @Autowired
    private UserService userService;

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

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();
//        System.out.println(username);
        String password = userService.findByName(username);
        System.out.println(password);

        if (password == null) {
            throw new UnknownAccountException(); //没有找到账号
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                username, //用户名
                password, //密码
                getName() //realm name
        );
        return authenticationInfo;
    }
}

5、实现Controller

    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public String login(
            User user,
            @RequestParam(value = "rememberme",required = false) boolean remember,
            Model model) {
        String username = user.getUsername();
        String password = user.getPassword();
        System.out.println(username);
        System.out.println(remember);
        String error = null;
        if (username != null && password != null) {
            //初始化
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            System.out.println(token + "----------");
            try {
                if(remember)
                token.setRememberMe(true); //这两步是记住我功能实现的关键步骤
                //登录,即身份校验,由通过Spring注入的UserRealm会自动校验输入的用户名和密码在数据库中是否有对应的值
                subject.login(token);
                return "/success.jsp";
            }catch (Exception e){
                e.printStackTrace();
                error = "未知错误,错误信息:" + e.getMessage();
            }
        } else {
            error = "请输入用户名和密码";
        }
        //登录失败,跳转到login页面,这里不做登录成功的处理,由
        model.addAttribute("error", error);
        return "/fail.jsp";
    }

在这里插入图片描述

由上图可知,记住我功能可以使用

至此,利用SSM + Shiro实现基本的权限认证和记住我功能简单实现完成。

注意:在刚开始的时候可能会注入不到userService,这是要在web.xml设置

spring-mvc.xml的加载顺序优先于springshiro.xml,具体操作参见:



Shiro解决无法注入Service问题(包括Spring MVC和Spring Boot)

记住我功能实现可参阅:


shiro教程:记住我功能



另外,推荐阅读:



Shiro系列相关文章(一)





Shiro系列相关文章(二)



版权声明:本文为qq_43060759原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。