java集成shiro框架

简介

java集成shiro框架

Apache Shiro 是一个功能强劲且易于使用的Java安全框架,可执行身份验证、授权、加密和会话管理。有了Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的web和企业应用程序。

特色

Shiro的目标是Shiro开发团队所称的“应用程序安全的四大基石”——认证、授权、会话管理和加密:

java集成shiro框架

  • Authentication: 有时也被称为“登录”,这是一种证明用户是谁的行为。
  • Authorization: 访问控制的过程,即确定“谁”可以访问“什么”。
  • Session Management: 管理特定于用户的会话,甚至是非web或EJB应用程序
  • Cryptography: 使用加密算法确保数据安全,同时依旧易于使用。

在不同的应用程序环境中,还提供了一些额外的特性来支持和加强这些关注点,特别是:

  • Web Support: Shiro的web支持api有助于轻松地保护web应用程序。
  • Caching: 在Apache Shiro的API中,缓存是第一层的,以确保安全操作保持快速和高效。
  • Concurrency: Apache Shiro通过其并发特性支持多线程应用程序。
  • Testing: 测试支持可以协助您编写单元测试和集成测试,并确保您的代码如预期的那样安全。
  • “Run As”:允许用户使用另一个用户身份的特性(如果允许的话),有时在管理场景中很有用。
  • “Remember Me”:记住用户跨会话的身份,这样他们只需要在强制时登录。

系统架构

从外部来看Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成

Apache Shiro的设计目标是通过直观和易于使用来简化应用程序的安全性。Shiro的核心设计模拟了大多数人对应用程序安全性的见解——在某人(或某物)与应用程序交互的上下文中。

软件应用程序一般是基于用户故事设计的。也就是说,您一般会根据用户将如何(或应该如何)与软件交互来设计用户界面或服务api。例如,您可能会说:“如果与我的应用程序交互的用户已经登录,我将向他们显示一个按钮,他们可以单击该按钮来查看自己的帐户信息。如果他们没有登录,我会显示一个注册按钮。”

这个示例语句表明,编写应用程序主要是为了满足用户的需求。即使“用户”是另一个软件系统而不是人,您依旧需要编写代码来反映当前与您的软件交互的人(或物)的行为。

Shiro在自己的设计中反映了这些概念。通过匹配软件开发人员已经具备的直觉性,Apache Shiro在实际应用程序中依旧保持直觉性,易于使用。

在最高的概念层次上,Shiro的架构有3个主要概念:主题(Subject)、安全管理器(SecurityManager)和领域(realm)。下面的图表是这些组件如何交互的高级概述,我们将在下面讨论每个概念:

java集成shiro框架

  • Subject: 正如我们在教程中提到的,Subject本质上是当前执行用户的一个安全特定的“视图”。虽然“用户”一词一般意味着人,但Subject可以是一个人,但它也可以代表第三方服务、守护帐户、cron作业或任何类似的东西——基本上是当前与软件交互的任何东西。
  • Subject实例都绑定到(并需要)SecurityManager。当您与Subject交互时,这些交互转换为与SecurityManager的特定于主题的交互
  • SecurityManager: SecurityManager是Shiro架构的核心,充当一种“保护伞对象”,协调构成对象图的内部安全组件。不过,一旦SecurityManager和它的内部对象图为应用程序配置好后,一般就不会再去管它了,应用程序开发人员将几乎所有的时间都花在Subject API上。我们将在稍后详细讨论SecurityManager,但重大的是要认识到,当您与Subject交互时,实际上是SecurityManager在幕后为任何Subject安全操作执行所有繁重的工作。这反映在上面的基本流程图中。
  • Realms: Realms充当Shiro和应用程序安全数据之间的“桥梁”或“连接器”。当需要与用户账户等安全相关数据进行实际交互以执行认证(登录)和授权(访问控制)时,Shiro会从一个或多个应用配置的Realms中查找这些信息。
  • 从这个意义上说,Realm本质上是一个特定于安全的DAO:它封装了数据源的连接细节,并使Shiro可以根据需要使用相关的数据。在配置Shiro时,必须至少指定一个Realm用于身份验证和/或授权。SecurityManager可以配置多个realm,但至少需要一个。

核心架构

• Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外
API 核心就是 Subject。Subject 代表了当前“用户”,与 Subject 的所有交互都会委托给 SecurityManager;

• SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro的核心,
它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色

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

