整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

在asp.net core 中控制访问权限的方法

在asp.net core 中控制访问权限的方法



ntro#

由于项目需要,需要在基于 asp.net mvc 的 Web 项目框架中做权限的控制,于是才有了这个权限控制组件,最初只是支持 netframework,后来 dotnetcore 2.0 发布了之后添加了对 asp.net core 的支持,在 dotnetcore 3.0 发布之后也增加了对 asp.net core 3.0 的支持(1.9.0及之后版本),目前对于 asp.net core 支持的更多一些,asp.net core 可以使用 TagHelper 来控制页面上元素的权限访问,也可以通过 Policy 来控制权限访问,同时支持通过中间件也可以实现对静态资源的访问。

安装 AccessControlHelper nuget 包#

安装 nuget 包 WeihanLi.AspNetMvc.AccessControlHelper

Copydotnet add package WeihanLi.AspNetMvc.AccessControlHelper

实现自己的访问策略#

资源访问策略/API访问策略#

以下代码定义了一个简单的访问策略,需要登录且拥有 Admin 角色,可以根据自己需要调整优化

Copypublic class AdminPermissionRequireStrategy : IResourceAccessStrategy
{
    private readonly IHttpContextAccessor _accessor;

    public AdminPermissionRequireStrategy(IHttpContextAccessor accessor)
    {
        _accessor=accessor;
    }

    public bool IsCanAccess(string accessKey)
    {
        var user=_accessor.HttpContext.User;
        return user.Identity.IsAuthenticated && user.IsInRole("Admin");
    }

    public IActionResult DisallowedCommonResult=> new ContentResult
    {
        Content="No Permission",
        ContentType="text/plain",
        StatusCode=403
    };

    public IActionResult DisallowedAjaxResult=> new JsonResult(new JsonResultModel
    {
        ErrorMsg="No Permission",
        Status=JsonResultStatus.NoPermission
    });
}

页面元素访问策略#

定义页面元素/控件访问策略:

