栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Shiro框架-史上详解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Shiro框架-史上详解

Shiro

1.权限管理概述

2.Shiro权限框架
2.1 概念
2.2 Apache Shiro 与Spring Security区别

3.Shiro认证
3.1 基于ini认证
3.2 自定义Realm --认证

4.Shiro授权
4.1 基于ini授权
4.2 自定义realm – 授权

5.项目集成shiro 认证-授权注意点
5.1 认证
5.2 授权
5.3 注解@RequiresPermissions()
5.4 标签式权限验证

6.Shiro加密

7.Shiro缓存

权限管理概述


RBAC(Role Based Access Control) :某个用户拥有什么角色,被允许做什么事情(权限)

用户登录—>分配角色---->(权限关联映射)---->鉴权(拥有什么什么权限)

熟悉框架流程
  • 概念 能做什么
  • 架构是怎样
  • 代码怎么实现
  • 项目运用

Shiro权限框架


  • 概念: Apache Shiro 是一个强大且易用的 Java 安全框架
  • 能做什么:Shiro可以帮我们完成 :认证、授权、加密、会话管理、与 Web 集成、缓存等。
  • 架构是怎样的

主要认识:

  • Subject(用户):当前的操作用户 获取当前用户Subject currentUser = SecurityUtils.getSubject()
  • SecurityManager(安全管理器):Shiro的核心,负责与其他组件进行交互,实现 subject 委托的各种功能
  • Realms(数据源) :Realm会查找相关数据源,充当与安全管理间的桥梁,经过Realm找到数据源进行认证,授权等操作
  • Authenticator(认证器): 用于认证,从 Realm 数据源取得数据之后进行执行认证流程处理。
  • Authorizer(授权器):用户访问控制授权,决定用户是否拥有执行指定操作的权限。
  • SessionManager (会话管理器):支持会话管理
  • CacheManager (缓存管理器):用于缓存认证授权信息
  • Cryptography(加密组件):提供了加密解密的工具包

Apache Shiro 与Spring Security区别

Shiro::用于中小型项目比较常见,简单易上手,可以支持多种环境
       Shiro 可以不跟任何的框架或者容器绑定,可独立运行
Spring Security:一般多用于spring环境,中大型项目,更强大
                Spring Security 则必须要有Spring环境

Shiro认证


基于ini认证

1.新建项目 --导入依赖
           --新建 配置文件ini
    
2. shiro帮我们创建用户
    创建令牌 ,类比页面传入的账号密码
    密码错误--  IncorrectCredentialsException
    账号错误--  UnknownAccountException
    
   源码分析--

  • 导入依赖

    commons-logging commons-logging 1.1.3 org.apache.shiro shiro-core 1.5.2 junit junit 4.13.2 org.projectlombok lombok 1.16.22 provided
  • 编写ini,shiro默认支持的是ini配置的方式(只是演示) shiro-au.ini,真实项目使用xml

    #用户的身份、凭据
    [users]
    zhangsan=555
    xiaoluo=666

  • 使用 Shiro 相关的 API 完成身份认证

    @Test
    public void testLogin(){
    //创建Shiro的安全管理器,是shiro的核心
    DefaultSecurityManager securityManager = new DefaultSecurityManager();
    //加载shiro.ini配置,得到配置中的用户信息(账号+密码)
    IniRealm iniRealm = new IniRealm(“classpath:shiro-au.ini”);
    securityManager.setRealm(iniRealm);
    //把安全管理器注入到当前的环境中
    SecurityUtils.setSecurityManager(securityManager);
    //无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
    Subject subject = SecurityUtils.getSubject();
    System.out.println(“认证状态:”+subject.isAuthenticated());
    //创建令牌(携带登录用户的账号和密码)
    UsernamePasswordToken token = new UsernamePasswordToken(“xiaoluo”,“666”);
    //执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
    subject.login(token);
    System.out.println(“认证状态:”+subject.isAuthenticated());
    //登出
    //subject.logout();
    //System.out.println(“认证状态:”+subject.isAuthenticated());
    }

自定义Realm–认证

模拟

@Getter
@Setter
public class Employee {
    private String username;
    private String password;}

思路:

//继承 AuthorizingRealm
//参数的token ,subject进行登录匹配传入的token : UsernamePasswordToken
1.获取用户名
    方式一:将token强转为UsernamePasswordToken-->getUsername()
    方式二:通过token.getPrincipal()    
    
2.以用户名为条件,查询mysql数据库,得到用户对象(用户名/密码)
        模拟从数据库查询的对象 ------->自己new一个
        