java集成shiro框架

  • Subject(org.apache.shiro.subject.Subject) 当前与软件交互的实体(用户、第三方服务、cron作业等)的安全特定的“视图”。
  • SecurityManager (org.apache.shiro.mgt.SecurityManager) 如上所述,SecurityManager是Shiro架构的核心。它主要是一个“保护伞”对象,协调其托管组件,以确保它们顺利地一起工作。它还管理Shiro对每个应用程序用户的视图,因此它知道如何对每个用户执行安全操作。
  • Authenticator (org.apache.shiro.authc.Authenticator) Authenticator是负责执行和响应用户的身份验证(登录)尝试的组件。当用户尝试登录时,该逻辑由Authenticator执行。Authenticator知道如何与一个或多个存储相关用户/账户信息的Realms进行协调。从这些领域获得的数据用于验证用户的身份,以确保用户的确 是他们所说的那个人。Authentication Strategy (org.apache.shiro.authc.pam.AuthenticationStrategy)如果配置了多个Realm,AuthenticationStrategy将协调Realm以确定认证尝试成功或失败的条件(例如,如果一个Realm成功但其他领域失败,尝试是否成功?所有的realms都必须成功吗?只有第一个?)
  • Authorizer (org.apache.shiro.authz.Authorizer) 授权者是负责确定用户在应用程序中的访问控制的组件。这是一种最终决定用户是否可以做某事的机制。与Authenticator一样,Authorizer也知道如何与多个后端数据源协调以访问角色和权限信息。授权方使用此信息来确定是否允许用户执行给定的操作
  • SessionManager (org.apache.shiro.session.mgt.SessionManager)SessionManager知道如何创建和管理用户会话生命周期,为所有环境中的用户提供健壮的会话体验。这是安全框架世界中的一个独特特性——Shiro能够在任何环境中本地管理用户会话,即使没有可用的Web/Servlet或EJB容器。默认情况下,Shiro将使用现有的会话机制(如Servlet容器),但如果没有,列如在独立应用程序或非web环境中,它将使用其内置的企业会话管理来提供一样的进程SessionDAO (org.apache.shiro.session.mgt.eis.SessionDAO) SessionDAO代表SessionManager执行会话持久性(CRUD)操作。这允许将任何数据存储插入到会话管理基础设施中。
  • CacheManager (org.apache.shiro.cache.CacheManager) CacheManager创建和管理其他Shiro组件使用的缓存实例生命周期。由于Shiro可以访问许多后端数据源来进行身份验证、授权和会话管理,缓存一直是框架中的一流架构特性,可以在使用这些数据源时提高性能。任何现代开源和/或企业缓存产品都可以插入到Shiro中,以提供快速高效的用户体验。
  • Cryptography (org.apache.shiro.crypto.*) Cryptography 是对企业安全框架的自然补充。Shiro的crypto包包含了易于使用和理解的密码学密码学的表明,哈希(又名摘要)和不同的编解码器实现。这个包中的所有类都经过精心设计,易于使用和理解。任何使用过Java本地密码学支持的人都知道,驯服它是一件很有挑战性的事情。Shiro的加密api简化了复杂的Java机制,使加密技术易于普通人类使用。Realms (org.apache.shiro.realm.Realm) 如上所述,Realms充当Shiro和应用程序安全数据之间的“桥梁”或“连接器”。当需要与用户账户等安全相关数据进行实际交互以执行认证(登录)和授权(访问控制)时,Shiro会从一个或多个应用配置的Realms中查找这些信息。你可以配置任意数量的Realms(一般每个数据源一个),Shiro会根据身份验证和授权的需要对它们进行协调。

身份验证流程

我们已经从架构一章中获得了前面的架构图,只突出显示了与身份验证相关的组件。每个数字表明身份验证尝试期间的一个步骤:

java集成shiro框架

  1. 应用程序代码调用Subject。登录方法,传入构造的代表最终用户主体和凭证的AuthenticationToken实例。
  2. Subject实例,一般是一个DelegatingSubject(或子类),通过调用SecurityManager.login(token)委托给应用程序的SecurityManager,这是实际身份验证工作开始的地方。
  3. 作为一个基本的“保护伞”组件,SecurityManager接收令牌,并通过调用Authenticator .authenticate(token)简单地将其委托给内部的Authenticator实例。这几乎总是一个ModularRealmAuthenticator实例,它支持在身份验证期间协调一个或多个Realm实例。ModularRealmAuthenticator本质上为Apache Shiro提供了一个PAM风格的范例(在PAM术语中,每个Realm都是一个“模块”)
  4. 如果为应用程序配置了多个Realm, ModularRealmAuthenticator实例将利用其配置的AuthenticationStrategy启动一个多领域身份验证尝试。在Realm被调用进行身份验证之前、期间和之后,AuthenticationStrategy将被调用,以允许它对每个Realm的结果做出反应。我们将很快讨论认证策略。
  5. 每个配置的Realm都会被咨询,看看它是否支持提交的AuthenticationToken。如果是这样,支持Realm的getAuthenticationInfo方法将被提交的令牌调用。getAuthenticationInfo方法有效地表明了针对特定领域的一次身份验证尝试。我们将很快讨论Realm身份验证行为。

