Java Web安全框架Shiro

相关文章链接:

IDEA使用Maven搭建SSM框架Web项目

观前提示:

本文所使用的IDEA版本为ultimate 2019.1,JDK版本为1.8.0_141,Tomcat版本为9.0.12,postman版本为v7.27.1。

1.简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。

这里建议大家去shiro官网看一下简介,附上链接:shiro官网

以下为shiro官网的一段引用
在这里插入图片描述

Shiro以Shiro开发团队所谓的应用程序安全性的四个基石为目标- Authentication,Authorization,Session Management和Cryptography:
1.Authentication:有时称为“登录”,这是证明用户就是他们所说的身份的行为。
2.Authorization:访问控制的过程,即确定“谁”有权访问“什么”。
3.Session Management:即使在非Web或EJB应用程序中,也可以管理用户特定的会话。
4.Cryptography:使用密码算法保持数据安全,同时仍易于使用。

在不同的应用程序环境中,还具有其他功能来支持和加强这些问题,尤其是:
1.Web Support:Shiro的Web支持API可帮助轻松保护Web应用程序。
2.Caching:缓存是Apache Shiro API的第一层公民,可确保安全操作保持快速有效。
3.Concurrency:Apache Shiro的并发功能支持多线程应用程序。
4.Testing:测试支持可帮助您编写单元测试和集成测试,并确保您的代码将按预期进行保护。
5.“Run As”:一种功能,允许用户采用其他用户的身份(如果允许),有时在管理方案中很有用。
6.“Remember Me”:记住用户在各个会话中的身份,因此他们仅在必要时登录。

2.核心组件

shiro主要有三个核心组件:Subject, SecurityManager 和 Realms

在这里插入图片描述
引用shiro官网的介绍

1.Subject:在Subject本质上是当前正在执行的用户的安全特定“视图”。“用户”一词通常表示一个人,一个人Subject可以是一个人,但它也可以表示第三方服务,守护程序帐户,cron作业或类似的东西-基本上是当前与该软件交互的任何东西。
Subject实例都绑定到(并要求)SecurityManager。当您与互动时Subject,这些互动会转化为与主题相关的互动SecurityManager。

2.SecurityManager:SecurityManager是Shiro体系结构的核心,并充当一种“伞”对象,该对象协调其内部安全组件,这些安全组件一起形成对象图。但是,一旦为应用程序配置了SecurityManager及其内部对象图,通常就不理会它,并且应用程序开发人员几乎所有的时间都花在SubjectAPI上。
稍后我们将SecurityManager详细讨论,但是重要的是要意识到,当您与进行交互时Subject,SecurityManager对于任何Subject安全操作而言,确实是幕后工作。这反映在上面的基本流程图中。

3.Realms:Realms充当Shiro与应用程序的安全数据之间的“桥梁”或“连接器”。当真正需要与安全性相关的数据(例如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从一个或多个为应用程序配置的Realms中查找许多此类内容。
从这个意义上说,Realms本质上是特定于安全性的DAO:它封装了数据源的连接详细信息,并根据需要使关联数据可用于Shiro。在配置Shiro时,您必须至少指定一个Realms用于身份验证和/或授权。所述SecurityManager可与多个境界被配置,但至少有一个是必需的。
Shiro提供了开箱即用的Realms,可以连接到许多安全数据源(又名目录),例如LDAP,关系数据库(JDBC),文本配置源(例如INI和属性文件)等。如果默认的Realms无法满足您的需求,那么您可以插入自己的Realm实现以表示自定义数据源。
像其他内部组件一样,Shiro SecurityManager管理着如何使用Realms来获取要表示为Subject实例的安全性和身份数据。

3.详细结构

在这里插入图片描述

引用shiro官网的介绍

1.Subject(org.apache.shiro.subject.Subject)
当前与软件进行交互的实体(用户,第三方服务,计划任务等)的特定于安全性的“视图”。

2.SecurityManager(org.apache.shiro.mgt.SecurityManager)
如上所述,这SecurityManager是Shiro体系结构的核心。它主要是一个“伞”对象,用于协调其托管组件以确保它们能够顺利协同工作。它还管理Shiro对每个应用程序用户的视图,因此它知道如何对每个用户执行安全性操作。

3.Authenticator(org.apache.shiro.authc.Authenticator)
负责执行和用户反应,以验证(登录)的尝试的组件。当用户尝试登录时,该逻辑由来执行Authenticator。该Authenticator知道如何与一个或多个协调Realms该商店相关的用户/帐户信息。从这些数据中获得的数据Realms用于验证用户的身份,以确保用户确实是他们所说的真实身份。

4.AuthenticationStrategy(org.apache.shiro.authc.pam.AuthenticationStrategy)
如果Realm配置了多个身份验证策略,则AuthenticationStrategy它将协调领域以确定确定尝试成功或失败的条件(例如,一个领域成功但其他领域失败) ,尝试是否成功?所有领域都必须成功吗?只有第一个领域?)。

