IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Shiro框架在项目中的应用 -> 正文阅读

[系统运维]Shiro框架在项目中的应用

1、Shiro 框架简介

Shiro 概述
Shiro 是Apache公司推出一个权限管理框架,其内部封装了项目中认证,授权,加密,会话等逻辑操作,通过Shiro框架可以简化我们项目权限控制逻辑的代码的编写。其认证和授权业务分析,如图所示:
在这里插入图片描述
Shiro 框架概要架构
Shiro 框架中主要通过Subject,SecurityManager,Realm对象完整认证和授权业务,其简要架构如下:
在这里插入图片描述
其中:
Subject 此对象负责提交用户身份、权限等信息
SecurityManager 负责完成认证、授权等核心业务
Realm 负责通过数据逻辑对象获取数据库或文件中的数据。

Shiro 框架详细架构分析
Shiro 框架进行权限管理时,要涉及到的一些核心对象,主要包括:认证管理对象,授权管理对象,会话管理对象,缓存管理对象,加密管理对象以及 Realm 管理对象(领域对象:负责处理认证和授权领域的数据访问题)等,其具体架构如图- 所示:
在这里插入图片描述
其中:

  1. Subject(主体):与软件交互的一个特定的实体(用户、第三方服务等)。
  2. SecurityManager(安全管理器) :Shiro 的核心,用来协调管理组件工作。
  3. Authenticator(认证管理器):负责执行认证操作。
  4. Authorizer(授权管理器):负责授权检测。
  5. SessionManager(会话管理):负责创建并管理用户 Session 生命周期,提供一
    个强有力的 Session 体验。
  6. SessionDAO:代表 SessionManager 执行 Session 持久(CRUD)动作,它允
    许任何存储的数据挂接到 session 管理基础上。
  7. CacheManager(缓存管理器):提供创建缓存实例和管理缓存生命周期的功能。
  8. Cryptography(加密管理器):提供了加密方式的设计及管理。
  9. Realms(领域对象):是 shiro 和你的应用程序安全数据之间的桥梁。

2、Shiro 框架基础配置

Shiro 依赖
在项目中添加Shiro相关依赖(参考官网http://shiro.apache.org/spring-boot.html),假如项目中添加过shiro-spring依赖,将shiro-spring依赖替换掉即可。

<dependency> 
<groupId>org.apache.shiro</groupId> 
<artifactId>shiro-spring-boot-web-starter</artifactId> 
<version>1.7.0</version> 
</dependency>

说明,添加完此依赖,直接启动项目会启动失败,还需要额外的配置。

Shiro 基本配置
第一步:创建一个Realm类型的实现类(基于此类通过DAO访问数据库),关键代码如下:

package com.cy.pj.sys.service.realm; 
public class ShiroRealm extends AuthorizingRealm { 
/**此方法负责获取并封装授权信息*/ 
@Override 
protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principalCollection) { 
return null; 
}
/**此方法负责获取并封装认证信息*/ @Override 
protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authenticationToken) throws AuthenticationException {
 return null; 
 } 
 }

第二步:在项目启动类中添加Realm对象配置,关键代码如下:

@Bean 
public Realm realm(){//org.apache.shiro.realm.Realm 
return new ShiroRealm(); 
}

第三步:在启动类中定义过滤规则(哪些访问路径要进行认证才可以访问),关键代码如下:

@Bean
 public ShiroFilterChainDefinition shiroFilterChainDefinition() { 
 DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); 
 LinkedHashMap<String,String> map=new LinkedHashMap<>(); 
 //设置允许匿名访问的资源路径(不需要登录即可访问) 
 map.put("/bower_components/**","anon");//anon对应shiro中的一个匿名过滤器 
 map.put("/build/**","anon"); 
 map.put("/dist/**","anon"); 
 map.put("/plugins/**","anon"); 
 //设置需认证以后才可以访问的资源(注意这里的顺序,匿名访问资源放在上面) 
 map.put("/**","authc");//authc 对应一个认证过滤器,表示认证以后才可以访问 
 chainDefinition.addPathDefinitions(map); 
 return chainDefinition;
 }

