赞
踩
apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的
API,可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Apache Shiro 的首要目标是易于使用和理解。安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架 应该尽可能掩盖复杂的地方,露出一个干净而直观的 API,来简化开发人员在使他们的应用程序安全上的努力。以 下是可以用 Apache Shiro 所做的事情:
这张图真是在网上的博客中到处都是。没啥意思。主要的几个模块功能如下:
肯定的,上面的这些看完肯定是不知道说了啥。来,让我挑几个,按照我的理解来说一下吧
用户账号密码
是否正确。判断用户的角色和权限的
。分布式的情况下,就会有一个共享的session。Shiro支持将sessin放在 mysql,memcached,redis等,这些数据库中。一般来说主要是redis中吧,还有session的生命周期
。将用户的密码加密放在数据库中去
。这个可以用,也可以不用。如果不用就自己手动加密。保存就好,在Authentication 中,将用户传来的密码又手动加密一下,在和数据库里面存储的做对比。
图一
Shiro的架构有3个主要的概念:Subject
,SecurityManager
和Realms
按照上面图上的英语先自己看一下,具体的解析在下面。稍等
也就是当前登录的用户,通过这个对象就能获得角色和权限,所以说这个对象就是角色和权限的一个实体
管理所有的object,也就是管理所有的角色和权限,那和这些相关的它都能管理到。这就是一个核心
看密码和账号是不是正确的
找用户的角色和权限
这里就是写怎么怎么在数据库中查找用户角色和权限的。
按照 图一 的描述
一个最简单的一个Shiro应用:
从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
这个案例就是一个简单的入门案例,和Spring的整合在下面的章节
<dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <!--添加log4j2相关jar包--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.7</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.7</version> </dependency>
# ============================================================================= #Tutorial INI configuration # #Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :) #============================================================================= #----------------------------------------------------------------------------- #Users and their (optional) assigned roles #username = password, role1, role2, ..., roleN #----------------------------------------------------------------------------- [users] #数据格式 用户名=密码,角色1,角色2…… root = secret, admin guest = guest, guest presidentskroob = 12345, president darkhelmet = ludicrousspeed, darklord, schwartz lonestarr = vespa, goodguy, schwartz
package com.liuchen; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; /** * @program: shrio * @description: 原生test * @author: lc * @date: 2020/6/26 **/ public class NativeTest { /** * 这里的值就是在 shiro.ini 配置的值 */ public static String userName = "root"; /** * 这里的值就是在 shiro.ini 配置的值 */ public static String password = "secret"; public static void main(String[] args) { Logger logger = LogManager.getLogger(NativeTest.class); //1.加载ini配置文件创建SecurityManager工厂类 IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2.获取securityManager SecurityManager securityManager = factory.getInstance(); //3.将securityManager绑定到当前运行环境 SecurityUtils.setSecurityManager(securityManager); //4.创建主体(此时的主体还为经过认证) Subject currentUser = SecurityUtils.getSubject(); /** * 这里啊 就假设数据是从前端传过来的 用户名和密码了 */ //判断当前用户是否登录过。也就是是否通过了 Authentication 认证了。 if (!currentUser.isAuthenticated()) { try { logger.info("用户开始登录:{}", userName); //5.构造主体登录的凭证(即用户名/密码) //第一个参数:登录用户名,第二个参数:登录密码 UsernamePasswordToken token = new UsernamePasswordToken(userName, password); //6.主体登录 currentUser.login(token); //7.验证是否登录成功 logger.info("用户登录成功= {}", currentUser.isAuthenticated()); // System.out.println("用户登录成功=" + currentUser.isAuthenticated()); //getPrincipal 获取登录成功的安全数据 System.out.println(currentUser.getPrincipal()); //登录尝试失败,可以捕获各种特定的异常,这些异常可以准确地告诉发生了什么,并允许进行相应的处理和响应: //在这里 可以根据业务需求来匹配。详情请看 http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/AuthenticationException.html } catch (UnknownAccountException uae) { //用户名不存在 logger.info("用户不存在:{}", userName); logger.info(uae.getMessage()); } catch (IncorrectCredentialsException ice) { //密码不匹配 logger.info("密码错误:{}", userName); logger.info(ice.getMessage()); } catch (LockedAccountException lae) { //账户或者密码锁定 logger.info("账号锁定:{}", userName); logger.info(lae.getMessage()); } catch (AuthenticationException ae) { logger.info("未通过授权:{}", userName); logger.info(ae.getMessage()); } } } }
如果将userName换掉,就会抛出异常,程序会走到 UnknownAccountException cath块里面。别的异常在之后慢慢的尝试吧
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
# 模拟从数据库查询的角色和权限列表 #数据格式 角色名=权限1,权限2
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5
看代码
package com.liuchen; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; /** * @program: shrio * @description: 原生test * @author: lc * @date: 2020/6/26 **/ public class NativeTest { /** * 这里的值就是在 shiro.ini 配置的值 */ public static String userName = "darkhelmet"; /** * 这里的值就是在 shiro.ini 配置的值 */ public static String password = "ludicrousspeed"; public static void main(String[] args) { Logger logger = LogManager.getLogger(NativeTest.class); //1.加载ini配置文件创建SecurityManager工厂类 IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2.获取securityManager SecurityManager securityManager = factory.getInstance(); //3.将securityManager绑定到当前运行环境 SecurityUtils.setSecurityManager(securityManager); //4.创建主体(此时的主体还为经过认证) Subject currentUser = SecurityUtils.getSubject(); /** * 这里啊 就假设数据是从前端传过来的 用户名和密码了 */ //判断当前用户是否登录过。也就是是否通过了 Authentication 认证了。 if (!currentUser.isAuthenticated()) { try { logger.info("用户开始登录:{}", userName); //5.构造主体登录的凭证(即用户名/密码) //第一个参数:登录用户名,第二个参数:登录密码 UsernamePasswordToken token = new UsernamePasswordToken(userName, password); //6.主体登录 currentUser.login(token); //7.验证是否登录成功 logger.info("用户登录成功= {}", currentUser.isAuthenticated()); // System.out.println("用户登录成功=" + currentUser.isAuthenticated()); //getPrincipal 获取登录成功的安全数据 System.out.println(currentUser.getPrincipal()); //8.用户认证成功之后才可以完成授权工作 /******************************************************* 用户授权判断 ************************************************************ */ logger.info("{}是否有lightsaber:save的权限:{}",userName,currentUser.isPermitted("lightsaber:save")); //登录尝试失败,可以捕获各种特定的异常,这些异常可以准确地告诉发生了什么,并允许进行相应的处理和响应: //在这里 可以根据业务需求来匹配。详情请看 http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/AuthenticationException.html } catch (UnknownAccountException uae) { //用户名不存在 logger.info("用户不存在:{}", userName); logger.info(uae.getMessage()); } catch (IncorrectCredentialsException ice) { //密码不匹配 logger.info("密码错误:{}", userName); logger.info(ice.getMessage()); } catch (LockedAccountException lae) { //账户或者密码锁定 logger.info("账号锁定:{}", userName); logger.info(lae.getMessage()); } catch (AuthenticationException ae) { logger.info("未通过授权:{}", userName); logger.info(ae.getMessage()); } } } }
这个和上面的差别就是多了一行
logger.info("{}是否有lightsaber:save的权限:{}",userName,currentUser.isPermitted("lightsaber:save"));
用户判断用户有没有这个权限。关于shiro.ini 这个文件的详细的配置请看这篇文章
当然在实际中,没有人会直接在ini文件中直接写死权限的配置的。还记得上面说的Realm么?,实际中,权限和角色的数据都是从这个里面通过查找数据库找的
Realm域:Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么 它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行 验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源
自定义realm,需要继承AuthorizingRealm父类
重写父类中的两个方法
- doGetAuthorizationInfo :授权
- doGetAuthenticationInfo :认证
自定义的realm
package com.liuchen; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.ArrayList; import java.util.List; /** * @program: shrio * @description: 自定义Realm * @author: lc * @date: 2020/6/26 **/ public class MyPermissionRealm extends AuthorizingRealm { Logger logger = LogManager.getLogger(NativeTest.class); /** * 给这个realm给个名字 * * @param name */ @Override public void setName(String name) { super.setName("MyPermissionRealm"); } /** * 授权:授权的主要目的就是查询数据库获取用户的所有角色和权限信息 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 1.从principals获取已认证用户的信息 String username = (String) principalCollection.getPrimaryPrincipal(); /** * 正式系统:应该从数据库中根据用户名或者id查询 * 这里为了方便演示,手动构造 * 实际中,这里就是显示的是配置的权限,一般都是页面的路由地址和请求的url */ // 2.模拟从数据库中查询的用户所有权限 List<String> permissions = new ArrayList<String>(); permissions.add("user:save"); permissions.add("user:delete"); permissions.add("user:update");// 商品更新权限 // 3.模拟从数据库中查询的用户所有角色 /** * 实际中这些个角色可能就是 部门经理啊,人事啊。这些角色. */ List<String> roles = new ArrayList<String>(); roles.add("role1"); roles.add("role2"); // 4.构造权限数据 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // 5.将查询的权限数据保存到 simpleAuthorizationInfo simpleAuthorizationInfo.addStringPermissions(permissions); // 6.将查询的角色数据保存到simpleAuthorizationInfo simpleAuthorizationInfo.addRoles(roles); return simpleAuthorizationInfo; } /** * 认证:认证的主要目的,比较用户输入的用户名密码是否和数据库中的一致 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken; String username = usernamePasswordToken.getUsername(); String password = new String(usernamePasswordToken.getPassword()); /** * 实际来说,这里都是从数据库中查找数据的. 这里我就模拟一下就好了 */ if ("admin".equals(username) && "admin".equals(password)) { logger.info("{} 登录成功:", username); return new SimpleAuthenticationInfo(username, password, this.getName()); } else { throw new RuntimeException("用户名或者密码错误"); } } }
# ============================================================================= # Tutorial INI configuration # # Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :) # ============================================================================= # 注意:注意: # 开头表示注释哦. # ----------------------------------------------------------------------------- # Users and their (optional) assigned roles # username = password, role1, role2, ..., roleN # ----------------------------------------------------------------------------- # [users] # 数据格式 用户名=密码,角色1,角色2…… # root = secret, admin # guest = guest, guest # presidentskroob = 12345, president # darkhelmet = ludicrousspeed, darklord, schwartz # lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # roleName = perm1, perm2, ..., permN # ----------------------------------------------------------------------------- # 模拟从数据库查询的角色和权限列表 #数据格式 角色名=权限1,权限2 # [roles] # admin = * # schwartz = lightsaber:* # goodguy = winnebago:drive:eagle5 [main] # 声明Realm域 名字=全限定类名 这个名字是随便起的。 permReam=com.liuchen.MyPermissionRealm #注册realm到securityManager中 securityManager.realms=$上面自己起的名字。注意。等号左边的不能动,这个就相当于Spring配置文件中的属性注入. securityManager.realms=$permReam
这个还是之前的代码,修改了用户名和密码,下面是修改的代码
下面是完整的代码
package com.liuchen; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; /** * @program: shrio * @description: 原生test * @author: lc * @date: 2020/6/26 **/ public class NativeTest { /** * 这里的值就是在 shiro.ini 配置的值 */ public static String userName = "admin"; /** * 这里的值就是在 shiro.ini 配置的值 */ public static String password = "admin"; public static void main(String[] args) { Logger logger = LogManager.getLogger(NativeTest.class); //1.加载ini配置文件创建SecurityManager工厂类 IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2.获取securityManager SecurityManager securityManager = factory.getInstance(); //3.将securityManager绑定到当前运行环境 SecurityUtils.setSecurityManager(securityManager); //4.创建主体(此时的主体还为经过认证) Subject currentUser = SecurityUtils.getSubject(); /** * 这里啊 就假设数据是从前端传过来的 用户名和密码了 */ //判断当前用户是否登录过。也就是是否通过了 Authentication 认证了。 if (!currentUser.isAuthenticated()) { try { logger.info("用户开始登录:{}", userName); //5.构造主体登录的凭证(即用户名/密码) //第一个参数:登录用户名,第二个参数:登录密码 UsernamePasswordToken token = new UsernamePasswordToken(userName, password); //6.主体登录 currentUser.login(token); //7.验证是否登录成功 logger.info("用户登录成功= {}", currentUser.isAuthenticated()); // System.out.println("用户登录成功=" + currentUser.isAuthenticated()); //getPrincipal 获取登录成功的安全数据 System.out.println(currentUser.getPrincipal()); //7.用户认证成功之后才可以完成授权工作 logger.info("{}是否有user:save的权限:{}",userName,currentUser.isPermitted("user:save")); logger.info("{}是否有role1的权限:{}",userName,currentUser.hasRole("role1")); //登录尝试失败,可以捕获各种特定的异常,这些异常可以准确地告诉发生了什么,并允许进行相应的处理和响应: //在这里 可以根据业务需求来匹配。详情请看 http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/AuthenticationException.html } catch (UnknownAccountException uae) { //用户名不存在 logger.info("用户不存在:{}", userName); logger.info(uae.getMessage()); } catch (IncorrectCredentialsException ice) { //密码不匹配 logger.info("密码错误:{}", userName); logger.info(ice.getMessage()); } catch (LockedAccountException lae) { //账户或者密码锁定 logger.info("账号锁定:{}", userName); logger.info(lae.getMessage()); } catch (AuthenticationException ae) { logger.info("未通过授权:{}", userName); logger.info(ae.getMessage()); } } } }
哦。对了,我里面的日志用户的log4j2,下面是它的配置文件 lo4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
差点也忘了,贴出我的 github 上的demo,这个里面还有 jwt的简单使用,jwt我就没这么系统的阐述了,readme.md上面记录了一点我的思路。https://github.com/daliuchen/shrio.git
下一篇是SpringBoot和shiro的整合.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。