权限注解

• @RequiresAuthentication:表明当前Subject已经通过login 进行了身份验证;即 Subject. isAuthenticated() 返回 true

• @RequiresUser:表明当前 Subject 已经身份验证或者通过记住我登录的。

•@RequiresGuest:表明当前Subject没有身份验证或通过记住我登录过,即是游客身份。

• @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表明当前 Subject 需要角色 admin 和user
• @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表明当前 Subject 需要权限 user:a 或
user:b。

过滤器拦截权限

类别

class

说明

anon

org.apache.shiro.web.filter.authc.AnonymousFilter

允许立即访问路径而不执行任何类型的安全检查的过滤器。

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

要求请求用户通过身份验证才能继续请求,如果没有,则通过将用户重定向到您配置的loginUrl强制用户登录。

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

需要对请求用户进行身份验证才能继续请求,如果没有,则需要用户通过HTTP Basic协议特定的挑战登录。成功登录后,允许他们继续访问请求的资源/url。

authcBearer

org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter

要求请求用户通过身份验证才能继续请求,如果没有,则要求用户通过特定于HTTP承载协议的挑战登录。成功登录后,允许他们继续访问请求的资源/url。

invalidRequest

org.apache.shiro.web.filter.InvalidRequestFilter

拦截恶意请求的请求过滤器。无效的请求将以400响应代码进行响应。

logout

org.apache.shiro.web.filter.authc.LogoutFilter

简单的过滤器,当接收到请求时,将立即注销当前执行的主题,然后将它们重定向到一个配置的redirectUrl。

noSessionCreation

org.apache.shiro.web.filter.session.NoSessionCreationFilter

一个PathMatchingFilter,它将禁用在请求期间创建新的会话。这是一个超级有用的过滤器,可以放在任何过滤器链的前面,这些过滤器链可能导致REST、SOAP或其他不打算参与会话的服务调用。

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

筛选器,如果当前用户具有映射值指定的权限,则允许访问;如果用户没有指定的所有权限,则拒绝访问。

port

org.apache.shiro.web.filter.authz.PortFilter

要求请求在特定端口上的过滤器,如果不是,则重定向到该端口上的一样URL。

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

过滤器将HTTP请求的方法(如GET、POST等)转换为相应的动作(动词),并使用该动词构造一个将被检查以确定访问的权限。

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

如果当前用户具有映射值指定的角色,则允许访问;如果用户没有指定的所有角色,则拒绝访问。

ssl

org.apache.shiro.web.filter.authz.SslFilter

要求请求通过SSL的过滤器。如果通过配置的服务器端口和request. issecure()接收到请求,则允许访问。如果任何一个条件为假,则过滤器链将不再继续。

user

org.apache.shiro.web.filter.authc.UserFilter

如果访问器是已知用户(定义为具有已知主体),则允许访问资源的筛选器。这意味着任何通过“记住我”功能被验证或记住的用户将被允许从这个过滤器访问。

SpringBoot整合Shiro

导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.6.1</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.9.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.9</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
    <optional>true</optional>
</dependency>

自定义Realm

package com.dw.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: daWang
 * @Date: 2022/4/22 16:24
 */
@Slf4j
public class UserRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("-----授权------");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //这个对象对应 SimpleAuthenticationInfo 中的
        Object primaryPrincipal = principals.getPrimaryPrincipal();
        log.info("授权对象内容: {}",primaryPrincipal);

        //查询数据库,把用户的角色和权限查询出来
        //用户表users  users_role 用户角色表 roles表  menu表  roles_menu表
        //查询 users_role 和 roles表 查询用户的角色

        //然后根据角色编号查询menu表 和 roles_menu表  权限查询出来

        //角色
        List<String> list = new ArrayList<String>();
        //权限
        List<String> perms = new ArrayList<String>();
        perms.add("sys:user:*");//
        perms.add("sys:menu:select");//
        perms.add("sys:role:select");//
        simpleAuthorizationInfo.addStringPermissions(perms);//Collection<String>
        simpleAuthorizationInfo.addRoles(list);//Collection<String>

        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("-----认证------");
