Spring Security OAuth2授权原理、流程与源码解读

文章目录

  • 前言
  • AuthorizationServerConfigurerAdapter(身份认证服务配置适配器)
    • OAuth2AuthorizationServerConfiguration(OAuth2授权服务配置)
  • EnableAuthorizationServer(开启身份认证服务)
    • AuthorizationServerEndpointsConfigurations身份认证服务站点配置类
      • AuthorizationEndpoint (授权码类型端点)
      • TokenEndpoint (Token端点)
      • CheckTokenEndpoint (检查Token端点)
  • AuthorizationServerSecurityConfigurer(授权服务安全配置)
    • ClientCredentialsTokenEndpointFilter (客户端认证Token端点过滤器)
  • ClientDetailsServiceConfigurer(客户端详情服务配置)
  • AuthorizationServerEndpointsConfigurer(授权服务端点配置)
    • TokenStore 设置
    • TokenGranter(Token授权)创建
  • TokenGranter(Token授权)
    • CompositeTokenGranter(综合Token授权)
    • AbstractTokenGranter(抽象Token授权)
      • RefreshTokenGranter(刷新Token授权)
      • AuthorizationCodeTokenGranter(授权码Token授权)
      • ResourceOwnerPasswordTokenGranter(密码Token授权)
      • ClientCredentialsTokenGranter(客户端凭证Token授权)
      • ImplicitTokenGranter(隐式Token授权)


前言

Spring Security OAuth2,认证原理与流程。

  1. 客户端认证由ClientCredentialsTokenEndpointFilter 完成客户端身份认证
  2. 用户授权由:TokenEndpointAuthorizationEndpoint 完成。
  3. Token创建、刷新、移除等。

执行流程如下图:
在这里插入图片描述

AuthorizationServerConfigurerAdapter(身份认证服务配置适配器)

授权服务安全配置

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
}

客户端详情配置

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
}

授权服务端点配置

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}

OAuth2AuthorizationServerConfiguration(OAuth2授权服务配置)

默认OAuth2授权服务配置,可以作为参考

EnableAuthorizationServer(开启身份认证服务)

在这个注解中引入了两个配置类AuthorizationServerEndpointsConfiguration(授权服务站点配置)和AuthorizationServerSecurityConfiguration(授权服务安全配置)


@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})

AuthorizationServerEndpointsConfigurations身份认证服务站点配置类

AuthorizationEndpoint (授权码类型端点)

@Bean
public AuthorizationEndpoint authorizationEndpoint() throws Exception {
	AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();
	FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
	//用户审批页面
	authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
	//异常处理程序提供者
	authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator());
	//异常页面
	authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
	//token授权
	authorizationEndpoint.setTokenGranter(tokenGranter());
	//配置客户端详情
	authorizationEndpoint.setClientDetailsService(clientDetailsService);
	//身份认证授权码服务
	authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());
	//OAuth2请求工厂
	authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
	//OAuth2请求校验
	authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
	//用户审批处理程序
	authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());
	//重定向解析器
	authorizationEndpoint.setRedirectResolver(redirectResolver());
	return authorizationEndpoint;
}

TokenEndpoint (Token端点)