第四步:配置认证页面(登录页面)
在spring的配置文件(application.yml)中,添加登录页面的配置,关键代码如下:

shiro: 
  loginUrl: /login.html

其中,login.html页面为项目中static目录定义好的一个页面。
在这里插入图片描述
第五步:启动服务进行访问测试
打开浏览器,输入http://localhost/doIndexUI检测是否会出现登录窗口,如图所示:
在这里插入图片描述

3、Shiro认证业务分析及实现

认证流程分析
当我们在登录页面,输入用户信息,提交到服务端进行认证,其中shiro框架的认证时序如图所示:
在这里插入图片描述
其中:

  1. token :封装用户提交的认证信息(例如用户名和密码)的一个对象。
  2. Subject: 负责将认证信息提交给SecurityManager对象的一个主体对象。
  3. SecurityManager是shiro框架的核心,负责完成其认证、授权等业务。
  4. Authenticator 认证管理器对象,SecurityManager继承了此接口。
  5. Realm 负责从数据库获取认证信息并交给认证管理器。

Shiro框架认证业务实现
第一步:在SysUserDao中定义基于用户名查询用户信息的方法,关键代码如下:

@Select("select * from sys_users where username=#{username}") 
SysUser findUserByUsername(String username);

第二步:修改ShiroRealm中获取认证信息的方法,关键代码如下:

/**此方法负责获取并封装认证信息*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1、获取用户提交提交的认证用户信息
        UsernamePasswordToken upToken=(UsernamePasswordToken) authenticationToken;
        String username = upToken.getUsername();
        //2、基于用户名查询从数据库用户信息
        SysUser sysUser = sysUserDao.findUserByUsername(username);
        //3、判断用户是否存在
        if (sysUser==null)
            throw new UnknownAccountException();//账户不存在
        //4、判断用户是否被禁用
        if (sysUser.getValid()==0)throw new LockedAccountException();
        //5、封装认证信息并返回
        ByteSource credentialsSalt=ByteSource.Util.bytes(sysUser.getSalt());
        SimpleAuthenticationInfo info=
                new SimpleAuthenticationInfo(
                        sysUser,//principal 传入的用户身份
                        sysUser.getPassword(),//hashedCredentials
                        credentialsSalt,//credentialsSalt
                        getName());
        return info;//返回给认证管理器
    }

第三步:在ShiroRealm中重谢获取凭证加密算法的方法,关键代码如下:

@Override
    public CredentialsMatcher getCredentialsMatcher() {
        HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");//加密算法
        matcher.setHashIterations(1);//加密次数
        return matcher;
    }

第四步:在SysUserController中添加处理登录请求的方法,关键代码如下:

@RequestMapping("doLogin")
    public JsonResult doLogin(String username,String password){
        //将账号和密码封装到token对象
        UsernamePasswordToken token =//参考官网
                new UsernamePasswordToken(username, password);
        //基于subject对象将token提交给securityManager
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.login(token);//提交给securityManager
        return new JsonResult("login ok");
    }

第五步:统一异常处理类中添加shiro异常处理代码,关键如下:

@ExceptionHandler(ShiroException.class)
    public JsonResult doShiroException(ShiroException e){
        JsonResult r=new JsonResult();
        r.setState(0);
        if (e instanceof UnknownAccountException){
            r.setMessage("用户名不存在");
        }else if (e instanceof IncorrectCredentialsException){
            r.setMessage("密码不正确");
        }else if (e instanceof LockedAccountException){
            r.setMessage("账户被锁定");
        }else  if (e instanceof AuthorizationException){
            r.setMessage("没有权限");
        }else {
            r.setMessage("认证或授权失败");
        }
        return r;
    }

第五步:在过滤配置中允许登录时的url匿名访问,关键代码如下:

... 
map.put("/user/doLogin","anon"); 
...

第六步:再过滤配置中配置登出url操作,关键代码如下:

..
map.put("/doLogout","logout");//logout是shiro框架给出一个登出过滤器
 ...

第六步:启动服务器,进行登录访问测试
在这里插入图片描述
第七步:Shiro框架认证流程总结分析
Step01:登录客户端(login.html)中的用户输入的登录信息提交SysUserController对象
Step02:SysUserController对象基于doLogin方法处理登录请求.
Step03:SysUserController中的doLogin方法将用户信息封装token中, 然后基于subject对象将
token提交给SecurityManager对象。
Step04:SecurityManager对象调用认证方法(authenticate)去完成认证,在此方法内部会调用
ShiroRealm中的doGetAuthenticationInfo获取数据库中的用户信息,然后再与客户端提交的
token中的信息进行比对,比对时会调用getCredentialsMatcher方法获取凭证加密对象,通过此对
象对用户提交的token中的密码进行加密。

4、Shiro授权业务分析及实现

授权流程分析
已认证用户,在进行系统资源的访问时,我们还要检查用户是否有这个资源的访问权限。并不是所有认证用户都可以访问系统内所有资源,也应该是受限访问的,具体授权流程如图所示:
在这里插入图片描述
Shiro框架中的授权实现
第一步: 在SysMenuDao中定义查询用户权限标识的方法,关键代码分析:

Set<String> findUserPermissions(Integer userId);

第二步:在SysMenuMapper中添加查询用户权限标识的SQL映射,关键代码如下:

<select id="findUserPermissions" resultType="String">
        select distinct permission
        from sys_user_roles ur join sys_role_menus rm join sys_menus m
            on ur.role_id=rm.role_id and rm.menu_id=m.id
        where ur.user_id=#{userId} and trim(m.permission)!='' and m.permission is not null
    </select>

在这里插入图片描述

第三步:修改ShiroRealm中获取权限并封装权限信息的方法,关键代码如下

/**此方法负责获取授权信息*/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //1、获取登录用户id
        SysUser user =(SysUser) principalCollection.getPrimaryPrincipal();
        //2、基于登录用户id获取用户权限标识
        Set<String> stringPermissions=sysMenuDao.findUserPermissions(user.getId());
        //3、封装数据并返回
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        info.setStringPermissions(stringPermissions);
        return info;//返回给授权管理器
    }

