一, Shiro 介绍
1, 可从本教程中明白以下几个知识点:
认识 Shiro 的整体架构和各组件的概念;
Shiro 认证, 授权过程;
Shiro 自定义 Realm.
2,Shiro 简介
Shiro 是 Apache 强大灵活的开源的安全框架, 有认证, 授权, 企业会话管理, 安全加密, 缓存管理等功能.
Shiro 和 Spring Security 相比较; Shiro 更加简单方便, 并且可脱离 Spring,Spring Security 比较笨重复杂, 不可脱离 Spring.
3,Shiro 架构图
详细图如上面所示: 在这里就不介绍具体每个组件了, 我会在以下 4 个实例代码中详细说明;
4, 项目中用到的依赖:
添加完依赖, 以下所有实例都可以直接运行.
二, 第一个实例(初识 Shiro 认证 / 授权)
1,Shiro 认证 / 授权过程: 代码有详解
认证原理: 创建 SecurityManager---->主体提交认证请求 ----->提交到 SecurityManager 认证 ---->SecurityManager 认证是由 Authenticator 进行认证 ---->Authenticator 认证需要通过 Realm 验证用户数据.
授权原理: 创建 SecurityManager---->主体授权 ---->提交到 SecurityManager 授权 ---->SecurityManager 授权是有 Authorizer 进行授权 ---->Realm 获取角色权限数据
- public class AuthenticationTest {
- SimpleAccountRealm simpleAccountRealm=new SimpleAccountRealm();
- @Before
- public void getAccount()
- {
- // 方便测试 新增用户和角色权限
- simpleAccountRealm.addAccount("quentin","123456","admin");
- }
- @Test
- public void AuthenticationTest()
- {
- //1, 创建 SecurtyManager
- DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
- defaultSecurityManager.setRealm(simpleAccountRealm);
- //2, 主体认证 / 授权
- SecurityUtils.setSecurityManager(defaultSecurityManager);
- Subject subject=SecurityUtils.getSubject();// 获得当前正在执行程序的用户
- UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456");
- //3,login()主体认证请求, 里面封装好的不用管, 它是由 SecurityManager 认证 ->Authenticate 认证 ->Realm 验证组成, 这样就可以实现认证.
- subject.login(usernamePasswordToken);
- System.out.println("isAuthen:"+subject.isAuthenticated());
- //3,checkRole()主体授权请求, 里面封装好的不用管, 它是由 SecurityManager 授权 ->Authenticate 授权 ->Realm 获取角色权限组成, 这样就可以实现授权功能.
- subject.checkRole("admin");
- }
- }
运行结果:
当验证不通过会抛出异常, 没有权限也会抛出异常!!
所以, 总结一下整个流程, 以认证为例: 就是在我们调用 Subject 的 login()方法之后, 可以看到我传入的是用户的 token(里面的实际原理不用管, 其实就是第一第二步它经历过一系列的步骤, 它调用了 Realm 中的 doGetAuthenticationInfo(token)方法).
三, 第二个实例(IniRealm 实例讲解)
介绍: 通过加载. INI 文件生成 realm 对象来验证
1, 在 resourses 中建立 user.INI 文件, 内容如下:
- [users]
- quentin=123456,admin
- [roles]
- admin=user:delete,user:update
设置用户名 "quentin", 密码是 "123456","admin" 角色,"admin" 拥有 "user:delete,user:update" 权限.
2, 实例文件代码如下:
- public class IniRealmTest {
- @Test
- public void IniRealmTest()
- {
- IniRealm iniRealm=new IniRealm("classpath:user.ini"); // 配置 user.INI 文件(账号和权限等信息)
- //1, 创建 SecurtyManager
- DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
- defaultSecurityManager.setRealm(iniRealm);// 设置 Realm
- //2, 主体认证 / 授权请求
- SecurityUtils.setSecurityManager(defaultSecurityManager);
- Subject subject=SecurityUtils.getSubject();
- UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456");
- subject.login(usernamePasswordToken);
- System.out.println("isAuth:"+subject.isAuthenticated());// 是否认证
- // 判断是否授权等
- subject.checkRole("admin");// 是否有角色权限
- subject.checkPermission("user:delete");// 是否有删除权限
- }
- }
运行结果:
说明认证通过(用户名密码正确), 但是跑出没有权限异常(因为 admin 没有 user:deleteall 全选).
四, 第三个实例(JdbcRealm 实例讲解)
介绍: 通过获取数据库用户数据来验证
1, 实例代码如下:
- public class JdbcRealmTest {
- DruidDataSource dataSource=new DruidDataSource();
- {
- dataSource.setUrl("jdbc:mysql://10.0.92.23:3306/test");
- dataSource.setUsername("root");
- dataSource.setPassword("123");
- }
- @Test
- public void JdbcRealmTest()
- {
- JdbcRealm jdbcRealm=new JdbcRealm();
- jdbcRealm.setDataSource(dataSource);
- String sql="select password from user where name=?";
- jdbcRealm.setAuthenticationQuery(sql); // 查询认证语句
- String sqlrole="select rolename from role where name=?";
- jdbcRealm.setUserRolesQuery(sqlrole); // 查询角色权限语句
- //1, 创建 SecurtyManager
- DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
- defaultSecurityManager.setRealm(jdbcRealm);
- //2, 主体认证 / 授权请求
- SecurityUtils.setSecurityManager(defaultSecurityManager);
- Subject subject=SecurityUtils.getSubject();
- UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("aaa","dfdf");
- subject.login(usernamePasswordToken);
- System.out.println("isAuth:"+subject.isAuthenticated());// 判断是有认证
- subject.checkRole("admin");// 是否有权限
- }
- }
运行结果也一样就不演示了. 其实 JdbcRealm 里面也提供了默认的 sql 语句, 但是考虑到查询不一样, 所以需要单独编写.
五, 第四个实例(自定义 Realm 及 Shiro 加密)
1, 自定义 Realm, 分为 doGetAuthenticationInfo()认证 和 doGetAuthorizationInfo()授权信息两部分, 注意代码注释:
代码如下:
- // 自定义 Realm, 代码如下:
- public class CustomerRealm extends AuthorizingRealm {
- // 继承父类 AuthorizingRealm 将获取 Subject 相关信息分成两步: 获取身份验证信息 (doGetAuthenticationInfo) 及授权信息(doGetAuthorizationInfo);
- // 原理!!!!!!!!!
- /* doGetAuthenticationInfo 获取身份验证相关信息: 首先根据传入的用户名获取 User 信息;
- * 然后如果 user 为空, 那么抛出没找到帐号异常 UnknownAccountException; 如果 user 找到但锁定了抛出锁定异常 LockedAccountException;
- * 最后生成 AuthenticationInfo 信息, 交给间接父类 AuthenticatingRealm 使用 CredentialsMatcher 进行判断密码是否匹配, 如果不匹配将抛出密码错误异常 IncorrectCredentialsException;
- * 另外如果密码重试此处太多将抛出超出重试次数异常 ExcessiveAttemptsException;
- * 在组装 SimpleAuthenticationInfo 信息时, 需要传入: 身份信息(用户名), 凭据(密文密码), 盐(username+salt),CredentialsMatcher 使用盐加密传入的明文密码和此处的密文密码进行匹配.
- * */
- @Override
- // 用于当前用户验证. 认证 login()方法执行会自动调用
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- //1, 从主体传过来的认证信息中, 获取用户名
- String UserName=(String)token.getPrincipal(); // getPrincipal(); 身份 | getCredentials(); 凭据
- //2, 通过用户名从数据库中获取密码凭证
- String Password=getPasswordByUsername(UserName);
- if(Password==null)
- {
- return null;
- }
- // 组装 SimpleAuthenticationInfo 信息,(用户名, 密文密码, 盐)
- SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo("quentin",Password,"customerReal");
- simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("salt")); // 将盐注册到信息中去
- return simpleAuthenticationInfo;
- }
- // 方便测试 设置用户数据
- Map<String,String> userMap=new HashMap<String, String>(16);
- {
- // 将带盐 "salt" 也一起加密
- Md5Hash passMD5=new Md5Hash("123456","salt");
- userMap.put("quentin",passMD5.toString());
- super.setName("customerReal");
- }
- private String getPasswordByUsername(String userName) {
- return userMap.get(userName);
- }
- //---------------------------------------------------------------- 以上是认证, 以下是授权 ------------------------------------------------------------------------------------------------------
- //doGetAuthorizationInfo 获取授权信息: PrincipalCollection 是一个身份集合, 因为我们现在就一个 Realm, 所以直接调用 getPrimaryPrincipal 得到之前传入的用户名即可;
- // 然后根据用户名调用 UserService 接口获取角色及权限信息.
- @Override
- // 用于当前登录用户授权, 用户的角色与权限初始化, 授权会自动调用
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- // 获取用户名
- String UserName=(String)principals.getPrimaryPrincipal(); //getPrimaryPrincipal()得到主要的身份 Set<String> getRealmNames(); // 获取所有身份验证通过的 Realm 名字
- // 获取角色和权限信息(一般从数据库或缓存中获取)
- Set<String> setRoles=getRolesByUserName();
- Set<String> setPression=getPressionByUserName();
- // 设置 SimpleAuthorizationInfo 信息,(角色和权限)
- SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
- simpleAuthorizationInfo.setRoles(setRoles);
- simpleAuthorizationInfo.setStringPermissions(setPression);
- return simpleAuthorizationInfo;
- }
- // 方便测试 设置用户权限数据
- private Set<String> getPressionByUserName() {
- Set<String> sets=new HashSet<String>();
- sets.add("user:add");
- sets.add("user:delete");
- sets.add("user:select");
- sets.add("admin:select");
- return sets;
- }
- // 方便测试 设置用户角色数据
- private Set<String> getRolesByUserName() {
- Set<String> sets=new HashSet<String>();
- sets.add("admin");
- sets.add("user");
- return sets;
- }
- }
2, 实例运行代码如下:
- public class CustomerRealmTest {
- @Test
- public void CustomerRealmTest()
- {
- // 引入自定义 Realm
- CustomerRealm customerRealm=new CustomerRealm();
- // 创建 DefaultSecurityManager
- DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
- defaultSecurityManager.setRealm(customerRealm);
- // 设置算法名称和加密次数
- HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
- matcher.setHashAlgorithmName("md5");
- matcher.setHashIterations(1);
- customerRealm.setCredentialsMatcher(matcher);
- // 主体认证, 得到 SecurityManager 实例 并绑定给 SecurityUtils
- SecurityUtils.setSecurityManager(defaultSecurityManager);
- // 得到 Subject 及创建用户名 / 密码身份验证 Token(即用户身份 / 凭证)
- Subject subject=SecurityUtils.getSubject();
- UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456");
- // 登录, 即身份验证
- subject.login(usernamePasswordToken);
- System.out.println("isAuth:"+subject.isAuthenticated());
- subject.checkRole("admin");
- subject.checkPermission("admin:select");
- }
- }
运行结果:
单独解释下 Shiro 加密:
在自定义 Realm 文件中
- Md5Hash passMD5=new Md5Hash("123456","salt");
- userMap.put("quentin",passMD5.toString());
- // 由上面代码也能看到解释, 其实就是将带盐 "salt" 也一起加密, 作为身份验证的密码信息
- +
- simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("salt")); // 将盐注册到信息中去
- // 将盐 "salt" 注册到信息中去验证
- =
这样就更加安全.
通过上面 4 个实例, 动手敲一敲, 相信你们都能快速了解 Shiro 认证授权原理了.
来源: https://www.cnblogs.com/qiujianfeng/p/10274161.html