当前位置: 首页 > news >正文

济源制作网站自由人网站开发

济源制作网站,自由人网站开发,全国失信被执行人名单查询,asp 企业网站安全框架#xff1a;Apache Shiro 前言您的第一个 Apache Shiro 应用程序Multiple Parts#xff08;多个部分#xff09;INI配置[main]部分[users]部分[roles]部分[urls]部分默认过滤器常规启用/禁用 密码学会话管理Remember Me 整合SpringBoot登录登录超时记住我注解登录后… 安全框架Apache Shiro 前言您的第一个 Apache Shiro 应用程序Multiple Parts多个部分INI配置[main]部分[users]部分[roles]部分[urls]部分默认过滤器常规启用/禁用 密码学会话管理Remember Me 整合SpringBoot登录登录超时记住我注解登录后跳回之前页面自定义缓存自定义SessionDao自定义过滤器登录退出单用户登录 问题排查解决SpringBoot整合Shiro报Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken...解决Springboot整合Shiro自定义SessionDAORedis管理会话登录后不跳转首页解决Springboot整合ShiroRedis退出登录后不清除缓存 项目示例 前言 Apache Shiro™ 是一个功能强大且易于使用的 Java 安全框架可执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API您可以快速轻松地保护任何应用程序 - 从最小的移动应用程序到最大的Web和企业应用程序。 下图显示了 Shiro 的精力集中在哪里 Authentication身份验证有时称为“登录”这是证明用户是他们所声称的身份的行为。Authorization授权访问控制的过程即确定“谁”可以访问“什么”。Session Management会话管理管理特定于用户的会话即使在非 Web 或 EJB 应用程序中也是如此。Cryptography加密使用加密算法确保数据安全同时仍然易于使用。 此外还有一些其他功能可以在不同的应用程序环境中支持和加强这些问题特别是 Web 支持Shiro的 Web 支持 API 有助于轻松保护 Web 应用程序。缓存缓存是Apache Shiro的API中的第一层公民用于确保安全操作保持快速和高效。并发Apache Shiro 通过其并发功能支持多线程应用程序。测试测试支持可帮助您编写单元测试和集成测试并确保您的代码按预期得到保护。“运行方式”允许用户代入其他用户的身份如果允许的功能有时在管理方案中很有用。“Remember Me记住我”记住用户跨会话的身份因此他们只需要在必须登录时登录。 Shiro 试图在所有应用程序环境中实现这些目标 - 从最简单的命令行应用程序到最大的企业应用程序而无需强制依赖其他第三方框架、容器或应用程序服务器。当然该项目的目标是尽可能地集成到这些环境中但它可以在任何环境中开箱即用。 您的第一个 Apache Shiro 应用程序 我们创建一个Maven项目引入依赖配置示例代码如下最新版2.0.2需要JDK11 dependencygroupIdorg.apache.shiro/groupIdartifactIdshiro-core/artifactIdversion1.13.0/version/dependencySpringBoot依赖配置如下 dependencygroupIdorg.apache.shiro/groupIdartifactIdshiro-spring-boot-web-starter/artifactIdversion1.13.0/version/dependency要在应用程序中启用Shiro首先要了解的是Shiro中的几乎所有内容都与称为SecurityManager的中心/核心组件相关。SecurityManager是应用程序的Shiro环境的核心 虽然我们可以直接实例化SecurityManager类但Shiro的SecurityManager实现有足够的配置选项和内部组件这使得在Java源代码中这样做很痛苦——使用灵活的基于文本的配置格式来配置SecurityManager会容易得多。 为此Shiro通过基于文本的INI配置提供了默认的“公分母”解决方案。如今人们已经厌倦了使用庞大的XML文件而INI易于阅读、使用简单并且只需要很少的依赖项。INI可以有效地用于配置像SecurityManager这样的简单对象图。 在src/main/resources目录下创建一个shiro.ini文件内容如下 # ----------------------------------------------------------------------------- # 用户和他们可选分配的角色 # username password, role1, role2, ..., roleN # ----------------------------------------------------------------------------- [users] root admin, admin guest guest, guest# ----------------------------------------------------------------------------- # 已分配权限的角色 # roleName perm1, perm2, ..., permN # ----------------------------------------------------------------------------- [roles] admin * guest user:query此配置基本上设置了一小部分静态用户帐户对于我们的第一个应用程序来说已经足够了。 然后创建工厂获取实例进行登录操作示例代码如下 public class Test {public static void main(String[] args) {// 初始化Security工厂IniSecurityManagerFactory iniSecurityManagerFactory new IniSecurityManagerFactory(classpath:shiro.ini);// 解析INI文件获取认证管理器实例SecurityManager securityManager iniSecurityManagerFactory.getInstance();// 将认证管理器放入SecurityUtilsSecurityUtils.setSecurityManager(securityManager);// 使用实例它只是表示“当前正在与软件交互的事物”Subject subject SecurityUtils.getSubject();// 身份验证UsernamePasswordToken usernamePasswordToken new UsernamePasswordToken(guest, 12345);try {subject.login(usernamePasswordToken);} catch (UnknownAccountException uae) {// 用户名不在系统中给他们显示错误信息System.out.println(用户不存在);} catch (IncorrectCredentialsException ice) {// 密码不匹配再试一次System.out.println(密码错误);} catch (LockedAccountException lae) {// 该用户名的/帐户被锁定-无法登录。System.out.println(账户被锁定);} catch (AuthenticationException ae) {//意外情况-错误System.out.println(意外错误);}} }不同的catch捕获的异常不同上述示例的guest的密码错误会打印对应日志。 当我们登录成功后我们可以对当前账户进行一些校验操作比如获取当前登录的信息示例代码如下 Object principal subject.getPrincipal(); /** Output: * root*/还可以测试它们是否具有特定的角色 boolean hasRole subject.hasRole(president); System.out.println(hasRole); /** Output:* false*/我们还可以查看他们是否有权对某种类型的实体执行操作 boolean permitted subject.isPermitted(user:query); System.out.println(permitted); /** Output:* true*/最后当用户使用完应用程序后他们可以注销 subject.logout();Multiple Parts多个部分 通配符权限支持多个级别或部分的概念。例如您可以通过授予用户权限来重新构建前面的简单示例 user:query此示例中第一部分是正在操作的域 user第二部分是正在执行的操作 query冒号是一个特殊字符用于分隔权限字符串中的下一部分。 每个部分可以包含多个值。 user:query,user:insert,user:delete如果要向用户授予特定部分中的所有值该怎么办我们可以根据通配符执行此操作。 user:* *:queryINI配置 INI 基本上是一种文本配置由由唯一命名的部分组织的键/值对组成。键仅对每个部分是唯一的而不是在整个配置中唯一的与 JDK 属性不同。但是每个部分都可以被视为单个 Properties 定义。 以下是 Shiro 理解的部分示例 # # Shiro INI configuration # [main] # 对象和它们的属性在这里定义例如securityManagerRealms等等或者需要构建SecurityManager myRealm com.example.study.MyRealm [users] # 用户和他们可选分配的角色 如username password, role1, role2, ..., roleN admin admin [roles] # 已分配权限的角色例如roleName perm1, perm2, ..., permN admin user:* [urls] # 用于基于url的安全 [main]部分 该 [main] 部分用于配置应用程序的 SecurityManager 实例及其任何依赖项例如 Realms。 [main] myRealm com.example.shiro.MyRealm # 定义参数 myRealm.username hello示例代码如下 public class MyRealm extends AuthenticatingRealm {private String username;public void setUsername(String username) {this.username username;}Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println(username);/** Output:* hello*/return null;} }如果您需要设置的值不是基元而是另一个对象该怎么办您可以使用美元符号 $ 来引用以前定义的实例。例如 [main] sha256Matcher org.apache.shiro.authc.credential.Sha256CredentialsMatcher myRealm com.example.shiro.MyRealm myRealm.sha256CredentialsMatcher $sha256Matcher示例代码如下 public class MyRealm extends AuthenticatingRealm {private Sha256CredentialsMatcher sha256CredentialsMatcher;public void setSha256CredentialsMatcher(Sha256CredentialsMatcher sha256CredentialsMatcher) {this.sha256CredentialsMatcher sha256CredentialsMatcher;}Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println(sha256CredentialsMatcher);/** Output:* org.apache.shiro.authc.credential.Sha256CredentialsMatcherf6c48ac*/return null;} }使用 INI 配置对象实例如 SecurityManager 或其任何依赖项听起来像是一件困难的事情因为我们只能使用名称/值对。 我们经常喜欢将这种方法称为“穷人”依赖注入虽然不如成熟的 Spring/Guice/JBoss XML 文件强大但您会发现它可以完成很多工作而不会太复杂。当然其他配置机制也可用但它们不是使用 Shiro 所必需的。 [users]部分 [users] 部分允许您定义一组静态用户帐户。[users] 部分中的每一行都必须符合以下格式 username password 角色名称 1 角色名称 2 … 角色名称 N 这在用户帐户数量非常少或不需要在运行时动态创建用户帐户的环境中非常有用。下面是一个示例 [users] root admin, admin guest guest, guest等号左侧的值是用户名 等号右侧的第一个值是用户的密码。需要密码。 密码后的任何逗号分隔值都是分配给该用户的角色的名称。角色名称是可选的。 Shiro 1 的算法例如 md5、SHA1、SHA256 等长期以来被认为不安全不再受支持。 既没有直接的迁移路径也没有向后兼容性。 我们先生成一个密钥示例代码如下 import org.apache.shiro.authc.credential.DefaultPasswordService; import org.apache.shiro.authc.credential.PasswordService;public class Test {public static String encriptPassword(String password) {PasswordService service new DefaultPasswordService();return service.encryptPassword(password);}public static void main(String[] args) {System.out.println(encriptPassword(guest)); /** Output:* $shiro1$SHA-256$500000$69Zvt22yzkGtnTcqQ6UrvQ$6k7YPH02jl130ZKzEugbafHxGLAcJB/ohBTaZcMibpA*/} }在[main]部分里配置加密规则将加密后的密码放入ini配置文件中示例代码如下 [main] # 设置加密 passwordMatcherorg.apache.shiro.authc.credential.PasswordMatcher passwordServiceorg.apache.shiro.authc.credential.DefaultPasswordService passwordMatcher.passwordService$passwordService iniRealm.credentialsMatcher$passwordMatcher securityManager.realms$iniRealm # ----------------------------------------------------------------------------- # 用户和他们可选分配的角色 # username password, role1, role2, ..., roleN # ----------------------------------------------------------------------------- [users] root admin, admin guest $shiro1$SHA-256$500000$69Zvt22yzkGtnTcqQ6UrvQ$6k7YPH02jl130ZKzEugbafHxGLAcJB/ohBTaZcMibpA, guest然后执行登录操作即可。 从 Shiro 2.0 开始该部分不能包含纯文本密码。 您可以使用密钥派生函数对它们进行加密。 Shiro 提供了 bcrypt 和 argon2 的实现。 如果不确定请使用 argon2 派生的密码。 [main] # Shiro2CryptFormat[users] # user1 sha256-hashed-hex-encoded password, role1, role2, ... user1 $shiro2$argon2id$v19$t1,m65536,p4$H5z81Jpr4ntZr3MVtbOUBw$fJDgZCLZjMC6A2HhnSpxULMmvVdW3su/GCU3YbxfFQ, role1, role2, ...一旦指定了派生的文本密码值就必须告诉Shiro这些值是加密的。您可以通过配置[main]部分中隐式创建的方法来使用与您指定的散列算法相对应的适当实现。 [roles]部分 [roles] 部分允许您将权限与 [users] 部分中定义的角色相关联。[roles] 部分中的每一行都必须按以下格式定义角色到权限的键/值映射 rolename permissionDefinition1 permissionDefinition2 … permissionDefinitionN 同样这在角色数量较少或不需要在运行时动态创建角色的环境中非常有用。下面是一个示例 [roles] admin user:* guest user:query[urls]部分 [urls]部分能够为应用程序中的任何匹配 URL 路径定义临时过滤器链这比你通常定义过滤器链的方式要灵活、强大和简洁得多该部分中每行的格式如下 [urls] URL Ant Path ExpressionURL Ant路径表达式 Path Specific Filter Chain路径特定的过滤器链URL Ant路径表达式是一种用于匹配URL路径的模式表达式。支持使用?匹配一个字符、*匹配零个或多个字符不包括路径分隔符、**匹配零个或多个目录层级包括路径分隔符等符号来匹配路径中的特定部分。 例如 [urls] # 匹配 /index.html 不匹配 /home/index.html /?ndex.html anon # 匹配 /user/login 不匹配 /user 、/user/a/b /user/* anon # 匹配 /file、/file/a/b /file/** authc路径特定的过滤器链是指针对特定URL路径配置的过滤器链。Shiro允许你为不同的URL路径定义不同的过滤器链以执行不同的安全检查和操作。 等号 右侧的标记是要为匹配该路径的请求执行的筛选条件的逗号分隔列表。它必须与以下格式匹配 # filterN是在section和[main]中定义的过滤器bean的名称。 # [optional_configN]是一个可选的带括号的字符串它对特定路径的特定过滤器有意义per-filter, path-specific configuration!。如果过滤器不需要对URL路径进行特定配置则可以丢弃括号 filter1[optional_config1], filter2[optional_config2], ..., filterN[optional_configN]因为 filter token 定义了链又名 List所以请记住顺序很重要按照您希望请求流经链的顺序定义逗号分隔的列表。 在Shiro中两者结合起来使用以实现基于URL路径的访问控制。例如在Shiro的INI配置文件中你可以这样配置 [urls] # 允许匿名访问 /login anon # 表示/admin目录下的所有路径都需要身份验证并且用户需要拥有**admin**角色。 /admin/** authc, roles[admin] # 表示/user目录下的所有路径都需要身份验证并且用户需要拥有user:view权限。 /user/** authc, perms[user:view]默认过滤器 当运行 Web 应用程序时Shiro 将创建一些有用的默认实例并自动使它们在该部分中可用。您可以像配置任何其他 bean 一样配置它们并在链定义中引用它们。例如 [main] ... # 注意我们没有为FormAuthenticationFilter authc定义类——它已经被实例化并且可用 authc.loginUrl /login.jsp ...[urls] ... # 确保最终用户已经过身份验证。如果没有重定向到authc。loginUrl”上面, # 验证成功后将它们重定向到原始帐户页面 # 我们试图查看 /account/** authc自动可用的默认过滤器实例由DefaultFilter枚举定义枚举的字段是可用于配置的名称。 名称类描述anonAnonymousFilter匿名过滤器表示不需要登录即可访问的资源。这通常用于过滤静态资源如图片、CSS、JavaScript等。authcFormAuthenticationFilter身份验证过滤器需要用户进行身份验证登录才能访问资源。authcBasicBasicHttpAuthenticationFilterHTTP Basic身份验证过滤器使用HTTP Basic协议进行身份验证。authcBearerBearerHttpAuthenticationFilterBearer Token身份验证过滤器通常用于OAuth 2.0的Bearer Token身份验证。用户需要在请求中携带一个有效的Bearer Token。logoutLogoutFilter登出过滤器用于处理用户的登出请求。执行登出操作后用户通常会被重定向到登录页面或指定的其他页面。noSessionCreationNoSessionCreationFilter不创建会话过滤器用于阻止Shiro为请求创建会话。permsPermissionsAuthorizationFilter权限授权过滤器用于验证用户是否拥有特定的权限。portPortFilter端口过滤器用于限制请求只能通过指定的端口访问。restHttpMethodPermissionFilterREST风格的方法权限过滤器允许为不同的HTTP方法如GET、POST、PUT、DELETE等配置不同的权限。rolesRolesAuthorizationFilter角色授权过滤器用于验证用户是否拥有特定的角色。sslSslFilterSSL过滤器用于强制某些请求必须通过SSLHTTPS连接进行。userUserFilter用户拦截器。如果用户未登录且未选择RememberMe则请求可能会被重定向到登录页面取决于配置。invalidRequestInvalidRequestFilter无效请求过滤器用于处理无效的请求如请求参数不完整或格式不正确等。这通常会导致一个错误响应或重定向到错误页面。 常规启用/禁用 通常通过将其属性设置为 true 或 false 来启用或禁用所有请求的过滤器。默认设置true是因为如果大多数过滤器在链中配置则它们本身需要执行。 例如 [main] ... # 在测试时禁用Shiro的默认‘ssl’过滤器 ssl.enabled false[urls] /some/path ssl, authc /another/path ssl, roles[admin]你也可以调用实现类的方法实现此操作示例代码如下 SslFilter sslFilter new SslFilter(); sslFilter.setEnabled(false);密码学 Shiro 专注于密码学的两个核心元素使用公钥或私钥加密电子邮件等数据的密码以及不可逆地加密密码等数据的哈希值又名消息摘要。 Shiro提供默认哈希在JDK中称为消息摘要的开箱即用实现如MD5、SHA-256、SHA-386、SHA-512等。比如new Sha256Hash(data)。 Md5Hash md5Hash new Md5Hash(123456, admin); Sha256Hash sha256Hash new Sha256Hash(123456, root, 2);他们有三个参数 source计算哈希的原始数据可能是字符串、字节数组或者任何可以转换为字节流的对象。它是你希望进行哈希处理的数据比如密码、文件内容、消息等。salt盐的作用是将原本相同的输入值通过添加不同的盐值变得不同从而防止不同的输入数据产生相同的哈希值即避免哈希冲突或彩虹表攻击。盐通常是随机生成的一段额外数据。hashIterations用于指定哈希算法重复执行的次数通常是为了增加计算的复杂性和提高安全性。这意味着即使原始输入数据和盐值保持不变增加哈希迭代的次数也能使得最终的哈希值更加安全尤其是在面对暴力破解或彩虹表攻击时。 Shiro Hash 实例可以通过其 toHex() 和toBase64() 方法自动提供哈希数据的 Hex 和 Base-64 编码。因此现在您无需弄清楚如何自己正确编码数据。 完整示例代码如下 public static void main(String[] args) {// md5方式Md5Hash md5Hash new Md5Hash(123456, admin);System.out.println(md5-tohexmd5Hash.toHex());System.out.println(md5-toBase64md5Hash.toBase64());// sha256Hash方式Sha256Hash sha256Hash new Sha256Hash(123456, root);System.out.println(sha256Hash-tohexsha256Hash.toHex());System.out.println(sha256Hash-toBase64sha256Hash.toBase64());Sha256Hash sha256Hash2 new Sha256Hash(123456, root, 2);System.out.println(sha256Hash2-tohexsha256Hash2.toHex());System.out.println(sha256Hash2-toBase64sha256Hash2.toBase64());/** Output:* md5-tohexa66abb5684c45962d887564f08346e8d* md5-toBase64pmq7VoTEWWLYh1ZPCDRujQ* sha256Hash-tohex28f4c77c534d5358329b61b326c995cd1743e2e37dd13949ace9c9b816de1fa9* sha256Hash-toBase64KPTHfFNNU1gym2GzJsmVzRdD4uN90TlJrOnJuBbeH6k* sha256Hash2-tohex16c273b943b67a662cacf11b777729103eb235d0aa1d0daf3ce8953001651cf3* sha256Hash2-toBase64FsJzuUO2emYsrPEbd3cpED6yNdCqHQ2vPOiVMAFlHPM*/}会话管理 使用会话 与Shiro中的几乎所有其他内容一样您可以通过Subject交互来获取 Subject subject SecurityUtils.getSubject(); Session session subject.getSession(); session.setAttribute(userId, test);在开发框架代码时可以使用subject.getSession(false)来确保不会不必要地创建会话。 一旦你获得了一个Subject你就可以用它做很多事情比如设置或检索属性设置超时等等。调用getSession()方法可以在任何应用程序中工作甚至是非web应用程序。 会话超时 默认情况下Shiro的实现默认为30分钟的会话超时。也就是说如果创建的任何数据在 30 分钟或更长时间内保持空闲状态则认为该数据已过期不允许再使用。 您可以设置所有会话的默认超时值该值以毫秒为单位而不是秒为单位。配置如下 [main] sessionManager org.apache.shiro.web.session.mgt.DefaultWebSessionManager # 如果需要可以在这里配置属性如会话超时该值以毫秒为单位而不是秒为单位。 securityManager.sessionManager.globalSessionTimeout 1800000会话侦听器 Shiro允许您在重要的会话事件发生时做出反应。你可以实现 SessionListener 接口或扩展方便的 SessionListenerAdapter并相应地对 session 操作做出反应。示例代码如下 public class MySessionListener implements SessionListener {Overridepublic void onStart(Session session) {System.out.println(进来了);}Overridepublic void onStop(Session session) {System.out.println(结束了);}Overridepublic void onExpiration(Session session) {System.out.println(过期了);} }配置监听类 [main] anotherSessionListener org.example.MySessionListener securityManager.sessionManager.sessionListeners $anotherSessionListener执行结果如图 会话存储 每当创建或更新会话时其数据都需要保存到存储位置以便应用程序稍后可以访问它。同样当会话无效且使用时间较长时需要将其从存储中删除以免耗尽会话数据存储空间。这些实现将这些 CRUD 操作委托给内部组件 SessionDAO它反映了数据访问对象 设计模式。 默认情况下Web 应用程序不使用本机会话管理器而是保留 Servlet 容器的默认会话管理器该管理器不支持 SessionDAO。如果您想在基于 Web 的应用程序中启用 SessionDAO 以进行自定义会话存储或会话集群则必须首先配置本机 Web 会话管理器。示例代码如下 public class MyCustomSessionDAO implements SessionDAO {private final MapSerializable, Session sessions new HashMap();Overridepublic Serializable create(Session session) {System.out.println(创建);String str UUID.randomUUID().toString();((SimpleSession)session).setId(str); // sessionId赋值sessions.put(str, session);return str;}Overridepublic Session readSession(Serializable serializable) throws UnknownSessionException {System.out.println(查询);return sessions.get(serializable);}Overridepublic void update(Session session) throws UnknownSessionException {System.out.println(修改);}Overridepublic void delete(Session session) {System.out.println(删除);}Overridepublic CollectionSession getActiveSessions() {return List.of();} }配置监听类 [main] sessionDAO org.example.MyCustomSessionDAO securityManager.sessionManager.sessionDAO $sessionDAO执行结果如图 自定义会话 ID Shiro的实现使用内部SessionIdGenerator组件在每次创建新会话时生成一个新的Session ID。将其分配给新创建的实例然后通过SessionDAO保存ID。 默认值为 JavaUuidSessionIdGenerator它基于 Java UUID 生成 ID。此实现适用于所有生产环境。 如果这不能满足你的需求你可以在 Shiro 的实例上实现接口并配置实现。示例代码如下 public class CustomSessionManager implements SessionIdGenerator {Overridepublic Serializable generateId(Session session) {return custom_ UUID.randomUUID();} }配置监听类 [main] sessionIdGenerator org.example.CustomSessionManager securityManager.sessionManager.sessionDAO.sessionIdGenerator $sessionIdGenerator执行结果如图 会话验证和调度 必须验证会话以便可以从会话数据存储中删除任何无效过期或已停止的会话。这可确保数据存储不会随着时间的推移而填满永远不会再次使用的会话。 在所有环境中默认可用的是 ExecutorServiceSessionValidationScheduler它使用 JDK ScheduledExecutorService 来控制验证的频率。此实施将每小时执行一次验证。您可以通过指定的新实例并指定不同的间隔以毫秒为单位来更改验证的发生速率 sessionValidationScheduler org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler # Default is 3,600,000 millis 1 hour: sessionValidationScheduler.interval 3600000如果您希望提供自定义实现您可以将其指定为 default 实例的属性。示例代码如下 public class MySessionValidationScheduler implements SessionValidationScheduler {Overridepublic boolean isEnabled() {return false;}Overridepublic void enableSessionValidation() {}Overridepublic void disableSessionValidation() {} }配置监听类 [main] sessionValidationScheduler org.example.MySessionValidationScheduler securityManager.sessionManager.sessionValidationScheduler $sessionValidationScheduler在某些情况下你可能希望完全禁用会话验证因为你已经设置了一个不受Shiro控制的流程来为你执行验证。 [main] securityManager.sessionManager.sessionValidationSchedulerEnabled false如果你关闭了 Shiro 的会话验证调度器你必须通过其他机制cron job 等执行定期会话验证。这是保证 Session不会填满数据存储的唯一方法。 但是某些应用程序可能不希望 Shiro 自动删除会话。但是某些应用程序可能不希望 Shiro 自动删除会话。 [main] securityManager.sessionManager.deleteInvalidSessions falseRemember Me 如果 Shiro 实现了 org.apache.shiro.authc.RememberMeAuthenticationToken 接口则 Shiro 将执行 rememberMe 服务。 public interface RememberMeAuthenticationToken extends AuthenticationToken {boolean isRememberMe(); }以编程方式使用 rememberMe您可以将值设置为在支持此配置的类上。例如 UsernamePasswordToken usernamePasswordToken new UsernamePasswordToken(guest, guest); usernamePasswordToken.setRememberMe(true);整合SpringBoot 经过前面的内容讲解掌握了Shiro的基本知识。下面将整合SpringBoot框架模拟Web操作前后不分离项目。 pom依赖配置文件如下 propertiesjava.version8/java.version/propertiesparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.7.18/versionrelativePath/ !-- lookup parent from repository --/parentdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-thymeleaf/artifactId/dependencydependencygroupIdorg.apache.shiro/groupIdartifactIdshiro-spring-boot-web-starter/artifactIdversion1.13.0/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-websocket/artifactId/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.9/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.28/version/dependency/dependencies注并没有使用最新的Shiro2.x.x版本要求是需要JDK11以上但是再与SpringBoot3.x.x整合过程中遇到很多问题所以建议使用Shiro1.x.xSpringBoot2.x.xJDK1.8。 然后创建Shiro需要的基本配置信息示例代码如下 Configuration public class ShiroConfig {/*** 创建Shiro Web应用的整体安全管理*/Beanpublic DefaultWebSecurityManager securityManager(){DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();// 可以添加其他配置如缓存管理器、会话管理器等return defaultWebSecurityManager;} }一切就绪后运行一下没问题最基本的SpringBootShiro整合已经完成。 登录 框架搭建完成后作为权限认证和授权最重要的一部分也是项目访问的入口我们先从登录开始讲解。 根据.ini文件的配置所需要的表有三个用户表、角色表、权限表然后用户表和角色表关联中间表角色表和权限表管理中间表一共有5个表 -- 用户表 CREATE TABLE user (id int NOT NULL AUTO_INCREMENT,username varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,password varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 密码,age int DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT5 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci; -- 角色表 CREATE TABLE role (id int NOT NULL AUTO_INCREMENT,name varchar(64) DEFAULT NULL,description varchar(100) DEFAULT NULL COMMENT 描述,PRIMARY KEY (id),KEY role_id_IDX (id) USING BTREE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci; -- 权限表 CREATE TABLE permission (id int NOT NULL AUTO_INCREMENT,name varchar(100) DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci; -- 用户-角色关联表 CREATE TABLE user_role (id int NOT NULL,user_id int NOT NULL,role_id int NOT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci; -- 角色-权限管理表 CREATE TABLE role_permission (id int NOT NULL AUTO_INCREMENT,role_id int NOT NULL,permission_id int NOT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;配置默认超管数据 -- 插入用户生成密码通过Sha256Hash.tobase64密码123456 INSERT INTO user (id, username, password, age) VALUES(1, admin, rA59A3gXCU6eC0RBbrjIJ1nsCkhJFwZfcbFhCaGng, 22); -- 插入角色 INSERT INTO role (id, name, description) VALUES(1, admin, 超级管理员); -- 插入权限所有权限 INSERT INTO permission (id, name) VALUES(1, *); -- 插入用户-角色关联关系 INSERT INTO user_role (id, user_id, role_id) VALUES(1, 1, 1); -- 插入角色-权限关联关系 INSERT INTO role_permission (id, role_id, permission_id) VALUES(1, 1, 1);生成对应POJO实体以及Mapper文件这里使用Mybatis-plus你要可以使用其他框架生成的代码就不展示了只介绍重点。 我们先创建一个Realm类继承AuthorizingRealm抽象类该类可以直接自定义验证和授权的业务示例代码如下 Component public class UserRealm extends AuthorizingRealm {Autowiredprivate UserService userService;Autowiredprivate RoleService roleService;Autowiredprivate PermissionService permissionService;// 验证Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {String username (String) authenticationToken.getPrincipal(); // 用户名String password new String((char[]) authenticationToken.getCredentials()); // 凭证密码需要转换类型User user userService.getOne(new QueryWrapperUser().ge(username, username));if (user null) {throw new UnknownAccountException(账号不存在);}// 手动验证密码是否正确shiro不会帮你做这些Sha256Hash sha256Hash new Sha256Hash(password.getBytes(StandardCharsets.UTF_8), username);if (!sha256Hash.toHex().equals(user.getPassword())) {throw new IncorrectCredentialsException(密码错误);}SimpleAuthenticationInfo simpleAuthenticationInfo new SimpleAuthenticationInfo(user, sha256Hash.toHex(), ByteSource.Util.bytes(username), getName());return simpleAuthenticationInfo;}// 授权Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {User user (User) principalCollection.getPrimaryPrincipal();// 获取角色ListRole roleList roleService.getByUserId(user.getId());SimpleAuthorizationInfo simpleAuthorizationInfo new SimpleAuthorizationInfo();roleList.forEach(item -{simpleAuthorizationInfo.addRole(item.getName());});ListInteger roleIds roleList.stream().map(Role::getId).collect(Collectors.toList());// 获取权限ListPermission permissions permissionService.listByIds(roleIds);permissions.forEach(item-{simpleAuthorizationInfo.addStringPermission(item.getName());});return simpleAuthorizationInfo;}}简单介绍下SimpleAuthenticationInfo类以四个构造参数为例 principal表示用户的身份信息这个参数可以通过subject.getPrincipal()方法获取表示当前记录的用户进而可以获取该用户的一系列所需属性。credentials表示用户的凭证信息通常是密码明文或密码的加密形式。如果未指定密码匹配规则就是明文否则就要按指定加密方式进行传参比如MD5、SHA-256等等。不然就会报异常错误。salt盐值用于加密密码。这是为了防止两个用户的初始密码相同通过添加盐值可以增加密码的复杂性和唯一性。在前面密码学章节有介绍。realmName表示当前方法调用的Realm名称。 Realm创建好后需要交给SecurityManager进行管理示例代码如下 Configuration public class ShiroConfig {/*** 创建Shiro Web应用的整体安全管理*/Beanpublic DefaultWebSecurityManager securityManager(){DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();defaultWebSecurityManager.setRealm(realm());// 可以添加其他配置如缓存管理器、会话管理器等return defaultWebSecurityManager;}/*** 注册Realm的对象用于执行安全相关的操作如用户认证、权限查询*/Beanpublic Realm realm() {UserRealm userRealm new UserRealm();userRealm.setCredentialsMatcher(hashedCredentialsMatcher()); // 为realm设置指定算法return userRealm;} }另外数据库使用的SHA-256加密在Realm校验时向SimpleAuthenticationInfo传参时为了保持一致防止报错所以还需要指定密码算法规则。 /*** 指定密码加密算法类型*/Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName(SHA-256); // 设置哈希算法return hashedCredentialsMatcher;}然后我们需要配置接口的过滤和拦截否则访问时会被拦截如图所示 示例代码如下 /*** 核心安全过滤器对进入应用的请求进行拦截和过滤从而实现认证、授权、会话管理等安全功能。*/Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);// 当未登录的用户尝试访问受保护的资源时重定向到这个指定的登录页面。//shiroFilterFactoryBean.setLoginUrl(/user/index);// 配置拦截器链指定了哪些路径需要认证、哪些路径允许匿名访问MapString, String filterChainDefinitionMap new LinkedHashMap();filterChainDefinitionMap.put(/user/login, anon);filterChainDefinitionMap.put(/user/index, anon);filterChainDefinitionMap.put(/**, authc);shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}然后我们编写Controller层代码示例代码如下 RestController RequestMapping(value /user) public class UserController {// 登录跳转GetMapping(/index)public String index() {return login.html;}PostMapping(/login)public ResponseEntityString login(RequestBody User user) {UsernamePasswordToken token new UsernamePasswordToken(user.getUsername(),//身份信息user.getPassword());//凭证信息// 对用户信息进行身份认证Subject subject SecurityUtils.getSubject();try {subject.login(token);} catch (UnknownAccountException e) {return ResponseEntity.ok(账号或密码错误);}return ResponseEntity.ok(success);}// 登录成功后跳转页面GetMapping(/main)public String main() {return main.html;} }然后我们输入地址http://localhost:8080/user/index进入登录页面输入正确的账号SESSIONID也生成了进入首页前端代码不展示如图所示 未授权跳转 前面的示例中在filterChainDefinitionMap中定义了/user/index登录页面接口为匿名未登录的情况下访问其他就会重定向到http://localhost:8080/login.jsp如图所示 现在.jsp页面用的也比较少如果想让他未授权的时候访问任何地址都会跳转到指定登录页面我们可以通过setLoginUrl()方法进行设置示例代码如下 Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);// 当未登录的用户尝试访问受保护的资源时重定向到这个指定的登录页面。shiroFilterFactoryBean.setLoginUrl(/user/index);// 配置拦截器链指定了哪些路径需要认证、哪些路径允许匿名访问MapString, String filterChainDefinitionMap new LinkedHashMap();filterChainDefinitionMap.put(/user/login, anon);filterChainDefinitionMap.put(/**, authc);shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}这样设置后未授权的情况下访问任何接口都可以跳到登录页面。 已授权跳转 前面我们通过setLoginUrl()方法指定未登录情况下的页面跳转setUnauthorizedUrl()方法用于登陆后访问没权限的资源 ShiroFilterFactoryBean shiroFilterFactoryBean new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);// 当未登录的用户尝试访问受保护的资源时重定向到这个指定的登录页面。shiroFilterFactoryBean.setLoginUrl(/user/index);// 成功后跳转地址但是测试时未生效 // shiroFilterFactoryBean.setSuccessUrl(/user/main);// 当用户访问没有权限的资源时系统重定向到指定的URL地址。shiroFilterFactoryBean.setUnauthorizedUrl(/user/unauth);如果没有设置接口拦截指定接口访问角色等设置访问的一些接口就会报错误页面如图所示 设置接口拦截指定角色访问示例代码如下 Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);// 当用户访问没有权限的资源时系统重定向到指定的URL地址。shiroFilterFactoryBean.setUnauthorizedUrl(/user/unauth);MapString, String filterChainDefinitionMap new LinkedHashMap();filterChainDefinitionMap.put(/user/login, anon);// 设置/root下的资源只能root角色访问filterChainDefinitionMap.put(/root/**, roles[root]);filterChainDefinitionMap.put(/**, authc);shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}使用其他角色访问/root/**下的资源就会跳转指定页面如图所示 setSuccessUrl()方法用于成功后页面跳转但是在测试过程中没有生效如果知道解决办法的可以评论区交流不过可以在登录成功后前端或后端跳转解决这种问题。 现在很多互联网公司在跳转登录页面时会判断用户会话是否存在如果存在就直接跳转首页不会进行登录操作除非登录退出后重新登录shiro提供非常方便的操作示例代码如下 GetMapping(/index)public ModelAndView index() {Subject subject SecurityUtils.getSubject();if (subject.isAuthenticated() || subject.isRemembered()) {return new ModelAndView(redirect:main);}return new ModelAndView(login.html);}我们在跳转登录页面时通过isAuthenticated()方法用户已登录和isRemembered()方法用户是否勾选“记住我”功能判断用户身份如果有存在的会话就不让他重新登录直接跳到首页即可。 另外在多说一句在前后不分离项目中可能ShiroFilterFactoryBean 用的比较多如果前后分离还有一种方式定义请求过滤链的组件示例代码如下 Beanpublic ShiroFilterChainDefinition shiroFilterChainDefinition() {DefaultShiroFilterChainDefinition chainDefinition new DefaultShiroFilterChainDefinition();chainDefinition.addPathDefinition(/user/login, anon);chainDefinition.addPathDefinition(/**, authc);return chainDefinition;}登录超时 登录超时是一种安全措施如果用户在公共计算机或共享设备上登录后忘记退出登录超时可以确保他们的会话在一段时间后自动结束从而降低被他人冒用的风险。 另一方面释放服务器端资源。每个活跃的会话都会占用服务器的一定资源包括内存、数据库连接等。通过设置合理的登录超时时间可以自动释放那些不再需要的会话资源提高服务器的性能和效率。 示例代码如下 /*** 创建Shiro Web应用的整体安全管理*/Beanpublic DefaultWebSecurityManager securityManager(){DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();defaultWebSecurityManager.setRealm(realm());defaultWebSecurityManager.setSessionManager(defaultWebSessionManager()); // 注册会话管理// 可以添加其他配置如缓存管理器、会话管理器等return defaultWebSecurityManager;}/*** 创建会话管理*/Beanpublic DefaultWebSessionManager defaultWebSessionManager(){DefaultWebSessionManager defaultWebSessionManager new DefaultWebSessionManager();defaultWebSessionManager.setGlobalSessionTimeout(10000); //设置会话过期时间毫秒return defaultWebSessionManager;}上述代码设置了会话过期时间为10s当时间范围内没有操作自动跳转登录页面 记住我 “记住我”Remember Me功能允许系统记住用户的登录状态即使用户关闭浏览器后关闭整个浏览器不是只关闭页面下一次访问时仍然能够自动登录。这个功能通常通过在用户登录时设置一个特殊的 rememberMe cookie 来实现。 调用登录接口设置RememberMe的参数值示例代码如下 // 方法一 UsernamePasswordToken token new UsernamePasswordToken(username, password, true); // 方法二 UsernamePasswordToken token new UsernamePasswordToken(username, password); token.setRememberMe(true);然后登录成功后我们可以在浏览器的Application中看到有一个rememberMe属性如图所示 注当你再次把浏览器全部关闭后rememberMe可能依旧未生效你需要检查你的拦截器链将authc过滤器改为user过滤器 示例代码如下 MapString, String filterChainDefinitionMap new LinkedHashMap();filterChainDefinitionMap.put(/root/**, roles[root]);filterChainDefinitionMap.put(/user/login, anon);filterChainDefinitionMap.put(/**, user);然后再次尝试即可user过滤器可以过滤未登录和rememberMe功能。 默认情况下cookie有效期为一年如图所示 你也可以自定义cookie有效期示例代码如下 /*** cookie管理对象;记住我功能,rememberMe管理器* return*/Beanpublic CookieRememberMeManager rememberMeManager(){CookieRememberMeManager cookieRememberMeManager new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)cookieRememberMeManager.setCipherKey(Base64.getDecoder().decode(h0nCDfE5LEh3owvvugjPHyYa7NDuduM2bUUPJf8zII));return cookieRememberMeManager;}Beanpublic SimpleCookie rememberMeCookie(){// 使用默认命名rememberMeSimpleCookie simpleCookie new SimpleCookie(rememberMe);// setcookie 的 httponly 属性如果设为 true 的话会增加对 xss 防护的安全系数// 只能通过http访问javascript无法访问防止xss读取cookiesimpleCookie.setHttpOnly(true);simpleCookie.setPath(/);// 记住我 cookie 生效时间10秒 ,单位是秒simpleCookie.setMaxAge(10);return simpleCookie;}/*** aes加密方法* return*/public static String generateRandomKey(int keySize) throws Exception {// 创建一个密钥生成器指定算法为AES或其他你选择的算法KeyGenerator keyGenerator KeyGenerator.getInstance(AES);// 初始化密钥生成器指定密钥长度例如128, 192, 或 256位keyGenerator.init(keySize);// 生成一个密钥SecretKey secretKey keyGenerator.generateKey();// 将密钥转换为字节数组byte[] keyBytes secretKey.getEncoded();// 将字节数组编码为Base64字符串String encodedKey Base64.getEncoder().encodeToString(keyBytes);return encodedKey;}然后将rememberMeManager注册到DefaultWebSecurityManager中示例代码如下 /*** 创建Shiro Web应用的整体安全管理*/Beanpublic DefaultWebSecurityManager securityManager(){DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();defaultWebSecurityManager.setRealm(realm());defaultWebSecurityManager.setRememberMeManager(rememberMeManager());// 可以添加其他配置如缓存管理器、会话管理器等return defaultWebSecurityManager;}设置完成后重启后访问页面登录后把浏览器关闭等待10s后再次打卡访问就会跳转到登录页面。 有的时候会很容易把过期时间和“记住我”弄混下面谈谈他们的区别 过期时间指的是某个对象、数据或会话在创建后能够保持有效或可用的时间段。一旦超过这个时间对象、数据或会话将不再有效或可用。“记住我”是一个功能选项通常出现在登录表单中。当用户勾选此选项并成功登录后即使关闭浏览器或清除会话下次访问网站时也会自动登录无需重新输入用户名和密码。 也就是说即使勾选了“记住我”登录后到会话过期时间后未操作一样会重新登录现在很多公司登录页面都没有“记住我”的功能选项了反而更偏向于会话时长。 注解 Shiro框架提供的一种简化开发的方式通过在代码中添加注解可以方便地实现权限控制和身份认证。 RequiresAuthentication注解 表示当前用户必须已经通过身份验证即登录才能访问被注解的方法或类。相当于Subject.isAuthenticated()返回true。为了演示我们把config中的权限都去掉示例代码如下 GetMapping(/list)RequiresAuthenticationpublic ResponseEntityString list() {Subject subject SecurityUtils.getSubject();Session session subject.getSession();System.out.println(session.getId());return ResponseEntity.ok(success);}如果我们没有登录的情况下访问该接口默认情况下就会报错我们可以自定义处理方式跳转登录页面如图所示 登录后就可以返回正确的数据如图所示 RequiresUser注解 表示当前用户已经进行了身份验证或者通过“记住我”功能进行了登录。这个注解比RequiresAuthentication更宽松因为它允许通过“记住我”功能登录的用户访问。 GetMapping(/list)RequiresUserpublic ResponseEntityString list() {//2.2对用户信息进行身份认证Subject subject SecurityUtils.getSubject();Session session subject.getSession();System.out.println(session.getId());return ResponseEntity.ok(success);}该注解不仅支持登录后访问还支持“记住我”功能浏览器关闭后也可以继续访问。 RequiresGuest注解 表示当前用户没有进行身份验证或者是以游客身份访问。如果用户已经登录则会被拒绝访问。 GetMapping(/list)RequiresGuestpublic ResponseEntityString list() {//2.2对用户信息进行身份认证Subject subject SecurityUtils.getSubject();Session session subject.getSession();System.out.println(session.getId());return ResponseEntity.ok(success);}感觉这个注解用的应该挺少把游客可以访问登录用户不能访问如图所示 RequiresRoles注解 表示当前用户必须拥有指定的角色才能访问被注解的方法或类。可以指定一个或多个角色并设置逻辑关系如AND、OR。 GetMapping(/list)RequiresRoles(value {admin,root}, logical Logical.AND)public ResponseEntityString list() {//2.2对用户信息进行身份认证Subject subject SecurityUtils.getSubject();Session session subject.getSession();System.out.println(session.getId());return ResponseEntity.ok(success);}如图所示 RequiresPermissions注解 当前用户必须拥有指定的权限才能访问被注解的方法或类。可以指定一个或多个权限并设置逻辑关系如AND、OR。 GetMapping(/list)RequiresPermissions(value {user:list,user:insert}, logical Logical.OR)public ResponseEntityString list() {//2.2对用户信息进行身份认证Subject subject SecurityUtils.getSubject();Session session subject.getSession();System.out.println(session.getId());return ResponseEntity.ok(success);}当未登录或者没有该权限的用户访问就会报错否则返回success如上图所示。 登录后跳回之前页面 通常我们希望在用户成功登录后能够跳回到他们之前尝试访问的页面即被拦截之前的页面。 Shiro提供了SavedRequest功能来自动处理这种情况。当你配置了Shiro的Web过滤器后Shiro会自动捕获被拦截的请求并存储在会话中。一旦用户成功登录你可以从会话中获取这个请求并重定向到它。 比如登录超时后重新登录跳转上次所在页面示例代码如下 PostMapping(/login)public ModelAndView login(HttpServletRequest request, RequestParam(username) String username, RequestParam(password) String password) {UsernamePasswordToken token new UsernamePasswordToken(username,//身份信息password);//凭证信息ModelAndView modelAndView new ModelAndView();// 对用户信息进行身份认证Subject subject SecurityUtils.getSubject();if (subject.isAuthenticated() || subject.isRemembered()) {modelAndView.setViewName(redirect:main);return modelAndView;}try {subject.login(token);// 判断savedRequest不为空时获取上一次停留页面进行跳转SavedRequest savedRequest WebUtils.getSavedRequest(request);if (savedRequest ! null) {String requestUrl savedRequest.getRequestUrl();modelAndView.setViewName(redirect: requestUrl);return modelAndView;}} catch (AuthenticationException e) {modelAndView.addObject(responseMessage, 用户名或者密码错误);modelAndView.setViewName(login);return modelAndView;}modelAndView.setViewName(redirect:main);return modelAndView;}如图所示 如果上一个页面是错误页面就会导致登录进来直接进入登录错误页面所以不建议这种处理方式 自定义缓存 Shiro提供的一个缓存管理器MemoryConstrainedCacheManager使用JVM的内存来存储缓存数据开发者可能会选择使用EhCache作为默认的缓存实现因为Shiro提供了与EhCache的集成支持。 登录时虽然继承的父类AuthorizingRealm实际进入的是AuthenticatingRealm进行的缓存操作当缓存数据为null时进入对应的Realm进行数据库查询否则使用缓存减轻数据库压力提供系统性能。 下面使用Redis当作Shiro缓存参考MemoryConstrainedCacheManager和MapCache示例代码如下 // 定义cachemanage public class RedisCacheManage implements CacheManager {private final RedisTemplateString, Object redisTemplate;public RedisCacheManage(RedisTemplateString, Object redisTemplate) {this.redisTemplate redisTemplate;}Overridepublic K, V CacheK, V getCache(String s) throws CacheException {return new RedisCache(s, redisTemplate);} } // 定义shiro缓存 public class RedisCacheK, V implements CacheK, V {private final HashOperationsString, K, V hashOperations;private final String name;public RedisCache(String name, RedisTemplateString, Object redisTemplate) {this.name name;this.hashOperations redisTemplate.opsForHash();}Overridepublic V get(K k) throws CacheException {return hashOperations.get(name, k);}Overridepublic V put(K k, V v) throws CacheException {hashOperations.put(name, k, v);return v;}Overridepublic V remove(K k) throws CacheException {V v hashOperations.get(name, k);hashOperations.delete(name, k);return v;}Overridepublic void clear() throws CacheException {hashOperations.delete(name);}Overridepublic int size() {return hashOperations.size(name).intValue();}Overridepublic SetK keys() {return hashOperations.keys(name);}Overridepublic CollectionV values() {return hashOperations.values(name);} }为了防止会话堆积你可以设置缓存过期时间。 Config中设置缓存管理示例代码如下 /*** 创建Shiro Web应用的整体安全管理*/Beanpublic DefaultWebSecurityManager securityManager(){DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();defaultWebSecurityManager.setRealm(realm());// 可以添加其他配置如缓存管理器、会话管理器等return defaultWebSecurityManager;}/*** 注册Realm的对象用于执行安全相关的操作如用户认证、权限查询*/Beanpublic Realm realm() {UserRealm userRealm new UserRealm();userRealm.setCachingEnabled(true); // 启动全局缓存userRealm.setAuthenticationCachingEnabled(true); // 启动验证缓存userRealm.setAuthenticationCacheName(Authentication); // 定义验证缓存名//userRealm.setAuthorizationCachingEnabled(true); // 启动授权缓存//userRealm.setAuthorizationCacheName(Authorization); // 定义授权缓存名userRealm.setCacheManager(cacheManager());return userRealm;}Beanpublic CacheManager cacheManager() {RedisCacheManage redisCacheManage new RedisCacheManage(redisTemplate());return redisCacheManage;}然后进行登录操作第一次登录会查询数据库如图所示 并在Redis中会插入一条会话的数据实际上缓存了验证时候的用户信息授权时候并没缓存也就不需要开启如图所示 第二次登录时就会直接从缓存中获取数据不会进入自定义Realm中。 你有可能遇到ByteSource无法序列化的问题你需要自己实现序列号和ByteSource具体逻辑参考SimpleByteSource示例代码如下 public class ByteSourceSerializable implements ByteSource,Serializable {private static final long serialVersionUID 9206836077237410719L;private final byte[] bytes;private String cachedHex;private String cachedBase64;public ByteSourceSerializable(byte[] bytes) {this.bytes bytes;}public ByteSourceSerializable(char[] chars) {this.bytes CodecSupport.toBytes(chars);}// ... ... }然后再自定义Realm中盐值使用自定义的类示例代码如下 SimpleAuthenticationInfo sai new SimpleAuthenticationInfo(user, sha256Hash.toHex(), new ByteSourceSerializable(username), getName());自定义SessionDao 在Shiro框架中SessionDAO的默认实现是MemorySessionDAO。MemorySessionDAO是Shiro已经实现的Session的CRUD创建、读取、更新、删除接口类它内部维护了一个ConcurrentMap来保存session数据即将session数据缓存在内存中。 似乎在单机部署的情况下够用但是在技术发展迅速的今天来说带来很多问题比如 不提供持久化功能这意味着当服务器重启或发生故障时用户的会话状态无法恢复。在分布式系统中由于session数据仅在单个服务器内存中保存因此当用户请求被负载均衡到其他服务器时无法访问到之前的session数据。 为了解决这些问题使用Redis作为Session存储可以解决分布式系统中的Session共享问题并提供较高的读写性能和可靠性。 创建自定义SessionDao参考EnterpriseCacheSessionDAO示例代码如下 public class RedisSessionDao extends CachingSessionDAO {protected Serializable doCreate(Session session) {Serializable sessionId this.generateSessionId(session);this.assignSessionId(session, sessionId);return sessionId;}protected Session doReadSession(Serializable sessionId) {return null;}protected void doUpdate(Session session) {}protected void doDelete(Session session) {} }Config中设置自定义SessionDao示例代码如下 /*** 创建Shiro Web应用的整体安全管理*/Beanpublic DefaultWebSecurityManager securityManager(){DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();defaultWebSecurityManager.setRealm(realm());defaultWebSecurityManager.setSessionManager(defaultWebSessionManager()); // 注册会话管理// 可以添加其他配置如缓存管理器、会话管理器等return defaultWebSecurityManager;}/*** 创建会话管理*/Beanpublic DefaultWebSessionManager defaultWebSessionManager() {DefaultWebSessionManager defaultWebSessionManager new DefaultWebSessionManager();defaultWebSessionManager.setGlobalSessionTimeout(10000); // 缓存过期时间defaultWebSessionManager.setSessionDAO(sessionDAO());defaultWebSessionManager.setCacheManager(cacheManager()); // 设置缓存管理器自动给sessiondao赋值return defaultWebSessionManager;}Beanpublic SessionDAO sessionDAO() {RedisSessionDao redisSessionDao new RedisSessionDao(); // redisSessionDao.setSessionIdGenerator(sessionIdGenerator()); // redisSessionDao.setCacheManager(cacheManager()); // 设置缓存管理器redisSessionDao.setActiveSessionsCacheName(shiro:session); // 自定义redis存放的key名称return redisSessionDao;}Beanpublic SessionIdGenerator sessionIdGenerator(){return new CustomSessionManager();}Beanpublic CacheManager cacheManager() {RedisCacheManage redisCacheManage new RedisCacheManage(redisTemplate());return redisCacheManage;}// redis配置Autowiredprivate RedisConnectionFactory redisConnectionFactory;Beanpublic RedisTemplateString, Object redisTemplate() {RedisTemplateString, Object redisTemplate new RedisTemplate();redisTemplate.setConnectionFactory(redisConnectionFactory);//设置默认序列化方式类型转换错误redisTemplate.setDefaultSerializer(new JdkSerializationRedisSerializer());//启动默认序列化redisTemplate.setEnableDefaultSerializer(true);return redisTemplate;}登录后跳转正常如图所示 查看Redis中是否保存Session信息如图所示 等待10s后会话过期自动跳回登录页面也没问题每次过期后会将当前Session对象删除再创建新的Session对象新的对象未使用隔段时间会定时清除。 自定义过滤器 Shiro的内置过滤器可能无法满足所有复杂的权限控制需求。自定义过滤器可以用于实现特定的认证逻辑如验证码验证、第三方登录等也可以实现基于用户访问频率的限制防止恶意请求或过度使用资源。 OncePerRequestFilter 确保每个请求只会被过滤一次。如果你只是需要执行一些通用的过滤逻辑例如日志、请求记录等可以继承这个类。 public class UserLoginFilter extends OncePerRequestFilter {Overrideprotected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {System.out.println(进来了);if (false) {// 执行下一个过滤器filterChain.doFilter(request, response);} else {// 方法一返回json数据MapString, Object map new HashMap();map.put(code, fail);map.put(message, 错误);response.getWriter().println(map);// 方法二重定向其它页面((HttpServletResponse)response).sendRedirect(/user/main);}} }Config配置文件示例代码如下 Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);// 过滤器赋值MapString, Filter filterMap new HashMap();filterMap.put(userLoginFilter, userLoginFilter());shiroFilterFactoryBean.setFilters(filterMap);// 配置拦截器链指定了哪些路径需要认证、哪些路径允许匿名访问MapString, String filterChainDefinitionMap new LinkedHashMap();filterChainDefinitionMap.put(/user/login, anon);filterChainDefinitionMap.put(/**, userLoginFilter);shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}Beanpublic UserLoginFilter userLoginFilter(){return new UserLoginFilter();} AccessControlFilter用来进行访问控制比如判断用户是否有权限访问某个 URL。如果你要实现基于认证、授权等条件的控制可以继承这个类。 public class UserLoginFilter extends AccessControlFilter {/*** 确定是否允许访问。如果返回true则允许访问继续进行如果返回false则会调用onAccessDenied方法*/Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object o) throws Exception {System.out.println(允许访问-进来了);return ((HttpServletRequest)request).getRequestURI().equals(/user/main);}/*** 当isAccessAllowed方法返回false时此方法被调用。在这里可以实现访问被拒绝时的处理*/Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {System.out.println(拒绝访问-进来了);// 方法一返回json数据MapString, Object map new HashMap();map.put(code, fail);map.put(message, 错误);response.getWriter().println(map);// 方法二重定向其它页面((HttpServletResponse)response).sendRedirect(/user/index);return false;} }当访问/user/main时被允许访问其它页面则拒绝进入onAccessDenied()方法然后进行对应拒绝处理如图所示 登录退出 在Apache Shiro中退出系统通常意味着释放当前用户的所有认证信息并结束会话。 GetMapping(/logout)public void logout() {SecurityUtils.getSubject().logout();} 调用logout()方法就可以实现登录退出的功能我们可以看下里面有做哪些处理 以缓存为例进入CachingRealm类如图所示 根据Debug先进入AuthorizingRealm类前面介绍过缓存没保存授权的记录如果开启了缓存依旧会删除操作先进入AuthenticatingRealm.doClearCache()然后获取缓存和凭证进行删除操作如图所示 如果使用自定义了SessionDAO使用缓存进行管理的话进入finally代码块执行会话清除工作如图所示 然后CachingSessionDAO类将原来的Session删除后生成一个新的Session对象用于下次登录操作登录时再执行修改操作将登录的信息保存到该Session如图所示 单用户登录 单用户登录Single User Login其核心意义在于一个账号在同一时间仅能由一个用户进行登录。如果某个用户已经使用某一账号成功登录系统那么当另一用户尝试以同一账号登录时系统将会阻止这次登录尝试或者迫使先前登录的用户退出系统以便新用户可以登录确保账号使用的唯一性和安全性。 网上最常见的方法就是获取所有已存在会话通过遍历匹配会话进行删除示例代码如下 Controller RequestMapping(value /user) public class UserController {Autowiredprivate SessionDAO sessionDAO;PostMapping(/login)public ModelAndView login(HttpServletRequest request, RequestParam(username) String username, RequestParam(password) String password) {// 提前加密解决自定义缓存匹配时错误UsernamePasswordToken token new UsernamePasswordToken(username,//身份信息password);//凭证信息ModelAndView modelAndView new ModelAndView();// 对用户信息进行身份认证Subject subject SecurityUtils.getSubject();if (subject.isAuthenticated() subject.isRemembered()) {modelAndView.setViewName(redirect:main);return modelAndView;}try {subject.login(token);// 单用户登录匹配已存在会话进行清除CollectionSession activeSessions sessionDAO.getActiveSessions();for (Session session : activeSessions) {String oldUsername (String) session.getAttribute(username);if (oldUsername ! null oldUsername.equals(username)) {sessionDAO.delete(session);}}} catch (AuthenticationException e) {e.printStackTrace();modelAndView.addObject(responseMessage, 用户名或者密码错误);modelAndView.setViewName(redirect:index);return modelAndView;}// 设置会话属性Session session subject.getSession();session.setAttribute(username, username);modelAndView.setViewName(redirect:main);return modelAndView;} }通过遍历去匹配会话的方式在会话数量庞大的情况下影响系统的整体性能。 解决该问题的另一种方法就是再定义一个username的Key里面存放会话信息这样匹配的效率提高。示例代码如下 public class RedisSessionDao extends CachingSessionDAO {private final static String usernameKey basic_;protected Serializable doCreate(Session session) {Serializable sessionId this.generateSessionId(session);this.assignSessionId(session, sessionId);return sessionId;}protected Session doReadSession(Serializable sessionId) {return null;}protected void doUpdate(Session session) {if (session.getAttribute(username) ! null){String username (String) session.getAttribute(username);getActiveSessionsCache().put(usernameKeyusername, session);}}protected void doDelete(Session session) {if (session.getAttribute(username) ! null){String username (String) session.getAttribute(username);getActiveSessionsCache().remove(usernameKeyusername);}}public Session getByUsername(String username){String key usernameKeyusername;return getActiveSessionsCache().get(key);} }然后调用getByUsername()方法进行下线逻辑示例代码如下 Controller RequestMapping(value /user) public class UserController {Autowiredprivate SessionDAO sessionDAO;PostMapping(/login)public ModelAndView login(HttpServletRequest request, RequestParam(username) String username, RequestParam(password) String password) {// 提前加密解决自定义缓存匹配时错误UsernamePasswordToken token new UsernamePasswordToken(username,//身份信息password);//凭证信息ModelAndView modelAndView new ModelAndView();// 对用户信息进行身份认证Subject subject SecurityUtils.getSubject();if (subject.isAuthenticated() subject.isRemembered()) {modelAndView.setViewName(redirect:main);return modelAndView;}try {subject.login(token);// 单用户登录匹配已存在会话进行清除Session activeSessions sessionDAO.getByUsername(username);if (activeSessions ! null) {sessionDAO.delete(activeSessions);}} catch (AuthenticationException e) {e.printStackTrace();modelAndView.addObject(responseMessage, 用户名或者密码错误);modelAndView.setViewName(redirect:index);return modelAndView;}modelAndView.setViewName(redirect:main);return modelAndView;} }问题排查 在整合Shiro过程会遇到很多问题相关文章如下 解决SpringBoot整合Shiro报Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken… 解决SpringBoot整合Shiro报Submitted credentials for token org.apache.shiro.authc.UsernamePasswordToken… 解决Springboot整合Shiro自定义SessionDAORedis管理会话登录后不跳转首页 解决Springboot整合Shiro自定义SessionDAORedis管理会话登录后不跳转首页 解决Springboot整合ShiroRedis退出登录后不清除缓存 解决Springboot整合ShiroRedis退出登录后不清除缓存 项目示例 项目示例
http://www.tj-hxxt.cn/news/233669.html

