apache shiro 如何升级_浅玩springboot整合shiro框架

本文介绍如何在 Spring Boot 中集成 Apache Shiro 安全框架,实现用户认证和授权功能。涵盖 Shiro 的核心组件介绍、配置步骤、自定义 Realm 以及会话管理等关键知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

14183160ab8590bc396dc32a7837c5c5.png点击蓝字关注我们吧!
什么是shiro

Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

基本功能点

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

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

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

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

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

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

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

Testing:提供测试支持;

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

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

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

接下来我们分别从外部和内部来看看 Shiro 的架构,对于一个好的框架,从外部来看应该具有非常简单易于使用的 API,且 API 契约明确;从内部来看的话,其应该有一个可扩展的架构,即非常容易插入用户自定义实现,因为任何框架都不能满足所有需求。

首先,我们从外部来看 Shiro 吧,即从应用程序角度的来观察如何使用 Shiro 完成工作。如下图:

1aa223affdbdd865f6404ca30c0703f9.png

可以看到:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject;其每个 API 的含义:

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

SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

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

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

  1. 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
  2. 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

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

看懂了吗

其实上面的概念我们大概知道就行了,没必要死磕到底,如果还是想了解多一点概念可以点击我。其实对于新手来说,更重要的是能自己实践出来即可。最近玩的项目用到了shiro框架身份认证和权限,所以总结了一下springboot整合shiro框架的应用。

代码实现
导入依赖
<?xml  version="1.0" encoding="UTF-8"?>
"http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">4.0.0org.springframework.bootspring-boot-starter-parent2.3.1.RELEASE com.aodemo0.0.1-SNAPSHOTdemoDemo project for Spring Boot1.8org.springframework.bootspring-boot-starterorg.springframework.bootspring-boot-starter-testtestorg.junit.vintagejunit-vintage-engineorg.apache.shiroshiro-spring-boot-web-starter1.4.0mysqlmysql-connector-java8.0.18com.alibabadruid-spring-boot-starter1.1.21com.baomidoumybatis-plus-boot-starter3.3.0org.projectlomboklombok1.18.8org.springframework.bootspring-boot-maven-plugin

shiro配置

ShiroConfig

package com.ao.demo.config;

import com.ao.demo.shiro.AdminAuthorizingRealm;
import com.ao.demo.shiro.AdminWebSessionManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {


    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map filterChainDefinitionMap = new LinkedHashMap();/*anon: 无需认证即可访问
  authc: 需要认证才可访问
  user: 点击“记住我”功能可访问
  perms: 拥有权限才可以访问
  role: 拥有某个角色权限才能访问*/
        filterChainDefinitionMap.put("/admin/auth/login", "anon");
        filterChainDefinitionMap.put("/admin/**", "authc");/*没有登录的用户请求需要登录的页面时自动跳转到登录页面。*/
        shiroFilterFactoryBean.setLoginUrl("/admin/auth/401");/*登录成功默认跳转页面,不配置则跳转至”/”,可以不配置,直接通过代码进行处理。*/
        shiroFilterFactoryBean.setSuccessUrl("/admin/auth/success");/*没有权限默认跳转的页面,登录的用户访问了没有被授权的资源自动跳转到的页面*/
        shiroFilterFactoryBean.setUnauthorizedUrl("/admin/auth/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;
    }//将自己的验证方式加入容器@Beanpublic Realm realm() {return new AdminAuthorizingRealm();
    }//管理会话@Beanpublic SessionManager sessionManager() {return new AdminWebSessionManager();
    }//权限管理,配置主要是Realm的管理认证@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        securityManager.setSessionManager(sessionManager());return securityManager;
    }
}
配置自己的验证方式

AdminAuthorizingRealm

package com.ao.demo.shiro;


import com.ao.demo.pojo.Admin;
import com.ao.demo.service.IAdminService;
import com.ao.demo.service.IPermissionService;
import com.ao.demo.service.IRoleService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
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;
import org.springframework.util.Assert;

import java.util.List;
import java.util.Set;

@Slf4j
public class AdminAuthorizingRealm extends AuthorizingRealm {
    /*当调用判断权限的方法, 才会触发 doGetAuthorizationInfo() 方法
 subject.hasRole();
 subject.checkPermission();
 subject.isPermitted();
 ....
*/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("----------doGetAuthorizationInfo方法被调用----------");
        if (principals == null) {
            throw new AuthorizationException("");
        }
        Admin admin = (Admin) getAvailablePrincipal(principals);
        String[] roleIds = admin.getRoleIds();
        Set roles =  new HashSet<>();
        roles.add("role1");
        Set permissions = new HashSet<>();
        permissions.add("order:list");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roles);
        info.setStringPermissions(permissions);return info;
    }//    3.在认证方法中的doGetAuthenticationInfo@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token; 
        String username = upToken.getUsername();  //获取到用户名
        String password = new String(upToken.getPassword());  //获取到密码//        查询数据库是否有这个用户,此处是mybatisplus的写法
        List adminList = adminService.list(new QueryWrapper().lambda().eq(Admin::getUsername, username));
        Assert.state(adminList.size() 2, "同一个用户名存在两个账户");if (adminList.size() == 0) {throw new UnknownAccountException("找不到用户(" + username + ")的帐号信息");
        }
        Admin admin = adminList.get(0);if (admin.getState() == 1){throw new LockedAccountException("帐号待审核");
        }if (admin.getState() == 2){throw new DisabledAccountException("帐号已禁用");
        }if (admin.getState() == 3){throw new ExcessiveAttemptsException("帐号已锁定");
        }/*.......一些业务逻辑*///这里如果上面的都成立后会进行密码校验,第二个参数是用户数据库的密码return new SimpleAuthenticationInfo(admin, admin.getPassword(), getName());
    }
}
会话管理

