Apache Shiro(三)——Spring Boot 与 Shiro的 整合

2023-11-05

在这里插入图片描述
在了解了Apache Shiro的架构、认证、授权之后,我们来看一下Shiro与Web的整合。下面以Spring Boot为例,介绍一下Spring Boot 与 Shiro的 整合。

一、创建一个Spring Boot项目

可以使用IDEA快速创建一个Spring Boot项目,这里就不多赘述。

二、在pom文件中引入 maven依赖

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <shiro.version>1.4.0</shiro.version>
    </properties>


	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

		<!--shiro依赖-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
            <version>${shiro.version}</version>
			<exclusions>
				<exclusion>
					<artifactId>slf4j-api</artifactId>
					<groupId>org.slf4j</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
			<exclusions>
				<exclusion>
					<artifactId>slf4j-api</artifactId>
					<groupId>org.slf4j</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>

	</dependencies>

三、配置shiro

在Spring Boot项目中shiro的配置文件如下:

package com.cczhang.shiroweb.config;


import com.cczhang.shiroweb.shiro.realm.ShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpSession;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * shiro权限管理的配置
 */
@Configuration
public class ShiroConfig {


    /**
     * session 失效时间(默认为30分钟 单位:秒)
     */
    private Integer sessionInvalidateTime = 30 * 60;

    /**
     * session 验证失效时间(默认为15分钟 单位:秒)
     */
    private Integer sessionValidationInterval = 15 * 60;

    /**
     * 安全管理器securityManager
     */
    @Bean
    public DefaultWebSecurityManager securityManager(CookieRememberMeManager rememberMeManager,
                                                     CacheManager cacheShiroManager,
                                                     SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(this.shiroRealm());
        securityManager.setCacheManager(cacheShiroManager);
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }


    @Bean
    public ShiroFilterFactoryBean shirFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//      必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager((org.apache.shiro.mgt.SecurityManager) securityManager);
//      setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
//        shiroFilterFactoryBean.setLoginUrl("/login");
        // 设置无权限时跳转的 url;
        shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
        //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
        shiroFilterFactoryBean.setLoginUrl("/unauth");

//        /**
//         * 默认的登陆访问url
//         */
//        shiroFilter.setLoginUrl("/login");
//        /**
//         * 登陆成功后跳转的url
//         */
//        shiroFilter.setSuccessUrl("/");
//        /**
//         * 没有权限跳转的url
//         */
//        shiroFilter.setUnauthorizedUrl("/global/error");
//



        // 设置拦截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //游客,开发权限
        filterChainDefinitionMap.put("/guest/**", "anon");
        //用户,需要角色权限 “user”
        filterChainDefinitionMap.put("/user/**", "roles[user]");
        //管理员,需要角色权限 “admin”
        filterChainDefinitionMap.put("/admin/**", "roles[admin]");
        //开放登陆接口
        filterChainDefinitionMap.put("/login", "anon");
        //其余接口一律拦截
        //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        System.out.println("Shiro拦截器工厂类注入成功");
        return shiroFilterFactoryBean;
    }


    /**
     * 项目自定义的Realm
     */
    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm =  new ShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return shiroRealm;
    }

    /**
     * session管理器
     */
    @Bean
    public DefaultWebSessionManager defaultWebSessionManager(CacheManager cacheShiroManager) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setCacheManager(cacheShiroManager);
        sessionManager.setSessionValidationInterval(sessionValidationInterval * 1000);
        sessionManager.setGlobalSessionTimeout(sessionInvalidateTime * 1000);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
        cookie.setName("shiroCookie");
        cookie.setHttpOnly(true);
        sessionManager.setSessionIdCookie(cookie);
        return sessionManager;
    }


    /**
     * 缓存管理器 使用Ehcache实现
     */
    @Bean
    public CacheManager getCacheShiroManager(EhCacheManagerFactoryBean ehcache) {
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManager(ehcache.getObject());
        return ehCacheManager;
    }


    /**
     * rememberMe管理器, cipherKey生成见{@code Base64Test.java}
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
        CookieRememberMeManager manager = new CookieRememberMeManager();
        manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA=="));
        manager.setCookie(rememberMeCookie);
        return manager;
    }


    /**
     * 记住密码Cookie
     */
    @Bean
    public SimpleCookie rememberMeCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天
        return simpleCookie;
    }



    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(1024);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }


    /**
     * 在方法中 注入 securityManager,进行代理控制
     */
    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
        MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
        bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        bean.setArguments(new Object[]{securityManager});
        return bean;
    }

    /**
     * Shiro生命周期处理器:
     * 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
     * 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 启用shrio授权注解拦截方式,AOP式方法级权限检查
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
                new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}

默认拦截器

Shiro 内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:

public enum DefaultFilter {

    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);

在这里插入图片描述
在这里插入图片描述

四、编写Shiro Realm

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

自定义Realm一般继承 AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

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

public class ShiroRealm extends AuthorizingRealm {

	//认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {

		//1. 把 AuthenticationToken 转换为 UsernamePasswordToken
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;

		//2. 从 UsernamePasswordToken 中来获取 username
		String username = upToken.getUsername();

		//3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
		System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息.");

		//4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
		if("unknown".equals(username)){
			throw new UnknownAccountException("用户不存在!");
		}

		//5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常.
		if("monster".equals(username)){
			throw new LockedAccountException("用户被锁定");
		}

		//6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
		//以下信息是从数据库中获取的.
		//1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象.
		Object principal = username;
		//2). credentials: 密码.
		Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
		if("admin".equals(username)){
			System.out.println("admin");
			credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
		}else if("user".equals(username)){
			System.out.println("user");
			credentials = "098d2c478e9c11555ce2823231e02ec1";
		}

		//3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
		String realmName = getName();
		//4). 盐值.
		ByteSource credentialsSalt = ByteSource.Util.bytes(username);

		//new SimpleAuthenticationInfo(principal, credentials, realmName);
		SimpleAuthenticationInfo info = null;
		info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		return info;
	}

	//授权会被 shiro 回调的方法
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		//1. 从 PrincipalCollection 中来获取登录用户的信息
		Object principal = principals.getPrimaryPrincipal();

		//2. 利用登录的用户的信息来用户当前用户的角色或权限(可能需要查询数据库)
		Set<String> roles = new HashSet<>();
		roles.add("user");
		if("admin".equals(principal)){
			roles.add("admin");
		}
		//3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性.
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);

		//4. 返回 SimpleAuthorizationInfo 对象.
		return info;
	}


	public static void main(String[] args) {
		String hashAlgorithmName = "MD5";
		Object credentials = "123456";
		Object salt = ByteSource.Util.bytes("admin");
		int hashIterations = 1024;

		Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
		System.out.println(result);
	}
}

Authenticator

Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验证核心的入口点:如果验证成功,将返回AuthenticationInfo 验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException 异常。

SecurityManager 接口继承了 Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm 进行验证,验证规则通过 AuthenticationStrategy 接口指定

AuthenticationStrategy(验证策略 )

FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略。
AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息。
AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy策略。

ModularRealmAuthorizer

ModularRealmAuthorizer 进行多 Realm 匹配流程:
1、首先检查相应的 Realm 是否实现了实现了Authorizer;
2、如果实现了 Authorizer,那么接着调用其相应的isPermitted*/hasRole* 接口进行匹配;
3、如果有一个Realm匹配那么将返回 true,否则返回 false。