相关文章:

  • 对于网站建设的描述网站后台扫描插件
  • 东莞市建设网站网页即时聊天
  • 做网站需要的带宽上行还是下行网站开发样板
  • wordpress 建站五分钟国际知名设计公司有哪些
  • 安徽长江建设集团有限公司网站视频制作价格明细
  • 个人网站有什么用php网站数据库怎么上传
  • 网站设计师的工作内容美食网站案例
  • 网站建设基本费用慈溪哪里有做网站
  • 网站建设毕业答辩ppt模板下载如何搭建网络论坛平台
  • 内蒙古电子商务网站做行业网站投入
  • 网做 网站有哪些辽宁省工程招投标信息网
  • 四川建设厅官方网站证书查询泰州市网站建设制作
  • 政务公开网站建设管理网站建设带后台
  • 营口 微网站建设wordpress怎么查看域名绑定
  • 广州网站建设 易点宠物医院网站开发
  • wordpress 下载站主题建设银行网上流览网站
  • 网站子目录绑定二级域名网页设计个人
  • 17网站一起做网店打不开做那种网站
  • 如何统计网站pv买了个网站后怎么做的
  • 网站seo策划wordpress页脚设计
  • 网站管理平台扩展插件建设网站需要多少费用
  • 天河区门户网站教育专栏茶百道加盟费大概要多少
  • 博湖网站建设丽水网站建设明恩玉杰
  • 美食网站建设书创新作品及其设计方案
  • fedora做网站服务器营销网络遍布全国
  • 公司网站百度搜索的描述怎么做做网站一般需要多久
  • 网站开发 php模板做网站有2个前提条件 一个是网站
  • 网站开发得多长时间wordpress 什么是插件吗
  • wordpress 视频站网站客户端怎么做的
  • 设计师网站库dw做了网站还可以做淘宝详情吗