AdminWebSessionManager

package com.ao.demo.shiro;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

public class AdminWebSessionManager extends DefaultWebSessionManager {

    public static final String LOGIN_TOKEN_KEY = "Shiro-Token";
    private static final String REFERENCED_SESSION_ID_SOURCE = "shiro request";

    /**
     * 获取session id
     * 从请求头中获取jsesssionid
     */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        // 从请求头中获取token
        String id = WebUtils.toHttp(request).getHeader(LOGIN_TOKEN_KEY);
        // 判断是否有值
        if (!StringUtils.isEmpty(id)) {
            // 设置当前session状态
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            // 若header获取不到token则尝试从cookie中获取
            return super.getSessionId(request, response);
        }
    }
}

controller

AdminAuthController

package com.ao.demo.controller;


import com.ao.demo.pojo.Admin;
import com.ao.demo.utils.JacksonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/admin/auth")
@Slf4j
public class AdminAuthController {

    @PostMapping("/login")
    public Object login(@RequestBody Admin loginAdmin) {
       // 1.SecurityUtils:是shiro的一个工具类。通过SecurityUtils获取Subject
        Subject currentUser = SecurityUtils.getSubject();
        try {
            /*UsernamePasswordToken是一个简单的包含username及password即用户名及密码的登录验证用token*/
            //2、new一个 UsernamePasswordToken,并传上用户名及密码。把返回值传给登入作为条件
            //3、当调用subject的登入方法时,会跳转到reaml认证的方法(doGetAuthenticationInfo)上。
         currentUser.login(new UsernamePasswordToken(loginAdmin.getUsername(), loginAdmin.getPassword()));
        } catch (UnknownAccountException uae) {
            return uae.getMessage();
        } catch (LockedAccountException lae) {
            return lae.getMessage();
        } catch (DisabledAccountException dae) {
            return dae.getMessage();
        }catch (AuthenticationException ae) {
            return "认证失败";
        }
   //是否有role1这个角色
        if(currentUser.hasRole("role1")){
            log.info("有角色role1");
        }else{
            log.info("没有角色role1");
        }
        //查看是否有查看订单的权限
        if(currentUser.isPermitted("order:list")){
            log.info("拥有查看订单的权限");
        }else {
            log.info("没有查看订单的权限");
        }
        //4、在认证方法中subject已经把获取到了用户,所以我们用subject.getPrincipal 可以获取到登录的用户
        Admin admin = (Admin) SecurityUtils.getSubject().getPrincipal();
        return "登录成功,用户信息:"+admin+"token:" + currentUser.getSession().getId();
    }


    @PostMapping("/tt")
    public String tt(){
        return "访问成功";
    }

}

测试
表的数据
979745913097a30f12dba70027977779.png
测试一波

可以看到doGetAuthorizationInfo被执行了两次,这是因为我在controller调用了hasRole和isPermitted。

1a4779c18a4b186bfd8438fa7eccd014.png
登录成功,返回了token(网上说这个token的过期时间是30分钟,具体需要查证源码)
21544ab415bd919bac348c72e96a8b22.png
登录失败,执行到new SimpleAuthenticationInfo(admin, admin.getPassword(), getName())这里,密码错误抛出AuthenticationException,controller捕获到所以返回结果认证失败。
e106c3fde8a7a1354457f0eba153b227.png
再测试一下会话管理

登录成功了,此时的token是:81973a5e-6e4c-4d7a-a6a6-7b212eea2b97

然后我们验证一下会话管理起不起作用

6591c4d8ebb1532a4ce78d71ea8c0fef.png

根据返回来的报文,没有登录的用户请求需要登录的页面时自动跳转到登录页面,这就是我们在shiroconfig设置的setLoginUrl,因为这个接口没写,所以404。接下来拿到登录用户的token,进行访问,结果是预期的。

2499f4088fd73dd7708056024bade4a2.png

   至此,springboot搭建shiro框架就成功啦!

ed02cec40152cd3ad35fcfe5b78efb1b.png748cc8a9bc5cac62f17dbd9121fa3ce6.png1ecf36f55ddd41cca632f6e7fb731376.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值