第四步:定义授权切入点方法,示例代码如下:
在shiro框架中,授权切入点方法需要通过**@RequiresPermissions**注解进行描述,例如:

@RequiresPermissions("sys:user:update") 
public int validById(Integer id,Integer valid){ 
int rows=sysUserDao.validById(id,valid); 
if(rows==0)throw new ServiceException("记录可能已经不存在");
 return rows; }

其中, @RequiresPermissions注解中定义的内容为,访问此方法需要的权限.
@RequiresPermissions 描述的方法为切入点方法,此方法在执行时需要在“通知方法”中判定用户是否由访问此方法的权限(检测用户权限中是否包含@RequiresPermissions注解内部包含的限),假如有权限,则授权访问.

第五步:启动服务进行访问测试和原理分析
在访问时首先要检测一下用户有什么权限,检测过程,先查询用户有什么角色,再查看角色有什么菜单的访问权限.
授权原理分析:(底层基于AOP实现)
Step01 页面上用户通过菜单触发对服务端资源的访问.
Step02 服务端Controller处理客户端的资源访问请求
Step03 假如客户端请求访问的资源业务放上有@RequiresPermissions注解描述则底层Controller
对象会调用Service的代理对象,代理对象会调用AOP中通知方法,在通知方法中获取@RequiresPermissions上的定义的权限标识.
Step04 通过Subject 对象提交@RequiresPermissions注解中的授权标识给SecurityManager对象,
此对象会调用ShiroRealm中的获取用户权限的方法,最终会将从数据权限信息与
@RequiresPermissions中的定义的权限信息做一个比对.

5、Shiro认证授权业务进阶实现

呈现登录用户信息
Controller 方法定义及实现
修改PageController中的doIndex方法(主页调用的方法),关键代码如下:

 @RequestMapping("doIndexUI")
    public String doIndexUI(Model model) {
        //获取登录用户信息(shiro框架给定的固定写法)
        SysUser user=(SysUser)
        SecurityUtils.getSubject().getPrincipal();
        //存储用户信息
        model.addAttribute("username", user.getUsername());
        return "starter";
    }