3.将用户对象封装成认证info对象返回
        //存在两种可能
        1.用户对象为null-----> return null
        2.不为空 ---->封装数据 return new SimpleAuthenticationInfo(
              有3个参数 
               1.employee ,  //从数据库查询出来需要进行密码匹配的用户对象
               2.employee.getPassword(),  //匹配对象密码
               3.super.getName()  //指定realm的名称 ,可自定义
    )

实现:

public class Realm extends AuthorizingRealm {
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.获取登录用户名
        String username = (String) token.getPrincipal();
        //2.以用户名为条件查询mysql数据库,得到用户对象
        //模拟数据
        Employee employee = new Employee();
        employee.setUsername("xiaoluo");
        employee.setPassword("123");

        //封装成一个认证info对象
        //判断用户是否为空
        if (employee!=null){
            return new SimpleAuthenticationInfo(
                    employee,
                    employee.getPassword(),
                    super.getName()
            );
        }
        return null;
    }
}


@Test
public void testLogin(){
    //创建Shiro的安全管理器,是shiro的核心
    DefaultSecurityManager securityManager = new DefaultSecurityManager();
  
        //自定义Realm,查出用户信息
        Realm realm = new Realm();
        securityManager.setRealm(realm);
    
    //把安全管理器注入到当前的环境中
    SecurityUtils.setSecurityManager(securityManager);
    //无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
    Subject subject = SecurityUtils.getSubject();
    System.out.println("认证状态:"+subject.isAuthenticated());
    //创建令牌(携带登录用户的账号和密码)
    UsernamePasswordToken token = new UsernamePasswordToken("xiaoluo","123");
    //执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
    subject.login(token);
    System.out.println("认证状态:"+subject.isAuthenticated());
    //登出
    //subject.logout();
    //System.out.println("认证状态:"+subject.isAuthenticated());
}

Shiro授权


基于ini授权

  • 登录

  • 分配

  • 鉴权

    权限表达式   资源:操作
    admin=*:*  //超级管理员
    emp=employee:*
    

    //判断是否有某个角色
    subject.hasRole(“role”);
    //用户拥有所有指定角色返回true
    subject.hasAllRoles(Arrays.aList(“role1”,“role2”));
    //判断用户是否有指定角色,将结果返回,封装到boolean数组中
    boolean[] booleans=subject.hasRoles(Arrays.aList(“role1”,“role2”));
    //check开头的是没有返回值,当没有权限时就会抛出异常
    subject.checkRole(“role”);

    //判断用户是否有某个权限
    subject.isPermitted(“权限表达式”)
    boolean[] booleans=subject.isPermitted(“权限表达式1”,“权限表达式2”)

自定义realm — 授权

思路:

1.获取当前登录用户的id/name
    //获取当前登录用户对象 ,登录传过来的第一个参数
    方式一 :principals.getPrimaryPrincipal();
    方式二:SecurityUtils.getSubject().getPrincipal();

2.以用户id作为条件查询mysql数据,查询该用户拥有角色/权限
    //假装查数据
    //List roles= roleService.queryByEmployee(Employee.getId());
   //List    //List roles= permissionService.queryByEmployee(Employee.getId());
= permissionService.queryByEmployee(Employee.getId());
    
    
3.将角色与权限封装到授权info对象中 
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addRoles(roles) //添加角色
    info.addStringPermission(permission);//添加权限    

实现:

 //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取当前用户对象
        Employee employee = (Employee)principalCollection.getPrimaryPrincipal();

        //以用户id查询数据库,判断该角色/用户是否拥有权限  模拟
        List roles= Arrays.asList("seller");
        List permissions= Arrays.asList("customer:list","customer:save");

        //封装到info对象
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(roles);
        info.addStringPermissions(permissions);

        return info;
    }


public class ShiroDemo {
    @Test
    public void testLogin(){
        //创建Shiro的安全管理器,是shiro的核心
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //加载shiro.ini配置,得到配置中的用户信息(账号+密码)
        IniRealm iniRealm = new IniRealm("classpath:shiro-author.ini");

        //自定义Realm,查出用户信息
        Realm realm = new Realm();
        securityManager.setRealm(realm);

        //把安全管理器注入到当前的环境中
        SecurityUtils.setSecurityManager(securityManager);
        //无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
        Subject subject = SecurityUtils.getSubject();

        System.out.println("认证状态:"+subject.isAuthenticated());

        //创建令牌(携带登录用户的账号和密码)
        UsernamePasswordToken token = new UsernamePasswordToken("xiaoluo","12");
        //执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
        subject.login(token);

        System.out.println("认证状态:"+subject.isAuthenticated());

        //登出
        //subject.logout();
        //System.out.println("认证状态:"+subject.isAuthenticated());

        //判断用户是否有某个角色
        System.out.println("hr:"+subject.hasRole("hr"));
        System.out.println("seller:"+subject.hasRole("seller"));

        //是否同时拥有多个角色
        System.out.println("是否同时拥有role1和role2:"+subject.hasAllRoles(Arrays.asList("hr", "seller")));
        boolean[] booleans = subject.hasRoles(Arrays.asList("hr", "seller"));
        //System.out.println(booleans);

        //check开头的是没有返回值的,当没有权限时就会抛出异常
        subject.checkRole("seller");

        //判断用户是否有某个权限
        System.out.println("user:delete:"+subject.isPermitted("user:delete"));
        subject.checkPermission("customer:list");
    }
}


	//如果用户是超级管理员 
    //设置超级管理员角色
   info.addRole("admin")
    //设置超级管理员权限