@Bean
public TokenEndpoint tokenEndpoint() throws Exception {
	TokenEndpoint tokenEndpoint = new TokenEndpoint();
	//配置客户端详情
	tokenEndpoint.setClientDetailsService(clientDetailsService);
	//异常处理Handler
	tokenEndpoint.setProviderExceptionHandler(exceptionTranslator());
	//token授权
	tokenEndpoint.setTokenGranter(tokenGranter());
	//OAuth2请求工厂
	tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
	//OAuth2请求校验
	tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
	//设置允许请求方法
	tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods());
	return tokenEndpoint;
}
  1. ClientCredentialsTokenEndpointFilter 完成客户端身份认证
  2. 从ClientDetailsService中获取客户端详情信息
  3. 通过OAuth2RequestFactory将请求参数和客户端详情转为TokenRequest
  4. 如果client不为空,且判断clientId和获取TokenRequest的clientId是否相等
  5. 获取的客户端详情信息通过OAuth2RequestValidator校验请求域
  6. 不支持implicit授权模式
  7. 判断是否授权码类型请求,是需要设置对应的请求域
  8. 判断是否刷新Token类型请求,是需要设置对应的请求域
  9. 通过TokenGranter完成授权,返回OAuth2AccessToken
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
	//由ClientCredentialsTokenEndpointFilter 完成客户端身份认证
	if (!(principal instanceof Authentication)) {
		throw new InsufficientAuthenticationException(
					"There is no client authentication. Try adding an appropriate authentication filter.");
	}
	//从ClientDetailsService中根据clientId获取客户端详情信息
	String clientId = getClientId(principal);
	ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

    //通过OAuth2RequestFactory将请求参数和客户端详情转为TokenRequest 
	TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
	//如果client不为空,且判断clientId和获取客户端详情的clientId是否相等
	if (clientId != null && !clientId.equals("")) {
		// Only validate the client details if a client authenticated during this
		// request.
		if (!clientId.equals(tokenRequest.getClientId())) {
			// double check to make sure that the client ID in the token request is the same as that in the
			// authenticated client
			throw new InvalidClientException("Given client ID does not match authenticated client");
		}
	}
	//获取的客户端详情信息通过OAuth2RequestValidator校验请求域
	if (authenticatedClient != null) {
		oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
	}
	if (!StringUtils.hasText(tokenRequest.getGrantType())) {
		throw new InvalidRequestException("Missing grant type");
	}
	// 不支持implicit授权模式
	if (tokenRequest.getGrantType().equals("implicit")) {
		throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
	}
	//是否授权码类型请求
	if (isAuthCodeRequest(parameters)) {
		// The scope was requested or determined during the authorization step
		if (!tokenRequest.getScope().isEmpty()) {
			logger.debug("Clearing scope of incoming token request");
			tokenRequest.setScope(Collections.<String> emptySet());
		}
	}
	//是否刷新Token类型请求
	if (isRefreshTokenRequest(parameters)) {
		// A refresh token has its own default scopes, so we should ignore any added by the factory here.
		tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
	}
	//通过Token授权,返回OAuth2访问Token
	OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
	if (token == null) {
		throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
	}

	return getResponse(token);

}

CheckTokenEndpoint (检查Token端点)

@Bean
public CheckTokenEndpoint checkTokenEndpoint() {
	CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices());
	endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter());
	endpoint.setExceptionTranslator(exceptionTranslator());
	return endpoint;
}

AuthorizationServerSecurityConfigurer(授权服务安全配置)

主要完成功能是安全配置
主要功能:ClientDetailsService转换为UserDetailsService并注入到AuthenticationManager

public void init(HttpSecurity http) throws Exception {

	registerDefaultAuthenticationEntryPoint(http);
	//将ClientDetailsService转换为UserDetailsService并注入到AuthenticationManager
	if (passwordEncoder != null) {
		ClientDetailsUserDetailsService clientDetailsUserDetailsService = new ClientDetailsUserDetailsService(clientDetailsService());
		clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder());
		http.getSharedObject(AuthenticationManagerBuilder.class)
					.userDetailsService(clientDetailsUserDetailsService)
					.passwordEncoder(passwordEncoder());
	}
	else {
		http.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService()));
	}
	http.securityContext().securityContextRepository(new NullSecurityContextRepository()).and().csrf().disable()
				.httpBasic().realmName(realm);
	if (sslOnly) {
			http.requiresChannel().anyRequest().requiresSecure();
	}
}

在配置类中完成ClientCredentialsTokenEndpointFilter 加入到FilterChainProxy中完成客户端身份认证。