//        Subject subject = SecurityUtils.getSubject();
//        subject.login(token);
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        //先得到用户输入的用户名和密码
        String username  = token.getUsername();
        //public char[] getPassword() {}
        String password = new String(token.getPassword());

        //从数据库查询
        //假数据模拟
        String u = "admin";
        String p = "df655ad8d3229f3269fad2a8bab59b6c";
        int status  = 1;//正常  0  冻结

        if (!username.equals(u)){
            throw  new UnknownAccountException("账户不存在!");
        }

        if (!password.equals(p)){
            throw  new IncorrectCredentialsException("密码错误!");

        }

        if (status==0){
            throw  new LockedAccountException("账户被冻结!");
        }

        //public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {
        /**
         * 参数一:一般是用户对象
         * 参数二:密码
         * 参数三:自定义realm名称
         */
        SimpleAuthenticationInfo   info = new SimpleAuthenticationInfo(username,password,this.getName());

        return info;
    }
}

配置类

package com.dw.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.spring.config.ShiroAnnotationProcessorConfiguration;
import org.apache.shiro.spring.config.ShiroBeanConfiguration;
import org.apache.shiro.spring.config.ShiroConfiguration;
import org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroRequestMappingConfig;
import org.apache.shiro.spring.web.config.ShiroWebConfiguration;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Import;
import sun.security.krb5.RealmException;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;


/**
 * @Author: daWang
 * @Date: 2022/4/22 13:45
 */
@Configuration
public class ShiroConfig {


    // 配置自定义Realm
    @Bean
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        //userRealm.setCredentialsMatcher(credentialsMatcher()); //配置使用哈希密码匹配
        return userRealm;
    }
    
    //权限管理器
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager  getSecurityManager(UserRealm userRealm){
        DefaultWebSecurityManager manager =new DefaultWebSecurityManager();
        manager.setRealm(userRealm);
        return manager;
    }

    //创建过滤器工厂
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager, ShiroFilterChainDefinition filterChainDefinition){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/not/login");
        bean.setFilterChainDefinitionMap(filterChainDefinition.getFilterChainMap());
        return bean;
    }


    // 配置url过滤器
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        chainDefinition.addPathDefinition("/logout","anon");
        chainDefinition.addPathDefinition("/not/login","anon");
        // all other paths require a logged in user
        chainDefinition.addPathDefinition("/login","anon");
        chainDefinition.addPathDefinition("/**", "authc");
        return chainDefinition;
    }


}

Controller

package com.dw.controller;

import com.fasterxml.jackson.databind.util.JSONPObject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: daWang
 * @Date: 2022/4/24 9:21
 */
@Controller
public class UserController {

    @RequiresRoles(value = {"admin"},logical = Logical.OR)
    @GetMapping("/query")
    @ResponseBody
    public Map query(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("code",200);
        map.put("msg","详细");
        return map;
    }

    @GetMapping("/not/login")
    @ResponseBody
    public Map notLogin(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("code",201);
        map.put("msg","您还未登陆");
        return map;
    }




    @GetMapping("/login")
    @ResponseBody
    public Map login(String userName, String passWord ) {
        //shiro认证
        UsernamePasswordToken token = new UsernamePasswordToken(userName, passWord);

        //得到Subject对象
        Subject subject = SecurityUtils.getSubject();
        HashMap<String, Object> map = new HashMap<>();
        try {
            subject.login(token);
            map.put("code", 200);
            map.put("msg", "login success");
            return map;
        } catch (UnknownAccountException e) {
            map.put("code", 201);
            map.put("msg", e.getMessage());
            return map;
        } catch (IncorrectCredentialsException e) {
            map.put("code", 201);
            map.put("msg", e.getMessage());
            return map;
        } catch (LockedAccountException e) {
            map.put("code", 201);
            map.put("msg", e.getMessage());
            return map;
        }
    }
}

ControllerAdvice

package com.dw.controller;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: daWang
 * @Date: 2022/4/25 11:19
 */
@Slf4j
@ResponseBody
@ControllerAdvice
public class ExceptionController {

    private Map createMap(){
        return new HashMap();
    }

    @ExceptionHandler(Exception.class)
    public Map handler(Exception e){
        log.error("{}",e.getMessage());
        Map map = createMap();
        map.put("code",202);
        map.put("msg","操作错误");
        return map;
    }

    @ExceptionHandler(AuthorizationException.class)
    public Map handler(AuthorizationException e){
        log.error("{}",e.getMessage());
        Map map = createMap();
        map.put("code",201);
        map.put("msg","您没有操作权限");
        return map;
    }
}
© 版权声明

相关文章

暂无评论

none
暂无评论...