页面Thymeleaf 表达式应用
打开starter.html页面,找到用户名对应的位置,然后通过[[${}]]表达式获取服务端model中数据,呈现在页面上,关键代码如下:

<span class="hidden-xs" id="loginUserId">[[${username}]]</span>

用户菜单的动态化呈现
业务分析
我们希望不同登录用户,登录系统以后,看到的用户菜单是不一样的。登录用户只能看到自己可以访问的一些菜单选项

服务端设计及实现
第一步:定义pojo对象存储用户菜单信息,关键代码如下:

package com.cy.pj.sys.pojo; 
@Data 
public class SysUserMenu implements Serializable{ 
private static final long serialVersionUID = -410105494012229800L; 
private Integer id; 
private String name; 
private String url; 
private List<SysUserMenu> childMenus; 
}

第二步:在SysMenuDao中定义查询用户一级和二级菜单信息的方法,关键代码如下:

List<SysUserMenu> findUserMenus(Integer userId);

第三步:在SysMenuMapper中定义查询用户一级和二级菜单信息时对应的sql映射,关键代码如下:

<resultMap id="sysUserMenu" type="com.cy.pj.sys.pojo.SysUserMenu">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="url" column="url"/>
        <collection property="childMenus" ofType="com.cy.pj.sys.pojo.SysUserMenu">
            <id property="id" column="cid"/>
            <result property="name" column="cname"/>
            <result property="url" column="curl"/>
        </collection>
    </resultMap>
    <select id="findUserMenus" resultMap="sysUserMenu">
        select p.id,p.name,p.url,c.id cid,c.name cname,c.url curl
        from sys_menus p join sys_menus c
        on p.id=c.parentId
        where p.parentId is null and c.id in (
            select rm.menu_id from sys_user_roles ur join sys_role_menus rm
                          on ur.role_id=rm.role_id
                     where ur.user_id=#{userId}
            )
    </select>

注:查询多级菜单可以根据递归查询,比如,先查询根据userid查询出全部菜单信息进行封装,然后根据一级菜单id查询出一级菜单进行封装,然后定义递归方法根据父菜单id进行查询子菜单信息封装到父级菜单,直到子菜单没有数据,结束。

第四步:在SysMenuService接口中添加查询用户菜单信息的方法,关键代码如下:

List<SysUserMenu> findUserMenus(Integer userId);

第五步:在SysMenuServiceImpl类中添加查询用户菜单信息的方法,关键代码如下:

public List<SysUserMenu> findUserMenus(Integer userId){
 return sysMenuDao.findUserMenus(userId); 
 }

第六步:修改PageController中的doIndexUI方法,关键代码如下:

@Autowired private SysMenuService sysMenuService;
 @RequestMapping("doIndexUI")
    public String doIndexUI(Model model) {
        //获取登录用户信息(shiro框架给定的固定写法)
        SysUser user=(SysUser)
        SecurityUtils.getSubject().getPrincipal();
        //存储用户信息
        model.addAttribute("username", user.getUsername());
        //查询用户菜单
        List<SysUserMenu> userMenus = sysMenuService.findUserMenus(user.getId());
        model.addAttribute("userMenus", userMenus);
        return "starter";
    }

客户端设计及实现
第一步:修改starter页面菜单呈现部分的内容,关键代码如下:

<li class="treeview" th:each="p:${userMenus}">   
         <a href="#"><i class="fa fa-link"></i>            
         <span>[[${p.name}]]</span>
         <span class="pull-right-container"> 
         <i class="fa fa-angle-left pull-right"></i> 
         </span>
       </a>
       <ul class="treeview-menu"> 
       <li th:each="c:${p.childMenus}"> 
       <a th:onclick="doLoadRS([[${c.url}]])">[[${c.name}]]</a></li> 
       </ul> 
</li>

第二步:添加菜单事件处理函数,关键代码如下

function doLoadRS(url){ $("#mainContentId").load(url); }

第三步:启动服务进行访问测试

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 12:51:11  更:2022-04-04 12:52:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 22:46:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码