@Override
public void configure(HttpSecurity http) throws Exception {	
	// ensure this is initialized
	frameworkEndpointHandlerMapping();
	if (allowFormAuthenticationForClients) {
		clientCredentialsTokenEndpointFilter(http);
	}
	for (Filter filter : tokenEndpointAuthenticationFilters) {
		http.addFilterBefore(filter, BasicAuthenticationFilter.class);
	}
	http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
/** 
 * 完成客户端身份认证
*/
private ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter(HttpSecurity http) {
	//创建过滤器,并设置匹配地址,默认/oauth/token
	ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter(
			frameworkEndpointHandlerMapping().getServletPath("/oauth/token"));
	// 设置身份认证管理器,由init()方法中获取的值
	clientCredentialsTokenEndpointFilter
				.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
	//OAuth2身份认证进入点
	OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
	authenticationEntryPoint.setTypeName("Form");
	authenticationEntryPoint.setRealmName(realm);
	clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
	clientCredentialsTokenEndpointFilter = postProcess(clientCredentialsTokenEndpointFilter);
	//将过滤器加入到HttpSecurity 中
	http.addFilterBefore(clientCredentialsTokenEndpointFilter, BasicAuthenticationFilter.class);
	return clientCredentialsTokenEndpointFilter;
}

ClientCredentialsTokenEndpointFilter (客户端认证Token端点过滤器)

  1. 过滤器匹配地址默认:/oauth/token
  2. 获取请求参数中的:client_idclient_secret 的数据,转换为 UsernamePasswordAuthenticationToken
  3. 通过AuthenticationManager完成客户端身份认证

ClientDetailsServiceConfigurer(客户端详情服务配置)

AuthorizationServerEndpointsConfigurer(授权服务端点配置)

TokenStore 设置

private TokenStore tokenStore() {
	if (tokenStore == null) {
		if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
			this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
		}
		else {
			this.tokenStore = new InMemoryTokenStore();
		}
	}
	return this.tokenStore;
}

TokenGranter(Token授权)创建

  1. 获取ClientDetailsService
  2. 获取AuthorizationServerTokenServices
  3. 获取AuthorizationCodeServices
  4. 获取OAuth2RequestFactory
  5. 创建AuthorizationCodeTokenGranter并添加到tokenGranters
  6. 创建RefreshTokenGranter并添加到tokenGranters
  7. 创建ImplicitTokenGranter并添加到tokenGranters
  8. 创建ClientCredentialsTokenGranter并添加到tokenGranters
  9. 如果设置了AuthenticationManager 则创建ResourceOwnerPasswordTokenGranter并添加到tokenGranters
private List<TokenGranter> getDefaultTokenGranters() {
	ClientDetailsService clientDetails = clientDetailsService();
	AuthorizationServerTokenServices tokenServices = tokenServices();
	AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
	OAuth2RequestFactory requestFactory = requestFactory();

	List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
	tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
				requestFactory));
	tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
	ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
	tokenGranters.add(implicit);
	tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
	if (authenticationManager != null) {
		tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
					clientDetails, requestFactory));
	}
	return tokenGranters;
}

TokenGranter(Token授权)

CompositeTokenGranter(综合Token授权)

 List<TokenGranter> tokenGranters

通过代理方式、循环tokenGranters,根据对应的授权模式,找到指定的TokenGranter完成Token授权模式的选择。在执行方法如下:

OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest)

AbstractTokenGranter(抽象Token授权)

/*
 * 授权服务Token服务
 * 创建Token:OAuth2AccessToken createAccessToken(OAuth2Authentication authentication)
 * 刷新Token: OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
 * 获取Token:OAuth2AccessToken getAccessToken(OAuth2Authentication authentication)
 */
AuthorizationServerTokenServices tokenServices;
/*
 * 客户端详情服务
 * 获取客户端详情信息:ClientDetails loadClientByClientId(String clientId) 
 */
ClientDetailsService clientDetailsService;
/*
 * OAuth2请求工厂
 * 创建OAuth2请求: OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest)
 * 	
 */
OAuth2RequestFactory requestFactory;
/*
 * 授权码类型:authorization_code、password、refresh_token、implicit、client_credentials
 */
String grantType