info.addStringPermission("*:*");

项目集成shiro 认证-授权注意点


认证

1.添加对应依赖
2.配置代理过滤器
   为什么不用配置拦截器?
   Shiro是选择使用filter过滤器来进行拦截的,因为Shiro不依赖Spring容器,所以当没有springmvc时意味着不能用拦截器,但过滤器则不同,只要是web项目都可以使用
3.创建shiro.xml
4.配置shiro过滤器
   
   
   
5.配置安全管理器  DefaultWebSecurityManager
6.修改LoginController
7.配置自定义Realm
8.将自定义Realm交给容器管理
**

shiro中的过滤器**

过滤器的名称

Java 类

anon

org.apache.shiro.web. lter.authc.AnonymousFilter

authc

org.apache.shiro.web. lter.authc.FormAuthenticationFilter

authcBasic

org.apache.shiro.web. lter.authc.BasicHttpAuthenticationFilter

roles

org.apache.shiro.web. lter.authz.RolesAuthorizationFilter

perms

org.apache.shiro.web. lter.authz.PermissionsAuthorizationFilter

user

org.apache.shiro.web. lter.authc.UserFilter

logout

org.apache.shiro.web. lter.authc.LogoutFilter

port

org.apache.shiro.web. lter.authz.PortFilter

rest

org.apache.shiro.web. lter.authz.HttpMethodPermissionFilter

ssl