Copypublic class AdminOnlyControlAccessStrategy : IControlAccessStrategy
{
    private readonly IHttpContextAccessor _accessor;

    public AdminOnlyControlAccessStrategy(IHttpContextAccessor httpContextAccessor)=> _accessor=httpContextAccessor;

    public bool IsControlCanAccess(string accessKey)
    {
        if ("Never".Equals(accessKey, System.StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }
        var user=_accessor.HttpContext.User;
        return user.Identity.IsAuthenticated && user.IsInRole("Admin");
    }
}

服务注册配置#

在 Startup 里注册服务:

Copyservices.AddAccessControlHelper()
    .AddResourceAccessStrategy<AdminPermissionRequireStrategy>()
    .AddControlAccessStrategy<AdminOnlyControlAccessStrategy>()
    ;

如果你只是 web api ,不涉及到页面元素的权限控制可以只注册 ResourceAccessStrategy

Copyservices.AddAccessControlHelper()
.AddResourceAccessStrategy<AdminPermissionRequireStrategy>();

默认访问策略的生命周期是单例的,如果需要注册为Scoped,可以指定默认的生命周期

Copyservices.AddAccessControlHelper()
.AddResourceAccessStrategy<AdminPermissionRequireStrategy>(ServiceLifetime.Scoped);

API/资源的权限控制#

对于 asp.net core 应用推荐使用 Policy 来控制权限的访问,可以在需要权限控制的 Action 或者 Controller 上设置 [Authorize("AccessControl")] 或者 [Authorize(AccessControlHelperConstants.PolicyName)]

Copy[Authorize(AccessControlHelperConstants.PolicyName)]
public class SystemSettingsController : AdminBaseController
{
    // ...
}
Copy[Authorize(AccessControlHelperConstants.PolicyName)]
public ActionResult UserList()
{
    return View();
}

页面元素的权限控制#

引用 TagHelper#

在 Views 目录下的 _ViewImports.cshtml 文件中导入 AccessControlHelper 的 TagHelper

Copy@using ActivityReservation
@using WeihanLi.AspNetMvc.AccessControlHelper
@using WeihanLi.AspNetMvc.MvcSimplePager

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, WeihanLi.AspNetMvc.AccessControlHelper

页面元素配置#

在需要权限控制的元素上增加 asp-access 的 attribute 就可以了,如果需要 access-key 通过 asp-access-key 来配置

Copy<ul class="list-group" asp-access asp-access-key="AdminOnly">
    <li role="separator" class="list-unstyled">
        <br />
    </li>
    <li class="list-group-item">@Html.ActionLink("用户管理", "UserList", "Account")</li>

    <li class="list-group-item">@Html.ActionLink("操作日志查看", "Index", "OperationLog")</li>
    <li class="list-group-item">@Html.ActionLink("系统设置管理", "Index", "SystemSettings")</li>
    <li class="list-group-item">
        @Html.ActionLink("微信设置管理", "Index", new {
        controller="Config",
        area="Wechat"
    })
    </li>
</ul>

这样就可以了,有权限访问的时候才会正常渲染,没有权限访问的时候,这一段 ul 并不会渲染输出,在客户端浏览器查看源代码也不会看到对应的代码

ormMaking介绍

FormMaking表单设计器是一款与行业无关的通用表单设计器前端解决方案,表单设计器包含设计器(MakingForm)和生成器(GenerateForm)两个模块,设计器主要负责表单的动态设计,可以通过拖拽的形式设计生成表单页面,生成器则负责对表单页面的渲染和数据的回写。

如果你对FormMaking不太了解可能会把FormMaking当成是单纯页面设计器,只为了减少重复的表单,稍微有点经验的前端开发,通过简单的Copy Paste就能很快做出一个固定的表单页面。而FormMaking做的是一整套与表单相关的前端方案,包含了表单的动态设计,表单渲染,数据回写,数据校验,事件支持,css自定义,动作面板等丰富的功能,而这些所有的功能只需要一个import 命令将MakingFormGenerateForm引入到项目中,你的项目就可以拥有这些功能。

FormMaking新功能 自定义字段高级用法

FormMaking基础功能可以看之前都介绍。

本文我们将介绍如何使用 FormMaking 来引入自己的高级组件,并可以通过设计器进行配置,处理事件等操作

封装分页数据表格组件

我们将封装 一个分页数据表格组件,组件采用 ElementPlus TableV2

<template>
  <div>
    <div style="height: 400px">
      <el-auto-resizer>
        <template #default="{ height, width }">
          <el-table-v2
            :columns="columns"
            :data="data"
            :width="width"
            :height="height"
            fixed
          />
        </template>
      </el-auto-resizer>
    </div>
    <el-pagination 
      background 
      layout="prev, pager, next" 
      :total="1000" 
      v-model:current-page="currentPage"
      @current-change="loadPageData"
    />
  </div>
</template>

<script setup>
import { onMounted, ref, watch } from 'vue'

const props=defineProps({
  modelValue:  {
    type: Array,
    default: ()=> []
  },
  columns: {
    type: Array,
    default: ()=> []
  }
})

const emit=defineEmits(['on-load'])

const data=ref(props.modelValue)

const currentPage=ref(1)

const loadPageData=(index)=> {
  // 通过 emit,可以将事件抛出到设计器中进行配置
  emit('on-load', index)
}

onMounted(()=> {
  emit('on-load', currentPage.value)
})

watch(()=> props.modelValue, (val)=> {
  data.value=val
})
</script>


引入到设计器

注册组件

首先应该在自己项目中进行组件的注册。

import CustomPaginationTable from 'PaginationTable.vue'

app.use(FormMakingV3, {
  components: [{
    name: 'custom-pagination-table',
    component: CustomPaginationTable // 自定义组件
  }]
})

也可以使用Vue.component 进行组件的注册

app.component('custom-pagination-table', CustomPaginationTable)

设计器配置

<template>
<fm-making-form
  :custom-fields="customFields"
>
  </fm-making-form>
</template>

<script>
  export default {
    data() {
      return {
        customFields: [
          {
            name: '分页数据列表',
            el: 'custom-pagination-table',
            options: {
              defaultValue: [],
              labelWidth: 0,
              isLabelWidth: false,
              hidden: false,
              dataBind: true,
              validator: '',
              extendProps: {
                columns: [] // 用于配置表格的列
              }
            },
            events: {
              onLoad: '' // 定义设计器可以配置的事件,处理组件 emit 的事件
            }
          }
        ]
      }
    }
  }
</script>

然后在自定义字段中就展示出来了


设计器界面

配置组件表格

表格列配置

在字段属性中对扩展属性配置 进行设置



数据加载

数据加载的 on-load事件,我们在自定义组件中通过emit抛出到设计器中进行配置,在字段属性-动作设置中添加onLoad事件配置如下:

效果展示

我们来查看下最后的效果

效果图

更多关于FormMaking详情可移步:

可视化低代码表单设计器 - FormMaking

宝页面比较复杂,含有各种请求参数和加密参数,如果直接请求或者分析Ajax将会非常繁琐。Selenium是一个自动化测试工具,可以驱动浏览器去完成各种工作,比如模拟点击、输入和下拉等多种功能,这样我们只需关心操作,不需要关心后台发生了怎么样的请求下面对具体操作步骤进行详述

实现流程

1.搜索关键字:利用Selenium驱动浏览器搜索关键字,得到查询后的商品列表

2.分析页码并翻页:得到商品页码数,模拟翻页,得到后续页面的商品列表

3.分析提取商品内容:利用PyQuery分析源码,解析得到商品列表

4.存储至MongoDB:将商品列表信息存储到数据库MongoDB

具体实现

1.首先需要声明一个browser用来操作,我的是chrome。这里的wait是在后面的判断元素是否出现时使用,第二个参数为等待最长时间,超过该值则抛出异常

#创建一个浏览器对象
browser=webdriver.Chrome()
#十秒内寻找元素失败,将报出TimeoutException异常
wait=WebDriverWait(browser, 10)

2.声明好之后就需要进行打开网页、进行搜索的操作

def search():
 """
 作用:通过关键字搜索得到搜索内容第一页产品信息,并返回搜索内容共计页码
 """
 print('正在搜索')
 try: 
 browser.get('https://www.taobao.com')
 print(browser.page_source)
 
 # 获取input输入框对象
 input=wait.until(
 EC.presence_of_element_located((By.CSS_SELECTOR, "#q")) 
 )#
 # 获取button提交对象
 submit=wait.until(
 EC.element_to_be_clickable(
 (By.CSS_SELECTOR, "#J_TSearchForm > div.search-button > button")
 )
 )
 # KEYWORD是config.py文件中定义的搜索关键字,可修改
 input.send_keys(KEYWORD)
 # 点击button提交按钮
 submit.click()
 # 通过CSS_SELECTOR选择器获取total页码
 total_page=wait.until(
 EC.presence_of_element_located(
 (By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.total")
 ) 
 )
 # 解析页面内容,并将数据保存至MongoDB中
 get_products()
 return total_page.text
 except TimeoutException:
 # 出现超时访问就重新调用一次search
 return search()

3.第一个页面操作之后,我们需要进行翻页操作,如下:

def next_page(page_number):
 """
 作用:作人为翻页功能,并得到该页产品信息
 page_number:循环的页码,将页码写入input框中
 """
 print('正在翻页', page_number)
 
 try:
 # 获取input输入框对象
 input=wait.until(
 EC.presence_of_element_located(
 (By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")
 ) 
 )
 # 获取button提交对象
 submit=wait.until(
 EC.element_to_be_clickable(
 (By.CSS_SELECTOR, "div > div > div > div.form > span.btn.J_Submit")
 )
 )
 # 将input框清空
 input.clear()
 # 向input框中输入页码
 input.send_keys(page_number)
 # 点击button提交按钮
 submit.click()
 wait.until(
 # 验证输入框内的页码是否为当前高亮页码,如果是,则继续执行
 # text参数是否在该span元素内,此处需要注意text的参数位置
 EC.text_to_be_present_in_element(
 (By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > ul > li.item.active > span'), str(page_number)
 )
 )
 
 # 获取产品信息并保存至MongoDB
 get_products()
 except TimeoutException:
 # 超时异常,则继续解析该页
 next_page(page_number)

4.写完搜索操作和翻页操作后,我们需要完成对每个页面的商品信息获取功能

def get_products():
 """
 作用:通过页面源代码,将产品信息保存至MongoDB中
 """
 # 通过CSS_SELECTOR选择器,验证页面中是否存在产品对象
 wait.until(
 EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-itemlist .items .item"))
 )
 # 拿到页面源码
 html=browser.page_source
 
 # 通过pyquery找到产品对象
 doc=pq(html)
 items=doc('#mainsrp-itemlist .items .item').items()
 # 遍历每个产品对象,组建数据内容
 for item in items:
 product={
 'image': item.find('.pic .img').attr('src'),
 'price': item.find('.price').text(),
 'deal': item.find('.deal-cnt').text()[:-3],
 'title': item.find('.title').text(),
 'shop': item.find('.shop').text(),
 'location': item.find('.location').text()
 }
 # print(product)
 # 将组建好的每个产品信息保存至MongoDB
 save_to_mongo(product)

5.获取信息之后则需要对信息进行存储,将数据库关键参数定义在config.py文件中

#!/usr/bin/env python
# coding=utf-8
# 本地的数据库
MONGO_URL='localhost'
# 数据库的名称
MONGO_DB='taobao'
# 数据库的表名
MONGO_TABLE='products'

6.将组建好的每个产品信息保存至MongoDB

# 创建一个MongoDB对象
client=pymongo.MongoClient(MONGO_URL)
# 创建一个数据库,注意是[]而不是()
db=client[MONGO_DB]
def save_to_mongo(result):
 """
 作用:将信息保存至MongoDB中
 """
 try:
 if db[MONGO_TABLE].insert(result):
 print('Successfully save to MongoDB', result)
 
 except Exception:
 print('Failed save to MongoDB', result)

7.爬取调度器

def TbMeishi_Spider():
 """
 作用:淘宝美食爬取调度器
 """
 try:
 total_page=search()
 # 通过正则将search返回的total页码内容解析成数字形式
 pattern=re.compile('(\d+)')
 total_page=int(pattern.search(total_page).group(1))
 # print(total_page)
 # 遍历页码,传入next_page函数中,做整站爬取
 for i in range(2, total_page + 1):
 next_page(i)
 
 except Exception:
 print('有错误产生...')
 
 finally:
 browser.close()

8.代码完成到这里已经可以结束了,但是使用Selenium模拟会发现每次运行程序都会打开一个浏览器,然后自动操作,可以最直接地观察到代码的运行,但是假如不想弹出浏览器的话,可以使用PhantomJS来实现一个无界面的webkit浏览器。虽然没有界面,但dom渲染、js运行、网络访问、canvas/svg绘制等功能都很完备,在页面抓取、页面输出、自动化测试等方面有广泛的应用。下载PhantomJS地址:http://phantomjs.org/download.html,下载完成后解压缩,将PhantomJS添加到环境变量中即可

在config.py文件中加上

# 关闭load-images的功能,加快运行速度,默认开启,开启disk-cache缓存,默认关闭
SERVICE_ARGS=['--load-images=false', '--disk-cache=true']
# 无界面浏览器,将browser改为
browser=webdriver.PhantomJS(service_args=SERVICE_ARGS)
# 设置PlantomJS的窗口大小,可能会影响内容下载
browser.set_window_size(1400, 900)

总结思考

1.目标网址不匹配

一开始使用显示等待访问,通过CSS_SELECTOR的id选择器找到网页元素,可最后程序都执行异常,每次都是TimeoutExceptions的异常错误。经过反复排查,才发现淘宝网有很多个站点

我在程序中使用的是:http://www.taobao.com

但是在进行网页分析的时候随意用百度搜了一个淘宝站点,它的站点是:https://uland.taobao.com/sem/tbsearch

两个站点地址不一样,网页元素也不一样,自己竟然会犯这种错误,应牢记,正确的选择元素应该是下面这几行

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
input=WebDriverWait(browser, 10).until(
 EC.presence_of_element_located((By.ID, 'q'))
)
button=WebDriverWait(browser, 10).until(
 EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search'))
)

2.text参数是否在该span元素内,此处需要注意text的参数位置

# 验证text参数是否与选择器选择的元素文本内容一致
text_to_be_present_in_element(locator, text_)

3.完整源码地址:https://github.com/XiaoFei-97/TbMeishi_Spider\