五、编写LoginController验证程序

@RestController
@RequestMapping("/login")
public class LoginController {

    @GetMapping("")
    public Map login(@RequestParam("account")  String account,
                     @RequestParam("password") String password){
        Map<String, Object> map = new HashMap<>();
        Subject subject = SecurityUtils.getSubject();
        System.out.println(account+"--"+password);
        UsernamePasswordToken token = new UsernamePasswordToken(account, password);
        if (!subject.isAuthenticated()){
            try {
                subject.login(token);
                if(subject.hasRole("admin")){
                    map.put("role", "admin");
                }
                else {
                    map.put("role", "user");
                }
                map.put("token", subject.getSession().getId());
                map.put("msg", "登录成功");
            } catch (IncorrectCredentialsException e) {
                map.put("msg", "密码错误");
            } catch (LockedAccountException e) {
                map.put("msg", "登录失败,该用户已被冻结");
            } catch (AuthenticationException e) {
                map.put("msg", "该用户不存在");
            } catch (Exception e) {
                map.put("msg", "未知错误");
                e.printStackTrace();
            }
        }
        return map;

    }
    @GetMapping(value = "/unauth")
    public Object unauth() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", "0");
        map.put("msg", "未登录");
        return map;
    }


    @GetMapping(value = "/notRole")
    public Object notRole() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", "110");
        map.put("msg", "notRole");
        return map;
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Apache Shiro(三)——Spring Boot 与 Shiro的 整合 的相关文章

  • A*寻路算法 lua

    function InitMap self AMap for i 1 10 do self AMap i for j 1 10 do local map map x i map y j map g 999 map h 0 map f 0 m