org.apache.shiro.web. lter.authz.SslFilter

  • anon: 匿名处理过滤器,即不需要登录即可访问;一般用于静态资源过滤;/static/**=anon
  • authc: 表示需要认证(登录)才能使用;(放最后) /**=authc
  • logout: 注销过滤器 /logout=logout
  • roles: 角色授权过滤器,验证用户是否拥有资源角色; /employee/input=perms["user:update"]

授权

  • 没有权限的异常 :org.apache.shiro.authz.UnauthorizedException

    shiro注解鉴权操作方式:
    类比RBAC:
    1.自定义权限注解
    2.将注解贴在请求映射方法上面
    3.将注解标注的权限表达式加载到数据库中
    4.将这些表达式根据用户角色进行权限分配
    5.当用户登录之后,访问某个请求映射方法时,先经过权限拦截器,进行鉴权操作
    1.获取当前登录用户权限表达式集合
    2.获取当前请求映射方法头顶上权限表达式
    3.判断用户权限表达式集合中是否包含该表达式

Shiro 权限验证三种方式

  • 编程式

  • 注解式

  • 页面标签式

    1.编程式
    Subject subject = SecurityUtils.getSubject();
    if(subject.hasRole(“hr”)) {
    //有权限
    } else {
    //无权限
    } 2.注解式
    @RequiresRoles(“admin”)
    @RequiresPermissions(“user:create”)
    public void hello() {
    //有权限
    }

    3.页面标签式
    <@shiro.hasPermission name=“employee:list”>

    /@shiro.hasRole

授权步骤

1.贴注解
2.开启 Shiro 注解扫描器
3.查询数据库真实数据 自定义realm    

注解@RequiresPermissions()

  • value属性: 这个属性是一个数组

  • Logical.AND: 必须同时拥有value配置所有权限才允许访问

  • Logical.OR:只需要拥有value配置所有权限中一个即可允许访问

    约定:权限表达式,第一值权限表达式,第二值为权限名称
    @RequiresPermissions(value={“employee:list”,“员工列表”},logical=Logical.OR)
    逻辑为OR,value满足其中一个条件成立

标签式权限验证

**
拓展FreeMarker标签**

默认的freemarker是不支持shiro标签,所以需要做功能拓展
 ----------------   
  public class ShiroFreeMarkerConfig extends FreeMarkerConfigurer {
    @Override
    public void afterPropertiesSet() throws IOException, TemplateException {
        //继承之前的属性配置,这不能省
        super.afterPropertiesSet();
        Configuration cfg = this.getConfiguration();
        cfg.setSharedVariable("shiro", new ShiroTags());//注册shiro 标签
    }
}  
--------------------------让之前的配置文件
    中的FreeMarkerConfigurer修改为自定义的ShiroFreeMarkerConfig类
    
    
        
        
        
        
        
            
                
                true
            
        
    
使用shiro标签
  • authenticated 标签:已认证通过的用户。

    <@shiro.authenticated> /@shiro.authenticated

  • notAuthenticated 标签:未认证通过的用户。

    <@shiro.notAuthenticated>/@shiro.notAuthenticated

  • principal 标签 :输出当前用户信息,通常为登录帐号信息

    <@shiro.principal property=“name” /> //对应name属性

  • hasRole 标签:验证当前用户是否拥有该角色

    <@shiro.hasRole name=“admin”>我是管理员/@shiro.hasRole

  • hasAnyRoles 标签:验证当前用户是否拥有这些角色中的任何一个,角色之间逗号分隔

    <@shiro.hasAnyRoles name=“admin,user,hr”>Hello admin/@shiro.hasAnyRoles

  • hasPermission 标签:验证当前用户是否拥有该权限

    <@shiro.hasPermission name=“department:delete”>删除/@shiro.hasPermission

Shiro加密


MD5
//参数1 :原文,参数2:盐 ,参数3:散列次数(加密次数)
Md5Hash hash=new Md5Hash("1","kent",3);

盐一般要求是固定长度的字符串,且每个用户的盐不同。

一般盐的选择的是用户的唯一数据(账号名等),盐是要求不能改变的,不然下次加密结果就对应不上了

Shiro缓存


当我们登录时,授权信息是要从数据库中查询的,如果每次刷新刷新都需要获取你到底有没有权限,对性能影响不好,用户登录后,授权信息一般很少改动,所以,我们可以将第一次授权后,将信息存在缓存中,下次直接再缓存中获取,就很好的避免了多次访问数据库

Shiro没有实现自己的缓存机制,只提供了支持缓存的API接口,这里使用的是EhCache

mybatis 一级  二级缓存------百度(ing)补充

集成EhCache

第三方EhCache
  1.配置缓存管理器并引用缓存管理器
    

    
    
    
    



 
    
    

    ----------------------------------------
    2.添加缓存配置文件  shiro-ehcache.xml
    
    
    

配置缓存文件属性说明
  • maxElementsInMemory: 缓存对象最大个数。
  • eternal:对象是否永久有效 ,一般为false
  • timeToIdleSeconds: 对象空闲时间
  • timeToLiveSeconds:对象存活时间
  • memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。
  • LFU(较少使用,意思是一直以来最少被使用的,缓存的元素有一个hit 属性(命中率),hit 值最小的将会被清出缓存)默认

拓展 统一全局异常

@ControllerAdvice 控制器功能增强注解

1.在进入请求映射方法之前做功能增强,经典用法:date日期格式化
2.在进入请求映射方法之后做功能增强,经典用法:统一异常处理 

3.处理异常的方法,方法需要贴ExceptionHandler注解
配置文件 配置代理过滤器

    shiroFilter
    
        org.springframework.web.filter.DelegatingFilterProxy
    


    shiroFilter
    /*

配置shiro.xml



    
    
    

    
    
        
        
        
    

    
    
        
        
        
        
        
        
            
                /userLogin=anon
                /css/**=anon
                /js/**=anon
                /img/**=anon
                /upload/**=anon
                /userLogout=logout
                /**=authc
            
        
    


    
    
    
    
    
        
    


    
    
        
        
    


关联mvc.xml

loginController
@RequestMapping("/userLogin")
    @ResponseBody
    public JsonResult login(String username, String password){
        try {
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            SecurityUtils.getSubject().login(token);
            return new JsonResult();
        } catch (UnknownAccountException e) {
            return new JsonResult(false, "账号不存在");
        } catch (IncorrectCredentialsException e) {
            return new JsonResult(false, "密码错误");
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResult(false, "登录异常,请联系管理员");
        }
    }
关联mvc.xml

loginController
@RequestMapping("/userLogin")
    @ResponseBody
    public JsonResult login(String username, String password){
        try {
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            SecurityUtils.getSubject().login(token);
            return new JsonResult();
        } catch (UnknownAccountException e) {
            return new JsonResult(false, "账号不存在");
        } catch (IncorrectCredentialsException e) {
            return new JsonResult(false, "密码错误");
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResult(false, "登录异常,请联系管理员");
        }
    }

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1032436.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号