5.Authorizer(org.apache.shiro.authz.Authorizer)
负责确定用户在该应用程序的访问控制。它是最终表明是否允许用户做某事的机制。像一样Authenticator,Authorizer还知道如何与多个后端数据源进行协调以访问角色和权限信息。在Authorizer使用该信息来准确确定是否允许用户执行特定的操作。

6.SessionManager(org.apache.shiro.session.mgt.SessionManager)
SessionManager知道如何创建和管理用户Session生命周期,提供在所有环境中的用户强大的会话体验。这是安全框架领域中的一项独特功能-Shiro能够在任何环境中本地管理用户会话,即使没有Web / Servlet或EJB容器也可以。默认情况下,Shiro将使用现有的会话机制(例如Servlet容器)(如果可用),但是如果没有这种机制(例如在独立应用程序或非Web环境中),它将使用其内置的企业会话管理来提供相同的编程经验。的SessionDAO存在允许任何数据源被用来坚持的会议。

7.SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO)
执行Session持久代(CRUD)操作SessionManager。这允许将任何数据存储插入会话管理基础结构。

8.CacheManager(org.apache.shiro.cache.CacheManager)
创建和管理Cache其他四郎组件使用实例的生命周期。因为Shiro可以访问许多后端数据源以进行身份​​验证,授权和会话管理,所以缓存一直是框架中的一流架构功能,可以在使用这些数据源时提高性能。可以将任何现代的开源和/或企业缓存产品插入Shiro,以提供快速有效的用户体验。

9.Cryptography (org.apache.shiro.crypto.*)
Cryptography 是企业安全框架的自然补充。Shiro的crypto软件包包含易于使用和理解的加密密码,哈希(又名摘要)和不同编解码器实现的表示形式。该软件包中的所有类都经过精心设计,以使其易于使用和理解。使用Java的本机加密技术支持的任何人都知道,驯服它可能是具有挑战性的动物。Shiro的加密API简化了复杂的Java机制,并使普通人容易使用加密技术。

