首页 文章 精选 留言 我的

精选列表

搜索[SpringBoot4],共10000篇文章
优秀的个人博客,低调大师

ASP.NET Core Identity 实战(4)授权过程

这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程 前情提要 在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity。同样授权过程也不属于Identity,授权过程放在Identity系列中将的原因和认证过程一样——和成员系统放在一起容易理解。 动手做 在弄清的是授权过程在哪里发生的之前,我们先来动手写一写授权的代码,如果了解策略授权,那么你可以快速浏览过这部分 打开之前创建的项目,添加一个名为Demo的控制器,控制器代码如下: using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace IdentityDemo.Controllers { [Produces("application/json")] [Route("api/demo")] public class DemoController : Controller { [Authorize] [HttpGet] public object Get() { return new { User.Identity.Name, User.Identity.IsAuthenticated 略... 用之前注册的账户登录系统, 访问/api/demo,你将得到如下结果: { "name": "jbl-2011@163.com", "isAuthenticated": true } 然后退出登录,再次访问/api/demo,那么将会跳转到登陆页面,在这个过程中Authorize特性起到了至关重要的作用,接下来去掉Authorize特性,重复上两个操作,未登录的结果将是: { "name": null, "isAuthenticated": false } 通过这两个小例子,我们很容易就能推断出Authorize特性拦截了没有登陆的用户,等等,是Authorize特性拦截了请求吗? 授权过程的发生地 很显然,不是Authorize特性拦截了请求,特性只是标记了这个方法需要被授权才能访问,而真正拦截了请求的是——“Mvc 中间件”。Action是由Mvc执行的,Mvc执行时会确认Action上的Authorize特性,来确定是否要进行授权操作(成功授权可以访问,失败了会被阻止(比如跳转到登陆)),以及如何授权(动物园例子中,第二个门卫根据切实的情况决定),也就是自定义授权(角色等等)。 另外,如果我们只是简单的为 Action方法打上[Authorize]标记,那么它的默认行为就是验证IsAuthenticated是否是true,也就是在认证环节(Authentication 中间件)是否通过了认证 现在,我们知道了两个点 认证过程 Authentication 发生在 Authentication 中间件中 授权过程 Authorization 发生在 Mvc中间件中 基于策略的灵活授权 在企业应用中最为常见的就是基于角色的授权,实现角色授权的方式有两种,一种是直接写在Authorize特性上: [Authorize(Roles = "admin,super-admin,")] [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Test() 不过这种方式,不推荐,因为这样的话我们就将“角色”和“Uri”的绑定“硬编码在代码里了”,在很多场景这显然不合适,所以接下来我们要介绍的基于策略的授权就允许我们自定义授权逻辑,这样就灵活多了 基于策略Policy的授权 我们假设我们的授权规则是要求和上方代码片段实现相同效果,即用户具有角色“admin”或者角色“super-admin”,我们来逐步实现这个目标: 第一步在 DI 中注册一个用于我们需要的 policy services.AddAuthorization(options => { options.AddPolicy("role-policy", policy => { policy.AddRequirements(new RoleRequirement("admin","super-admin")); }); }); 我们为该策略指定了一个名字role-policy,并且指定了这个策略的需求条件,需求条件主要是为了设置策略的初始值,我们可以在策略注册时更改需求条件从而灵活控制授权。 接下来我们来编写 RoleRequirement public class RoleRequirement : IAuthorizationRequirement { public IEnumerable<string> Roles { get; } public RoleRequirement(params string[] roles) { Roles = roles ?? throw new ArgumentNullException(nameof(roles)); 略... 那我们的 RoleRequirement 主要实现的功能就是确定要包含的角色,因为要包含的角色是在构造函数中确定的,那么我们就将角色授权的逻辑(稍后介绍的Handler)和具体授权的数据分开了。 然后我们来实现RoleRequirement对应的处理程序: public class RoleHandler : AuthorizationHandler<RoleRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement) { foreach (var item in requirement.Roles) { if (context.User.IsInRole(item)) { context.Succeed(requirement); return Task.CompletedTask; } } context.Fail(); return Task.CompletedTask; 略... 这个处理器的工作十分简单就是验证当前用户是否在任意一个由RoleRequirement指定的角色中。在这里context.Succeed(requirement);指示授权成功,而授权失败一般不需要调用 context.Fail();因为对于这个需求还可能有其它处理器进行处理,而此例中调用 context.Fail();可以确保授权失败,因为RoleRequirement的处理器只有一个,所以这样做是没有问题的。 要注意的是刚刚提到的,我们已经将角色授权的逻辑(稍后介绍的Handler)和具体授权的数据分开了。 因为RoleHandler并不清楚要求用户有哪些角色,RoleHandler只知道如何去验证用户含有哪些角色,而具体要求用户含有哪些角色,是由 RoleRequirement 来决定的,这符合关注点分离和单一职责这两个编程概念。 再然后,我们要将刚刚写好的RoleHandler注册进Di services.AddSingleton<IAuthorizationHandler, RoleHandler>(); 最后一步,更换原来的Attribute: // [Authorize(Roles = "admin,super-admin,")] [Authorize(Policy ="role-policy")] [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Test() 现在,一个最基本的基于策略的授权就完成了。 本文中的示例较为简单,也并没有使用全部的授权特性,更详细的使用方法参考资料很多,本文也就不多做介绍。另外你可以参考ASP.NET Core中基于策略的授权来学习更过关于策略授权的内容 授权时指定AuthenticationScheme 指定AuthenticationScheme的代码类似这样: // [Authorize(Roles = "admin,super-admin,")] [Authorize(AuthenticationSchemes ="jwt"/*注意,这里的名字取决于你添加AuthenticationHandler时的名字*/, Policy ="role-policy")] [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<IActionResult> Test() 在上一篇博客ASP.NET Core Identity 实战(3)认证过程中提到,在Authentication中间件中可以放置多个Handler,而有一个是默认激活的,那么剩下的是被动调用的,现在我们的情况就是由我们在Authorize特性中去挑选一个Handler来执行,例如我们在Authentication中间件上放置两个Handler——CookieAuthenticationHandler和JwtAuthenticationHandler,并经CookieAuthenticationHandler指定为默认,那么我们想经由Jwt认证时怎么办? 这里有一个重要问题就是:当HttpContext流过Authentication中间件后才到Mvc中间件,而Mvc在确认Action指定的AuthenticationHandler时,Authentication过程已经结束了。 那这是怎么做到的呢? 还记的HttpContext中有一个扩展方法叫AuthenticateAsync,作为HttpContext的扩展方法也就意味着,我们可以在任何时候调用它进行认证操作。 namespace Microsoft.AspNetCore.Authentication { public static class AuthenticationHttpContextExtensions { public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context); public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme); 略... 看它的第二个重载,它是指定了 AuthenticationScheme的名字的,所以在Mvc中间件探查到Attribute指定了AuthenticationScheme时,就会重新挑选指定的AuthenticationHandler再次对请求进行认证 授权的发生地——AuthorizationFilter 在旧的Asp.Net时代,我们知道MvcFilter这个东西,现在它仍然在,如果你不了解它,我建议你稍作了解,建议参考官方文档 正如这一节的标题,授权发生在Microsoft.AspNetCore.Mvc.Authorization.AuthorizationFilter中,授权的逻辑类似这样: 先进行认证 如果指定了scheme,那么重新认证,如果没有,则使用之前 Authentication中间件的授权结果: public virtual async Task<AuthenticateResult> Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy policy, HttpContext context) { if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0) { ClaimsPrincipal newPrincipal = null; foreach (var scheme in policy.AuthenticationSchemes) { var result = await context.AuthenticateAsync(scheme); if (result != null && result.Succeeded) { newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal); } } if (newPrincipal != null) { context.User = newPrincipal; return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes))); } else { context.User = new ClaimsPrincipal(new ClaimsIdentity()); return AuthenticateResult.NoResult(); } } return (context.User?.Identity?.IsAuthenticated ?? false) ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")) : AuthenticateResult.NoResult(); } 这里面值得再次深入探讨的是 context.AuthenticateAsync(scheme),这是在 HttpAbstractions项目中的扩展方法,它的实现是: public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) => context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme); IAuthenticationService我们在 Authentication中间件中也见过,Authentication中间件也是使用了IAuthenticationService,之前的文章有提到过,这也再次证明了单一原则职责,身份认证中间件负责在管道中认证,而认证本身并非是和身份认证中间件捆绑的,上一篇博客ASP.NET Core Identity 实战(3)认证过程的最后有认证的源代码 再进行授权 授权总共分三步 拿到IAuthorizeHandler的实例(前面我们写了一个)(可能一个或者多个) 执行授权(每个Handler都会进行授权) 没了 这部分代码还是很简单的: public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements) { // 第一步 var authContext = _contextFactory.CreateContext(requirements, user, resource); var handlers = await _handlers.GetHandlersAsync(authContext); // 第二部 foreach (var handler in handlers) { await handler.HandleAsync(authContext); if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed) { break; } } // 没了(这主要是对结果进行处理) var result = _evaluator.Evaluate(authContext); if (result.Succeeded) { _logger.UserAuthorizationSucceeded(GetUserNameForLogging(user)); } else { _logger.UserAuthorizationFailed(GetUserNameForLogging(user)); } return result; } 这里面和我们在项目中写的代码有关就是IAuthorizeHandler的实例,在本文中,我们写了一个RoleHandler 到此,授权过程就结束了,另外一些就是边边角角的知识点,比如授权之后如何操作,这些不难,就不再文中赘述了

优秀的个人博客,低调大师

文档预览功能使用技巧(4)---图片水印

一、导语 智能媒体管理 提供了文档预览功能,通过 快速搭建 文章的介绍,详细描述了使用 “文档转换 + JS 前端渲染引擎” 实现文档预览的过程,本文将介绍预览功能中的 图片水印(wmType=2 和 wmValue=url 参数) 技巧。 二、技术背景 通过 快速搭建 提供如下的预览 URL: https://preview.imm.aliyun.com/index.html ?url=https://yourid-dev-imm.oss-cn-shanghai.aliyuncs.com/test-data/office/paxos.pptx/output //无需在output后加'/' &region=oss-cn-shanghai //转换数据所在桶的region &bucket=yourid-de

优秀的个人博客,低调大师

基于webpack4搭建的react项目框架

介绍 框架介绍,使用webpac构建的react单页面应用,集成antd。使用webpack-dev-server启动本地服务,加入热更新便于开发调试。使用bundle-loader进行代码切割懒加载 手动搭建,不使用cli,大量注释适合初学者对webpack的理解学习,对react项目的深入了解 启动 git clone https://gitee.com/wjj0720/react-demo.git cd react-demo yarn yarn start 打包 yarn build 目录结构 +node_modules -src +asset +Layout +pages +redux +utils +app.js +index.html +index.js .babelrc package.json postcss.config.js webpack.config.js //webpack 配置 bundle-loader 懒加载使用 // webpack.config.js 配置 module: { rules: [ { test: /\.bundle\.js$/, use: { loader: 'bundle-loader', options: { name: '[name]', lazy: true } } } ] } // 页面引入组件 import Home from "bundle-loader?lazy&name=[name]!./Home"; // 组件使用 因为组件懒加载 是通过异步的形式引入 所以不能再页面直接以标签的形式使用 需要做使用封装 import React, {Component} from 'react' import { withRouter } from 'react-router-dom' class LazyLoad extends Component { state = { LoadOver: null } componentWillMount() { this.props.Loading(c => { this.setState({ LoadOver: withRouter(c.default) }) }) } render() { let {LoadOver} = this.state; return ( LoadOver ? <LoadOver/> : <div>加载动画</div> ) } } export default LazyLoad // 通过封装的懒加载组件过度 增加加载动画 <LazyLoad Loading={Home} /> 路由配置 框架按照模块划分,pages文件夹下具有route.js 即为一个模块 // 通过require.context读取模块下路由文件 const files = require.context('./pages', true, /route\.js$/) let routers = files.keys().reduce((routers, route) => { let router = files(route).default return routers.concat(router) }, []) // 模块路由文件格式 import User from "bundle-loader?lazy&name=[name]!./User"; export default [ { path: '/user', component: User }, { path: '/user/:id', component: User } ] redux 使用介绍 // ---------创建 -------- // 为了不免action、reducer 在不同文件 来回切换 对象的形式创建 // createReducer 将书写格式创建成rudex认识的reducer export function createReducer({state: initState, reducer}) { return (state = initState, action) => { return reducer.hasOwnProperty(action.type) ? reducer[action.type](state, action) : state } } // 创建页面级别的store const User_Info_fetch_Memo = 'User_Info_fetch_Memo' const store = { // 初始化数据 state: { memo: 9, test: 0 }, action: { async fetchMemo (params) { return { type: User_Info_fetch_Memo, callAPI: {url: 'http://stage-mapi.yimifudao.com/statistics/cc/kpi', params, config: {}}, payload: params } }, ... }, reducer: { [User_Info_fetch_Memo] (prevState = {}, {payload}) { console.log('reducer--->',payload) return { ...prevState, memo: payload.memo } }, ... } } export default createReducer(store) export const action = store.action // 最终在模块界别组合 [当然模块也有公共的数据(见Home模块下的demo写法)] import {combineReducers} from 'redux' import info from './Info/store' export default combineReducers({ info, 。。。 }) // 最终rudex文件夹下的store.js 会去取所有模块下的store.js 组成一个大的store也就是我们最终仓库 // --------使用------ // 首先在app.js中将store和app关联 import { createStore } from 'redux' import { Provider } from 'react-redux' // reducer即我们最终 import reducer from './redux/store.js' // 用户异步action的中间件 import middleware from './utils/middleware.js' let store = createStore(reducer, middleware) <Provider store={store}> 。。。 </Provider> // 然后组件调用 只需要在组件导出时候 使用connent链接即可 import React, {Component} from 'react' import {connect} from 'react-redux' // 从页面级别的store中导出action import {action} from './store' class Demo extends Component { const handle = () => { // 触发action this.props.dispatch(action.fetchMemo({})) } render () { console.log(this.props.test) return <div onClick={this.handle}>ss</div> } } export default connect(state => ({ test: state.user.memo.test }) )(demo) 关于redux中间件 // 与其说redux中间件不如说action中间件 // 中间件执行时机 即每个action触发之前执行 // import { applyMiddleware } from 'redux' import fetchProxy from './fetchProxy'; // 中间件 是三个嵌套的函数 第一个入参为整个store 第二个为store.dispatch 第三个为本次触发的action // 简单封装的中间件 没有对请求失败做过多处理 目的在与项错误处理机制给到页面处理 const middleware = ({getState}) => next => async action => { // 此时的aciton还没有被执行 const {type, callAPI, payload} = await action // 没有异步请求直接返回action if (!callAPI) return next({type, payload}) // 请求数据 const res = await fetchProxy(callAPI) // 请求数据失败 提示 if (res.status !== 200) return console.log('网络错误!') // 请求成功 返回data return next({type, payload: res.data}) } export default applyMiddleware(middleware) 原文发布时间:2018年06月30日 作者:前端栈开发 本文来源:CSDN如需转载请联系原作者

优秀的个人博客,低调大师

java8中的时间处理4 - Instant

Instant 类是机器易读的时间信息,存放的是unix时间戳。 import java.time.Duration; import java.time.Instant; public class InstantExample { public static void main(String[] args) { Instant timestamp = Instant.now(); System.out.println("当前时间 = "+timestamp); Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli()); System.out.println("特定时间 = "+specificTime); //时间段 Duration thirtyDay = Duration.ofDays(30); System.out.println(thirtyDay); } }

优秀的个人博客,低调大师

4.python爬虫浏览器伪装技术

#python爬虫的浏览器伪装技术 #爬取csdn博客,会返回403错误,因为对方服务器会对爬虫进行屏蔽,此时需要伪装成浏览器才能爬取 #浏览器伪装,一般通过报头进行。 import urllib.request url="http://blog.csdn.net/bingoxubin/article/details/78503370" headers=("User-Agent","浏览器中User-Agent的值") opener=urllib.request.build_opener() opener.add_handlers=[headers] data=opener.open(url).read() print(len(data))

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册