随机推荐

  • 如何运行一个CFDEM tutorial case

    新手入门 万里长征第一步 安装完CFDEM后 我们知道有很多tutorial可以教我们怎么设置合理的参数 很多tutorial case里都有写好的Allrun脚本 最简单的方法就是运行Allrun脚本走一遍流程 准备工作 复制随便一个tu
  • 当里个当,免费的HTML5连载来了《HTML5网页开发实例详解》连载(一)

    读懂 HTML5网页开发实例详解 这本书 你还在用Flash嘛 帮主早不用了 乔布斯生前在公开信 Flash之我见 中预言 像HTML 5这样在移动时代中创立的新标准 将会在移动设备上获得胜利 国际巨头Google 苹果等都支持HTML 5
  • IDEA写爬虫,配jsoup

    环境变量配了 jdk的lib目录下也加了jsoup的jar包 返回IDEA还是没有 点击左上角的File进Project Setting SDKs 选择自己的jdk文件 在右侧点击 把jar包导进来
  • K8S 安装步骤

    K8S 安装步骤 一 为每台机器安装docker 1 安装docker 1 1 卸载旧版本 sudo yum remove docker docker client docker client latest docker common do
  • 【游戏开发】[用代码创建unity5.X的动画状态机]

    不废话 上代码 可能有的人 会质疑 这不是editor的功能吗 是的 但是这个允许在运行时候使用 你们跟一下代码看看继承于哪里就知道了 using UnityEngine using System Collections using Sys
  • JAVA-时间日期格式转换

    第一种方式 获取当前时间然后按照指定格式转换成String类型 DateFormat dateFormat new SimpleDateFormat yyyy MM dd HH mm ss Calendar cal Calendar get
  • 【Shell牛客刷题系列】SHELL31 netstat练习3-输出每个IP的连接数

    该系列是基于牛客Shell题库 针对具体题目进行查漏补缺 学习相应的命令 刷题链接 牛客题霸 Shell篇 该系列文章都放到专栏下 专栏链接为 专栏 Shell 欢迎关注专栏 本文知识预告 本文复习了awk grep sort uniq命令
  • DEiT实战:使用DEiT实现图像分类任务(二)

    文章目录 训练 导入项目使用的库 设置随机因子 设置全局参数 图像预处理与增强 读取数据 设置模型 train py train dist py 定义训练和验证函数 训练函数 验证函数 调用训练和验证方法 运行以及结果查看 测试 完整的代码
  • win10 git bash 设置别名

    使用git挺久时间 每次输入需要敲入长命令已感费劲 zshell git 有不少别名 可以提高输入效率 从而寻找windows下设置 git bash 别名的方法 环境 git v2 22 0 win10 方法1 通过 profile 文件
  • 使用gtest做单元测试

    使用gtest做单元测试 文章目录 使用gtest做单元测试 1 用gtest写测试工程的大致流程 配置gtest头文件及库 gtest的相关概念 TEST与TEST F 断言 事件机制 参考 gtest是一个跨平台的 Liunx Mac
  • Pycharm 常用快捷键

    常用快捷键 快捷键 功能 Ctrl Q 快速查看文档 Ctrl F1 显示错误描述或警告信息 Ctrl 行注释 可选中多行 Ctrl Alt L 代码格式化 Ctrl Alt O 自动导入 Ctrl Alt I 自动缩进 Tab Shift
  • 【网络】多路转接——poll

    作者 一只大喵咪1201 专栏 网络 格言 你只管努力 剩下的交给时间 书接上文五种IO模型 select poll epoll poll 认识接口 简易poll服务器 poll的特点 epoll 认识接口 epoll原理 简易epoll服
  • CentOS7 安装Redis6过程详解

    CentOS7 安装Redis6过程详解 一 安装redis 1 下载Redis 2 解压 3 使用make编译 4 编译完成后在目录中执行make install安装redis服务 5 安装完成 二 设置redis后台运行以及远程连接 1
  • 华为OD机试 - 找车位(Java)

    题目描述 停车场有一横排车位 0代表没有停车 1代表有车 至少停了一辆车在车位上 也至少有一个空位没有停车 为了防剐蹭 需为停车人找到一个车位 使得距停车人的车最近的车辆的距离是最大的 返回此时的最大距离 输入描述 一个用半角逗号分割的停车
  • Zookeeper 基本数据模型

    介绍 ZooKeeper是一个树形结构 类似于前端开发中的tree js组件 ZooKeeper的数据模型也可以理解为linux unix的文件目录 usr local 每个节点都称为znode 它可以有子节点 也可以有数据 每个节点分为临
  • 正则表达式-分组与后向引用

    前文中 已经总结了正则表达式中的常用字符 次数匹配 位置匹配等 这篇文章中 我们来了解一下正则中的 分组 与 后向引用 什么是分组 什么是后向引用 我们慢慢聊 先来说说什么是分组 算了 思考了半天 我也不知道从何说起 先看个示例吧 根据示例
  • 对NetBackup 问题进行故障排除的步骤

    错误消息通常是指出哪里出现故障的手段 如果在界面上没有看到错误消息 但仍怀疑有问题 请检查报告和日志 NetBackup提供了各种报告和日志记录工具 这些工具可提供错误消息 直接为您指出解决方案 日志还可显示什么运行良好以及当发生问题时Ne
  • html设置 元素最小宽度,css如何让元素宽度自适应屏幕大小

    如今 手机的快速发展使得越来越多的人开始使用手机上网 那么就会出现一个问题 如何才能让PC端的网页在手机上正常显示 让元素能够自动适应不同的屏幕大小呢 css如何让元素宽度自适应屏幕大小 1 在网页代码的头部 加入一行viewport元标签
  • Unity中SLua、Tolua、XLua和ILRuntime效率评测

    Unity脚本效率评测 对SLua Tolua XLua和ILRuntime四个脚本插件进行效率测试 对框架脚本进行选型 本文项目 https github com cateatcatx UnityScriptPTest tolua htt
  • Apache Shiro(三)——Spring Boot 与 Shiro的 整合

    在了解了Apache Shiro的架构 认证 授权之后 我们来看一下Shiro与Web的整合 下面以Spring Boot为例 介绍一下Spring Boot 与 Shiro的 整合 一 创建一个Spring Boot项目 可以使用IDEA