10.Realm(org.apache.shiro.realm.Realm)
如上所述,Realm充当Shiro与应用程序的安全数据之间的“桥梁”或“连接器”。当真正需要与安全性相关的数据(例如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从一个或多个为应用程序配置的Realm中查找许多此类内容。您可以根据Realms需要配置任意数量(通常每个数据源一个),并且Shiro会根据需要进行协调,以进行身份​​验证和授权。

4. 例子

4.1 代码

pom.xml引入jar包

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.5.3</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.5.3</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.5.3</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.5.3</version>
</dependency>

配置 web.xml

需要添加代码

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
     classpath:/spring/applicationContext-shiro.xml
    </param-value>
  </context-param>
  
  <!-- shiro过滤器 -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <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>

完整代码如下

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
  <display-name>MyDemo</display-name>

  <!-- 读取log4j配置文件 -->
  <context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>classpath:/config/log4j.xml</param-value>
  </context-param>
  <!-- 添加日志监听器 -->
  <listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
  </listener>

  <!-- 读取spring配置文件 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath:/spring/applicationContext.xml,
      classpath:/spring/applicationContext-shiro.xml
    </param-value>
  </context-param>
  <!-- 添加spring监听器 -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 配置DispatcherServlet -->
  <servlet>
    <servlet-name>springServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- servlet配置文件的位置 -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:/spring/spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <!-- 拦截设置 -->
  <servlet-mapping>
    <servlet-name>springServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!-- shiro过滤器 -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <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>
  <!-- Spring字符集过滤器 -->
  <filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encoding</filter-name>
    <!--"/*"表示拦截所有的请求 -->
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

编写 ** spring-mvc.xml**

需要添加代码

    <!-- 启用shrio授权注解拦截方式 -->
    <aop:config proxy-target-class="true"></aop:config>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

完成代码如下

<?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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">

    <!-- 自动扫描且只扫描@Controller -->
    <context:component-scan base-package="com.example" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- 配置视图解析器 如何把handler 方法返回值解析为实际的物理视图 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- jsp路径前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <!-- jsp路径后缀 -->
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!-- 注解驱动 -->
    <mvc:annotation-driven/>

    <!-- 拦截器配置,根据自身业务配置,若无需求可不配置 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.example.interceptor.RequestMappingInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

    <!-- 启用shrio授权注解拦截方式 -->
    <aop:config proxy-target-class="true"></aop:config>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!-- 容器默认的DefaultServletHandler处理 所有静态内容与无RequestMapping处理的URL-->
    <mvc:default-servlet-handler/>

    <!-- SpringMVC上传文件时,需要配置MultipartResolver处理器 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8" />
        <!-- 指定所上传文件的总大小不能超过10485760000B。注意maxUploadSize属性的限制不是针对单个文件,而是所有文件的容量之和 -->
        <property name="maxUploadSize" value="10485760000"></property>
        <property name="maxInMemorySize" value="40960"></property>
    </bean>

    <!-- 静态资源映射 -->
    <mvc:resources mapping="/static/**" location="/WEB-INF/static/"/>
</beans>

编写 applicationContext-shiro.xml

<?xml version="1.1" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"   xmlns:util="http://www.springframework.org/schema/util"
       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.2.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
		http://www.springframework.org/schema/util  http://www.springframework.org/schema/util/spring-util-3.0.xsd"
       default-lazy-init="true">

    <description>Shiro配置</description>

    <!-- Shiro的主要业务层对象基于web的应用程序 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="securityRealm" />
<!--        <property name="cacheManager" ref="shiroEhcacheManager" />-->
    </bean>
    <!-- Shiro Filter -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login" />
        <property name="unauthorizedUrl" value="/error/401"/>
        <property name="filterChainDefinitions">
            <!--authc:代表shiro框架提供的一个过滤器,这个过滤器用于判断当前用户是否已经完成认证,
                         如果当前用户已经认证,就放行,如果当前用户没有认证,跳转到登录页面
                 anon:代表shiro框架提供的一个过滤器,允许匿名访问-->
            <value>
                <!-- 静态资源允许访问 -->
                /static/** = anon
                /user/delete = anon
                /** = authc
            </value>
        </property>
    </bean>

    <!-- 缓存管理器 使用Ehcache实现 -->
<!--    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">-->
<!--        <property name="cacheManagerConfigFile" value="classpath:resource/cache/ehcache-shiro.xml"/>-->
<!--    </bean>-->

    <!-- Shiro生命周期处理器 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 会话DAO -->
    <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO"/>

    <!-- 会话管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="sessionDAO" ref="sessionDAO"/>
    </bean>

</beans>

编写实现类 SecurityRealm.java

package com.example.security;

import org.apache.shiro.SecurityUtils;
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.apache.shiro.subject.Subject;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

@Component(value = "securityRealm")
public class SecurityRealm extends AuthorizingRealm {
    
    

    /**
     * 认证回调函数,登录时调用
     * @param authcToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
    
    
        UsernamePasswordToken upToken = (UsernamePasswordToken) authcToken;

        String account = (String) upToken.getPrincipal();
        char[] pwd = upToken.getPassword();
        StringBuilder sb = new StringBuilder();
        for (char c : pwd) {
    
    
            sb.append(c);
        }
        // 1.认证工号
        Object principal = account;
        // 2.认证密码
        Object credentials = sb.toString();

        if(!"admin".equals(account) || !"11111111".equals(credentials)){
    
    
            Subject subject = SecurityUtils.getSubject();
            subject.logout();
            throw new AuthenticationException("登录验证失败");
        }
        // 框架进行登录判断
        return new SimpleAuthenticationInfo(principal, credentials, getName());
    }

    /**
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    
    
        Subject subject = SecurityUtils.getSubject();
        // 查询用户拥有的权限并交给shiro框架
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> permissionSet = new HashSet<String>();
        permissionSet.add("user:search");

        //log.info("当前用户拥有的权限包括:{}" ,sns.toString());
        info.addStringPermissions(permissionSet);
        return info;
    }
}

登录 Login.java

package com.example.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

/**
 * @Description 登录Controller
 * @author jjy
 * @Creation 2019-02-21
 */
@Controller
public class LoginController {
    
    

    private static Logger log = LoggerFactory.getLogger(LoginController.class);

    /**
     * 登录
     * @param request
     * @return
     */
    @RequestMapping(value = "/login")
    public String init(HttpServletRequest request) {
    
    
        Subject subject = SecurityUtils.getSubject();
        // 身份验证
        subject.login(new UsernamePasswordToken(request.getParameter("userName"), request.getParameter("password")));

        return "login/login";
    }
}

编写 UserController.java

package com.example.controller;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

@Controller
@RequestMapping("user")
public class UserController {
    
    

    @RequestMapping(value = "/search", method = RequestMethod.POST)
    @RequiresPermissions("user:search")
    @ResponseBody
    public String search(HttpServletRequest request) {
    
    
        return "success";
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    @RequiresPermissions("user:add")
    @ResponseBody
    public String add(HttpServletRequest request) {
    
    
        return "success";
    }

    @RequestMapping(value = "/delete", method = RequestMethod.POST)
    @ResponseBody
    public String delete(HttpServletRequest request) {
    
    
        return "success";
    }
}

4.2 测试

测试均使用的postman发送请求

4.2.1 成功登陆

发送请求:http://localhost:8080/MavenMvc/login
在这里插入图片描述

结果成功跳转login.jsp页面为
在这里插入图片描述

4.2.2 无效用户登陆

发送请求:http://localhost:8080/MavenMvc/login
在这里插入图片描述
结果为
在这里插入图片描述

4.2.3 测试开放接口

发送请求:http://localhost:8080/MavenMvc/user/delete

在这里插入图片描述
结果为

在这里插入图片描述

4.2.4 未认证访问接口

发送请求:http://localhost:8080/MavenMvc/user/search
在这里插入图片描述
结果为
在这里插入图片描述

4.2.5 认证成功且拥有访问接口权限

发送请求:http://localhost:8080/MavenMvc/user/search
在这里插入图片描述

结果为
在这里插入图片描述

4.2.6 认证成功但没有访问接口权限

发送请求:http://localhost:8080/MavenMvc/user/add
在这里插入图片描述

结果为
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.可能遇到的问题(坑)

5.1 没有认证就能访问接口

有可能是因为你没有在 applicationContext-shiro.xml中配置

/** = authc

在这里插入图片描述

  1. authc:代表shiro框架提供的一个过滤器,这个过滤器用于判断当前用户是否已经完成认证,如果当前用户已经认证,就放行,如果当前用户没有认证,跳转到登录页面

  2. anon:代表shiro框架提供的一个过滤器,允许匿名访问

5.2 没有执行doGetAuthorizationInfo进行授权

有可能是你没有在 spring-mvc.xml中配置

    <aop:config proxy-target-class="true"></aop:config>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43611145/article/details/107005934