授权

  1. 判断授权类型是否符支持对应的TokenGranter
  2. 从ClientDetailsService根据clientId获取ClientDetails
  3. 抽象类validateGrantType由每个子类具体完成验证授权类型。
  4. 将ClientDetails (客户端详情)和TokenRequest(Token请求) 通过OAuth2RequestFactory生成OAuth2Request,并创建未OAuth2Authentication。
  5. AuthorizationServerTokenServices(授权服务Token服务)类根据OAuth2Authentication创建OAuth2AccessToken 。
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
	if (!this.grantType.equals(grantType)) {
		return null;
	}
	
	String clientId = tokenRequest.getClientId();
	ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
	validateGrantType(grantType, client);
	if (logger.isDebugEnabled()) {
			logger.debug("Getting access token for: " + clientId);
	}
	return getAccessToken(client, tokenRequest);
}

RefreshTokenGranter(刷新Token授权)

  1. 刷新Token
  2. 从AuthorizationServerTokenServices根据refreshToken 更新OAuth2AccessToken
@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
	String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
	return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
}

AuthorizationCodeTokenGranter(授权码Token授权)

  AuthorizationCodeServices

ResourceOwnerPasswordTokenGranter(密码Token授权)

  /* 
   * 授权类型:password
   *  身份认证管理
   * /
  AuthenticationManager

验证授权类型

  1. 根据username和password创建UsernamePasswordAuthenticationToken
  2. 通过AuthenticationManager完成用户身份认证
  3. 如果抛出异常,则根据不同的异常给予不同的抛出异常信息。
  4. 认证通过,则创建OAuth2Authentication
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

	Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
	String username = parameters.get("username");
	String password = parameters.get("password");
	// Protect from downstream leaks of password
	parameters.remove("password");
	//根据username和password创建UsernamePasswordAuthenticationToken
	//不同的Authentication支持不通的AuthenticationProvider类
	//UsernamePasswordAuthenticationToken支持类有AbstractUserDetailsAuthenticationProvider类的实现
	Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
	((AbstractAuthenticationToken) userAuth).setDetails(parameters);
	try {
		//通过AuthenticationManager完成用户身份认证
		userAuth = authenticationManager.authenticate(userAuth);
	}catch (AccountStatusException ase) {
		//账户状态异常,则抛出无效授权异常
		throw new InvalidGrantException(ase.getMessage());
	}catch (BadCredentialsException e) {
		// 用户名或密码校验异常
		throw new InvalidGrantException(e.getMessage());
	}
	if (userAuth == null || !userAuth.isAuthenticated()) {
		throw new InvalidGrantException("Could not authenticate user: " + username);
	}
		
	OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);		
	return new OAuth2Authentication(storedOAuth2Request, userAuth);
}

ClientCredentialsTokenGranter(客户端凭证Token授权)

  1. 客户端凭证Token授权
  2. 根据父类(AbstractTokenGranter)方法执行grant(授权方法)获得OAuth2AccessToken
  3. 创建DefaultOAuth2AccessToken,并根据是否允许刷新Token
  4. 返回DefaultOAuth2AccessToken
@Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
	OAuth2AccessToken token = super.grant(grantType, tokenRequest);
	if (token != null) {
		DefaultOAuth2AccessToken norefresh = new DefaultOAuth2AccessToken(token);
		// The spec says that client credentials should not be allowed to get a refresh token
		if (!allowRefresh) {
			norefresh.setRefreshToken(null);
		}
		token = norefresh;
	}
	return token;
}

ImplicitTokenGranter(隐式Token授权)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/30881.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt编写精美输入法(历时十年迭代/可换肤/支持Qt4/5/6/win/linux/mac/嵌入式等)

一、前言 大概是从2012年就开始研究用Qt写输入法&#xff0c;因为项目需要&#xff0c;嵌入式板子上&#xff0c;没有对应的输入法&#xff0c;当初使用过很多NVR&#xff0c;里面也是鼠标按下弹出输入法面板进行输入&#xff0c;可以切换数字和字母及中文&#xff0c;于是借鉴…

jmeter如何进行http压力测试

目录 前言&#xff1a; 1、添加线程组&#xff1a; 2、添加采样器&#xff1a; 3、添加监视器 压力测试知识说明 前言&#xff1a; JMeter是一个基于Java的开源压力测试工具&#xff0c;可用于评估Web应用程序的性能&#xff0c;包括HTTP、HTTPS、FTP、SOAP、Restful、JD…

Oracle-高版本SQL优化分析(bind mismatch)

背景: 接到用户报障说一套Oracle19c数据库近期出现insert语句执行变慢的情况&#xff0c;执行一次数据插入需要1秒的时间&#xff0c;而且问题发生的数据库是跑在一体机上面&#xff0c;数据插入正常不应该这么慢&#xff0c;需要分析插入慢的原因 问题: 数据库近期出现insert…

StarRocks 文章收集

StarRocks在58的实践 StarRocks在58的实践 - 墨天轮StarRocks在58的实践 --2022-06-08https://www.modb.pro/db/639611 StarRocks之系统架构 StarRocks之系统架构 - 墨天轮https://www.modb.pro/db/610300 StarRocks小规模集群部署最佳实践(1/2) 0016.S StarRocks小规模集…

2自由度并联仿生腿的制作

1. 运动功能说明 本文实例将实现2自由度并联仿生腿模组运动起来&#xff0c;模拟实现狗腿行走的动作。 2. 结构说明 2自由度并联仿生腿模组是由两个舵机驱动的&#xff0c;它的所有动作都将在两个舵机的配合运动下实现。 3. 运动原理说明 2自由度并联仿生腿模组运动的点位如下…

数据结构-各种树(二叉树、二叉查找树、平衡二叉树、红黑树、B树、B+树)

文章目录 二叉树二叉查找树平衡二叉树红黑树B树B树 二叉树 概念&#xff1a;二叉树&#xff08;binary tree&#xff09;是指树中节点的度不大于2的有序树&#xff0c;它是一种最简单且最重要的树。二叉树的递归定义为&#xff1a;二叉树是一棵空树&#xff0c;或者是一棵由一…

2023 年6月开发者调查统计结果——最流行的技术(1)

2023 年6月开发者调查统计结果——最流行的技术&#xff08;1&#xff09; 本文目录&#xff1a; 一、编程、脚本和标记语言 二、数据库 三、云平台 四、网络框架和技术 五、其他框架和库 六、其他工具 七、集成开发环境 八、异步工具 九、同步工具 ​十、操作系统 …

端午出行电脑没网怎么办?无线网卡解决网络问题

无线网卡是一种可以让电脑或其他设备通过无线信号连接网络的硬件设备&#xff0c;无线网卡有多种类型和接口&#xff0c;例如USB无线网卡&#xff0c;PCI-E无线网卡&#xff0c;PCMCIA无线网卡等。端午出行在即&#xff0c;不妨看看驱动人生准备的无线网卡攻略&#xff0c;让大…

基于Python的招聘信息可视化系统,附源码

文章目录 1 简介2 技术栈3 总体设计3.1 系统结构3.2 数据库设计3.2.1 数据库实体3.2.2 数据库表设计 4 运行设计4.1 招聘热门行业分析4.2热门岗位分析界面4.3招聘岗位学历分析界面4.4岗位分布分析界面 5 源码下载 1 简介 基于Python的招聘信息可视化系统,通过对招聘数据进行分…

MFC扩展库BCGControlBar Pro v33.5亮点 - Ribbon Bar等全新升级

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v33.5已正式发布了&#xff0c;此版本包含了Ribbon&#xff08;功能区&#xff09;自定义…

Linux国产操作系统,UCA-系统工程师学习必备技能,使用dpkg管理软件包、apt命令、内网获取依赖包及源码安装

目录 ​编辑 1.使用dpkg管理软件包 2.apt命令 3.内网获取依赖包 4.源码安装 1.使用dpkg管理软件包 第一种方法当然可以上网搜索软件安装包&#xff0c;下载然后解压成软件。 第二种也就是我接下来要介绍的&#xff0c;dpkg 命令&#xff0c;dpkg 全称叫做debian package…

步长(stride) | 填充(padding) | 扩长(dilation)

这几个名词中文真的好难翻译&#xff0c;不是大佬就不要造名词了&#xff0c;后面还是老老实实用英文吧&#xff01;&#xff08;标题是机翻的 。&#xff09; stride stride 很好理解&#xff0c;stride 就是卷积核移动的步长。 如下图&#xff1a; stride1 stride2 paddi…

技术新动向 | 谷歌云大举扩展安全 AI 生态系统

【本文由 Cloud Ace 整理发布&#xff0c; Cloud Ace 是谷歌云全球战略合作伙伴&#xff0c;拥有 300 多名工程师&#xff0c;也是谷歌最高级别合作伙伴&#xff0c;多次获得 Google Cloud 合作伙伴奖。作为谷歌托管服务商&#xff0c;我们提供谷歌云、谷歌地图、谷歌办公套件…

【设计模式】SpringBoot优雅使用策略模式

文章目录 1.概述1.1.简述策略模式 2.实现方法2.1.实现思路2.2.实现代码2.3.策略拓展2.4.执行调用 3.总结 1.概述 本篇文章主要会描述SpringBoot与策略模式的结合使用&#xff0c;因为不涉及到理论部分&#xff0c;所以在阅读本篇之前&#xff0c;需要对策略模式的理论已经有了…

HarmonyOS学习路之开发篇—Java UI框架(JS FA调用Java PA)

JS FA调用Java PA机制 使用兼容JS的类Web开发范式的方舟开发框架提供了JS FA&#xff08;Feature Ability&#xff09;调用Java PA&#xff08;Particle Ability&#xff09;的机制&#xff0c;该机制提供了一种通道来传递方法调用、处理数据返回以及订阅事件上。 当前提供Ab…

鼠标键盘实验

文章目录 USB参考资料USB设备STM32F407USB 硬件连接软件移植官方HIDSTM32F4USB通信库 USB参考资料 ①《STM32F4xx中文参考手册》-第30章 全速USB on-the-go(OTG_FS) ②光盘&#xff1a;STM32参考资料:STM32 USB 学习资料-CD00289278.pdf(UM1021) ③光盘&#xff1a;STM32参考资…

Python3 函数与数据结构 | 菜鸟教程(十一)

目录 一、Python3 函数 &#xff08;一&#xff09;定义一个函数 1、你可以定义一个由自己想要功能的函数&#xff0c;以下是简单的规则&#xff1a; 2、语法 3、实例 ①让我们使用函数来输出"Hello World&#xff01;"&#xff1a; ②更复杂点的应用&#xff…

【gcc, cmake, eigen, opencv,ubuntu】一.gcc介绍

文章目录 gcc介绍1.查看当前gcc 版本2.安装其他版本的gcc3.设置多个版本的优先级4.修改默认的版本5.查看cpu信息 gcc介绍 gcc介绍和makefile介绍 1.查看当前gcc 版本 gcc --version2.安装其他版本的gcc sudo apt install gcc-10 g-10这样我们电脑里包含gcc-9 和 gcc-10两个…

干货分享|HOOPS Web平台和Polygonica进行增材制造的云CAM服务示例

这篇文章提供了一个示例项目&#xff0c;展示了使用 Machineworks Polygonica 和 HOOPS Web 平台进行增材制造的云 CAM 服务。该项目作为一个示例&#xff0c;说明了如何在服务器端使用 Polygonica 与 HOOPS Communicator 和 Exchange 来开发云服务。 它涵盖了增材制造 CAM 的…

三、DSMP/OLS等夜间灯光数据贫困地区识别——MPI和灯光指数拟合、误差分析

一、前言 当我们准备好MPI和灯光指数(包括总灯光指数和平均灯光指数)之后,接下来主要的过程就是通过将MPI和灯光指数拟合,构建多维度指数估算模型,这里我解释一下前文中的MPI计算过程,其实利用熵值法确定指标权重,并通过各 指 标 归 一 化 数 值 乘 以 对 应 的 权 重 …