SpringSecurity(二十三)--OAuth2:使用JWT和加密签名(上)对称密钥加密

news2024/11/16 17:32:51

一、前言

最近阳了所以一直都在休整,大家一定要注意身体,能不阳就不阳,如果阳康后还是一直咳嗽,最好是能去医院看看,这绝对不是专家口中所说的新冠感冒那么简单,也绝对不是什么80%的无症状,大家不要放松警惕,仅以我个人观点发声,身体才是最重要的。好了,接下来步入正题,终于,我们也迎来了Spring Security的尾声,学习完jwtTokenStore之后,Spring Security的主要内容学习就告一段落,之后我可能还会更新一些这方面的拓展,例如全局方法安全性,但这些都是后话了。然后是本章的代码仍然是以前面学习过的代码为基础的情况下继续修改开发,所以大家需要先去学习之前的再来学习本章效果才会更好。之后我打算开始一个新的技术栈—》elasticSearch,并且学习gitee的一个项目打造一个基于springboot的elasticsearch java客户端调用封装工具。希望大家也能跟着我一起坚持学习。
本章将讨论如何将JSON Web Token(JWT)用于令牌实现。
使用加密签名验证令牌的优点是允许资源服务器验证令牌而不需要直接调用授权服务器,也不需要共享数据库。这种实现令牌验证的方法通常用于使用OAuth2实现身份验证和授权的系统。出于这个原因,我们需要了解这一实现令牌验证的方式。

二、使用JWT以及对称秘钥签名的令牌

用于令牌签名的最简单的方法是使用对称秘钥。在这种方法中,使用相同的密钥,既可以签署一个令牌,又可以验证它的签名。使用对称秘钥对令牌进行签名的优点是,它比将在本章后面内容讨论的其他方法更简单,而且速度更快。然后,正如将介绍的,它也有缺点,不能总是与身份验证过程中涉及的所有应用程序共享用于签名令牌的密钥

2.1、使用JWT

JWT是一个令牌实现。令牌由三部分组成:头信息、主体和签名头信息和主体中的详情用JSON表示,并且它们是Base64编码的,第三部分是签名,这是使用一种加密算法生成的,该算法使用头信息和主体作为其输入密码算法还意味着需要密钥。密钥就像一个密码,拥有正确密钥的所有者可以签署令牌或验证签名的真实性。如果令牌上的签名是真实的,就可以确保在签名之后没有人修改令牌
在这里插入图片描述
JWT被签名时,我们也称它为JWS(JSON Web Token Signed)。通常,应用加密算法对令牌进行签名就足够了,但有时可以选择对令牌进行加密,如果对令牌进行了签名,就可以在没有任何密钥或密码的情况下查看其内容。但是,即使黑客看到了令牌的内容,他们也不能更改令牌的内容,因为如果他们这么做了,签名就会无效。要让签名有效,签名必须

  • 是使用正确密钥生成的
  • 匹配签名过的内容

在这里插入图片描述
如果令牌被加密了,则还会将其称为JWE(JSON Web Token Encrypted)。没有有效密钥,则无法看到已加密令牌的内容。

2.2、使用授权服务器以颁发JWT

本家将实现一个授权服务器,该服务器会向客户端颁发JWT以进行授权。我们之前说过管理令牌的组件是TokenStore。本节要做的是使用Spring Security提供的TokenStore的另一种实现。这里要使用的实现的名称是JwtTokenStore,它会管理JWT。本节还将测试授权服务器。而关于资源服务器,我们暂时不会进行特定实现,之后会解析原因。

  • 如果使用相同的密钥对令牌进行签名和验证签名,就可以说该密钥是对称的
  • 如果使用一个密钥签名令牌,但使用另一个密钥验证签名,则可以说使用的是一个非对称密钥对

这个示例将使用对称密钥实现签名。这种方法意味着授权服务器和资源服务器都知道并使用相同的密钥。授权服务器使用密钥对令牌进行签名,资源服务器使用相同的密钥验证签名
在这里插入图片描述
那么依赖仍然使用我们之前学习搭建项目使用的依赖即可。
这里为JdbcTokenStore配置JwtTokenStore的方式与 第14章相同。此外,还需要定义一个JwtAccessTokenConverter类型的对象。使用JwtAccessTokenConverter,就可以配置授权服务器验证令牌的方式;在这个示例中,将使用对称密钥。下面代码展示了如何在授权服务器配置类中配置JwtTokenStore:

import com.mbw.security.service.ClientDetailsServiceImpl;
import com.mbw.security.service.UserDetailsServiceImpl;
import com.mbw.security.token.JsonRedisTokenStore;
import com.mbw.security.token.enhancer.CustomTokenEnhancer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.util.ArrayList;
import java.util.List;


@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
	@Autowired
	private AuthenticationManager authenticationManager;
	@Autowired
	private ClientDetailsServiceImpl clientDetailsServiceImpl;
	@Autowired
	private UserDetailsServiceImpl userDetailsServiceImpl;
	@Autowired
	private CustomTokenEnhancer customTokenEnhancer;
	@Autowired
	private JsonRedisTokenStore jsonRedisTokenStore;

	@Value("${jwt.key}")
	private String jwtKey;  //从application.yaml文件中获取对称密钥的值

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.authenticationManager(authenticationManager)
				.userDetailsService(userDetailsServiceImpl)
				.accessTokenConverter(jwtAccessTokenConverter()) //配置访问令牌转换器对象
				.tokenStore(tokenStore());    //配置令牌存储对象
		DefaultTokenServices tokenService = getTokenStore(endpoints);
		endpoints.tokenServices(tokenService);
	}

	//配置TokenService参数
	private DefaultTokenServices getTokenStore(AuthorizationServerEndpointsConfigurer endpoints) {
		DefaultTokenServices tokenService = new DefaultTokenServices();
		tokenService.setTokenStore(endpoints.getTokenStore());
		tokenService.setSupportRefreshToken(true);
		tokenService.setClientDetailsService(endpoints.getClientDetailsService());
		tokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
		//token有效期 1小时
		tokenService.setAccessTokenValiditySeconds(3600);
		//token刷新有效期 15天
		tokenService.setRefreshTokenValiditySeconds(3600 * 12 * 15);
		tokenService.setReuseRefreshToken(false);
		return tokenService;
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.withClientDetails(clientDetailsServiceImpl);
	}



	/**
	 * 解决访问/oauth/check_token 403的问题
	 *
	 * @param security
	 * @throws Exception
	 */
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		// 允许表单认证
		security
				.tokenKeyAccess("permitAll()")
				.checkTokenAccess("permitAll()")
				.allowFormAuthenticationForClients();

	}

	@Bean
	public TokenStore tokenStore(){
		return new JwtTokenStore(jwtAccessTokenConverter());  //创建带有与之关联的访问令牌转换器的令牌存储
	}

	@Bean
	public JwtAccessTokenConverter jwtAccessTokenConverter(){
		JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
		jwtAccessTokenConverter.setSigningKey(jwtKey); //设置访问令牌转换器对象的对称密钥的值
		return jwtAccessTokenConverter;
	}
}

这里在application.yaml文件中存储了这个示例的对称密钥的值,如下面的代码片段所示。但是,不要忘记签名密钥是敏感数据,在现实场景中将其存储在密钥库中

jwt:
  key: MjWP5L7CiD

接下来可以启动授权服务器并调用/oauth/token端点来获取访问令牌。下面的代码片段展示了用于调用/oauth/token端点的postman调用展示:
调用后,首先在DefaultTokenService建立原始的refreshToken和accessToken,此时它们还不是JWT
在这里插入图片描述
然后放行,你会发现程序直接结束了,出来的token不是jwt,这是为什么呢?
在这里插入图片描述
经过我初步查资料,首先我们回归到创建token的代码----DefaultTokenServices
在这里插入图片描述
我们点进createAccessToken的方法,看到最后一行代码,如果accessTokenEnhancer存在,则做token增强,如果不存在,则返回普通token。回到问题之初,正是返回了普通token,所以,最大的可能便是此处的accessTokenEnhancer为空。
在这里插入图片描述
那我们debug看一下原因是否是这个:
在这里插入图片描述
果然是空,所以我们的配置类根本没有设置这个accessTokenEnhancer
我们来到配置DefaultTokenServices的地方–AuthorizationServerEndpointsConfigurer:

private DefaultTokenServices createDefaultTokenServices() {
		DefaultTokenServices tokenServices = new DefaultTokenServices();
		tokenServices.setTokenStore(tokenStore());
		tokenServices.setSupportRefreshToken(true);
		tokenServices.setReuseRefreshToken(reuseRefreshToken);
		tokenServices.setClientDetailsService(clientDetailsService());
		tokenServices.setTokenEnhancer(tokenEnhancer());
		addUserDetailsService(tokenServices, this.userDetailsService);
		return tokenServices;
	}

	private TokenEnhancer tokenEnhancer() {
		if (this.tokenEnhancer == null && accessTokenConverter() instanceof JwtAccessTokenConverter) {
			tokenEnhancer = (TokenEnhancer) accessTokenConverter;
		}
		return this.tokenEnhancer;
	}

逻辑比较明显,如果tokenEnhancer为空,同时,此时的accessTokenConverter为JwtAccessTokenConverter时,tokenEnhancer便赋值为accessTokenConverter,即JwtAccessTokenConverter。但是此时,我们并没有配置accessTokenConverter。所以tokenEnhancer便为空。从而造成DefaultTokenServices中的token返回便会直接普通token。
那你可能会有疑问,刚刚那段代码我们不是配置了accessTokenConverter吗,为什么没生效呢,原因就在于我们自己配置的DefaultTokenServices修改了配置类获取tokenEnhancer的逻辑

//配置TokenService参数
	private DefaultTokenServices getTokenStore(AuthorizationServerEndpointsConfigurer endpoints) {
		DefaultTokenServices tokenService = new DefaultTokenServices();
		tokenService.setTokenStore(endpoints.getTokenStore());
		tokenService.setSupportRefreshToken(true);
		tokenService.setClientDetailsService(endpoints.getClientDetailsService());
		tokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
		//token有效期 1小时
		tokenService.setAccessTokenValiditySeconds(3600);
		//token刷新有效期 15天
		tokenService.setRefreshTokenValiditySeconds(3600 * 12 * 15);
		tokenService.setReuseRefreshToken(false);
		return tokenService;
	}

看到我们这边调用的tokenEnhancer使用的是endpoints.getTokenEnhancer,点进这个方法可以看到它使用的是源码中获取tokenEnhancer的代码,也就是我们如果没有做额外配置,它直接取的是你一开始的属性,也就是空,也就是没有走我们刚刚看到的处理enhancer的逻辑,那么就算我们设置了tokenConverter也是无济于事的:
在这里插入图片描述

那么我们唯一想到的办法就是给endPoints(AuthorizationServerEndpointsConfigurer)配置上我们的tokenEnhancer,你可能会想,我不额外配置DefaultTokenServices就好了,让它使用默认的,但现在假设我们需要额外设置token的有效时间这些属性,那我们不得不重写tokenServices这个类,那么方法就只有对endPoints中的tokenEnhancer下手,这里呢我们可以直接写一个方法配置tokenEnhancer:

import com.mbw.security.service.ClientDetailsServiceImpl;
import com.mbw.security.service.UserDetailsServiceImpl;
import com.mbw.security.token.JsonRedisTokenStore;
import com.mbw.security.token.enhancer.CustomTokenEnhancer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.util.ArrayList;
import java.util.List;


@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
	@Autowired
	private AuthenticationManager authenticationManager;
	@Autowired
	private ClientDetailsServiceImpl clientDetailsServiceImpl;
	@Autowired
	private UserDetailsServiceImpl userDetailsServiceImpl;
	@Autowired
	private CustomTokenEnhancer customTokenEnhancer;
	@Autowired
	private JsonRedisTokenStore jsonRedisTokenStore;

	@Value("${jwt.key}")
	private String jwtKey;

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.authenticationManager(authenticationManager)
				.userDetailsService(userDetailsServiceImpl)
				.tokenEnhancer(tokenEnhancerChain())
				.tokenStore(tokenStore());
		DefaultTokenServices tokenService = getTokenStore(endpoints);
		endpoints.tokenServices(tokenService);
	}

	@Bean
	public TokenEnhancerChain tokenEnhancerChain(){
		TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
		List<TokenEnhancer> enhancers = new ArrayList<>();
		enhancers.add(jwtAccessTokenConverter());
		enhancers.add(customTokenEnhancer);
		enhancerChain.setTokenEnhancers(enhancers);//将自定义Enhancer加入EnhancerChain的delegates数组中
		return enhancerChain;
	}

	//配置TokenService参数
	private DefaultTokenServices getTokenStore(AuthorizationServerEndpointsConfigurer endpoints) {
		DefaultTokenServices tokenService = new DefaultTokenServices();
		tokenService.setTokenStore(endpoints.getTokenStore());
		tokenService.setSupportRefreshToken(true);
		tokenService.setClientDetailsService(endpoints.getClientDetailsService());
		tokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
		//token有效期 1小时
		tokenService.setAccessTokenValiditySeconds(3600);
		//token刷新有效期 15天
		tokenService.setRefreshTokenValiditySeconds(3600 * 12 * 15);
		tokenService.setReuseRefreshToken(false);
		return tokenService;
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.withClientDetails(clientDetailsServiceImpl);
	}



	/**
	 * 解决访问/oauth/check_token 403的问题
	 *
	 * @param security
	 * @throws Exception
	 */
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		// 允许表单认证
		security
				.tokenKeyAccess("permitAll()")
				.checkTokenAccess("permitAll()")
				.allowFormAuthenticationForClients();

	}

	@Bean
	public TokenStore tokenStore(){
		return new JwtTokenStore(jwtAccessTokenConverter());
	}

	@Bean
	public JwtAccessTokenConverter jwtAccessTokenConverter(){
		JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
		jwtAccessTokenConverter.setSigningKey(jwtKey);
		return jwtAccessTokenConverter;
	}
}

如上所示,配置自定义令牌增强器有点复杂,假设我现在需要额外配置一个自定义的令牌增强对象customTokenEnhancer,对我的令牌做一个增强,又想让其包含jwtTokenConverter的功能怎么办呢?

package com.mbw.security.token.enhancer;

import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;

@Component
public class CustomTokenEnhancer implements TokenEnhancer {
	@Override
	public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
		DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(accessToken);
		HashMap<String, Object> stringObjectHashMap = new HashMap<>();
		stringObjectHashMap.put("loginTime",new Date());
		token.setAdditionalInformation(stringObjectHashMap);
		return token;
	}
}

那么我们必须创建一个令牌增强器链TokenEnhancerChain,并设置整个链,而不是只设置一个对象,因为访问令牌转换器对象也是一个令牌增强器。如果只配置自定义令牌增强器,则要重写访问令牌转换器的行为。我们转而要将两者都添加到职责链中,并配置包含这两个对象的链。
现在我们可以启动我们的授权服务器,并且调用/oauth/token接口了
在这里插入图片描述
可以在响应中观察到,访问和刷新令牌现在都是JWT。在下面代码片段中,可以找到令牌主体的解码(JSON)形式,那么这个呢是通过一个在线解析jwt获取的,点击这个链接即可

{
  "exp": 1672371264,
  "user_name": "张飞",
  "authorities": [
    "read",
    "ROLE_USER",
    "ROLE_管理员",
    "update",
    "ROLE_ADMIN",
    "delete",
    "write"
  ],
  "jti": "dbd597f0-46ed-4a21-83c5-c5a66fb90273",
  "client_id": "f7n6ockwdb9zmayr",
  "scope": [
    "user_info"
  ]
}

那么下一步我们实现资源服务器,我们来到我们之前搭建的另一个服务–spring_security_resource_server,首先我们将我们配置类之前在yaml配置的用来同步授权服务器那个服务中的资源服务器这部分配置代码先注释,这个是为了能更好的带大家体验jwtTokenStore
在这里插入图片描述
然后在yaml配置和授权服务器相同的对称密钥:

jwt:
  key: MjWP5L7CiD

然后在资源服务器中配置tokenStore,这个你会发现要比redisTokenStore好配置很多,之前我们配置redisTokenStore的做法是在授权服务器中暴露一个获取角色资源的端点,然后用授权服务器的同一个服务中的资源服务器去保护这个端点,这样就避免了在其他资源服务器服务中引入过多依赖,尤其是对userDetails还做了定制的服务。
但是jwtTokenStore不同,这个不需要我们引入过多依赖,所以我们可以试着通过在资源服务器中配置我们的jwtTokenStore。那么最重要的方式是确保密钥使用相同的值。资源服务器需要该密钥用来验证令牌的签名:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import javax.servlet.http.HttpServletResponse;


@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

	@Value("${jwt.key}")
	private String jwtKey;

	@Override
	public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
		resources.tokenStore(tokenStore());
	}

	@Override
	public void configure(HttpSecurity http) throws Exception {
		http
				.csrf().disable()
				.exceptionHandling()
				.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
				.and()
				.authorizeRequests()
				.antMatchers("/test/**").authenticated()
				.and()
				.httpBasic();
	}


	@Bean
	public TokenStore tokenStore(){
		return new JwtTokenStore(jwtAccessTokenConverter());
	}

	@Bean
	public JwtAccessTokenConverter jwtAccessTokenConverter(){
		JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
		jwtAccessTokenConverter.setSigningKey(jwtKey);
		return jwtAccessTokenConverter;
	}
}

现在可以启动资源服务器,并使用前面从授权服务器获得的有效JWT调用我们保护的资源。在这个示例中,必须将令牌添加到以"Bearer"为前缀的请求的Authorization HTTP头信息中:
在这里插入图片描述
那么已上就是对称密钥对令牌进行签名和验证。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络——路由信息协议RIP的基本工作原理

&#x1f49f;&#x1f49f;前言 ​ 友友们大家好&#xff0c;我是你们的小王同学&#x1f617;&#x1f617; 今天给大家打来的是 计算机网络——路由信息协议RIP的基本工作原理 希望能给大家带来有用的知识 觉得小王写的不错的话麻烦动动小手 点赞&#x1f44d; 收藏⭐ 评论&…

(四)devops持续集成开发——jenkins的全局工具配置之maven环境安装及配置

前言 本节内容我们主要介绍jenkins中如何集成自定义的maven环境及流水化组件maven插件的安装&#xff0c;这样我们就可以发布流水化的maven项目工程。 正文 上传并安装maven①上传maven安装包 ②解压maven安装包 unzip apache-maven-3.8.3-bin.zip ③配置maven依赖包环境变量…

Attention:何为注意力机制?

本文来自公众号“AI大道理” 人类利用有限的注意力资源从大量信息中快速筛选出高价值信息&#xff0c;这是人类在长期进化中形成的一种生存机制&#xff0c;人类视觉注意力机制极大地提高了视觉信息处理的效率与准确性。 attention从注意力模型的命名方式看&#xff0c;借鉴了…

带你认识不一样的人工智能

人工智能简称AI&#xff0c;它是研究、模拟、延伸和扩展人类智能的理论、方法、技术和应用系统的新兴技术。人工智能是计算机科学的一个领域&#xff0c;正在向机器传递智能&#xff0c;通过模拟人的某些思维过程和智能行为&#xff0c;让机器像人类一样工作、反应和决策。自人…

学习C语言笔记:初始C语言

学习内容&#xff1a; 1.运算符——&#xff1b; 2.函数——main()、printf()&#xff1b; 3.编写一个简单的C程序&#xff1b; 4.创建整型变量&#xff0c;为其赋值并在屏幕上显示其值&#xff1b; 5.换行字符&#xff1b; 6.如何在程序中写注释&#xff0c;创建包含多个函数的…

Docker网络下-自定义网络实战

通过前面两篇的学习,我们对docker网络及四大网络类型都了解了。本文,咱们就来学习docker的自定义网络。我们为什么需要自定义网络呢?是为了让各个主机分门别类,井井有条。方便关联,使得网络之间可以通过服务名进行通信。为什么在容器中,我们要通过服务名进行通信呢?那是…

分享一个门店会员管理系统模板

会员制对于很多人来说都不陌生&#xff0c;进入中国市场几十年的时间里在许多行业都得到了广泛应用。所谓会员制&#xff0c;是指通过向特定的消费群体发放会员卡&#xff0c;并由消费者缴纳会费或者充钱的形式可享受商家的价格折扣、服务等方面优惠的经营形式&#xff0c;从而…

数据库设计以及分布式事务的产生

一、数据库架构的演进 单点时代 1在早期互联网或者当前小型网站,一般数据库和APP都采用单点方式进行部署,系统简单,容易维护读写分离 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29随着互联网的发展,网站访问量越来越大,数据库最先达到瓶颈,…

jenkins环境基本配置

上一篇文章讲解了jenkins的安装&#xff0c;这一篇文章介绍jenkins安装后的基本配置 1.进入jenkins之后&#xff0c;选择右上角admin下拉框选择设置 2.修改密码&#xff0c;并重新登录 3. jenkins的汉化 3.1 Dashboard ➥ Mange Jenkins ➥ Manage Plugins插件管理 ➥ 已安装…

github上有什么好的unity开源项目?

大量项目来袭 一、github上的Unity开源项目 github上的Unity开源项目 项目名称&#xff1a;《TowerDefense》《TowerDefense》 项目链接&#xff1a;《TowerDefense》项目链接 项目简介&#xff1a; 基于 Unity 的塔防示例游戏&#xff0c;此项目主要用来上手和学习基于 Un…

为什么WordPress要禁止编辑主题和插件?如何进行设置呢?

一淘模板问大家为什么WordPress要禁止编辑主题和插件&#xff1f;如何进行设置呢&#xff1f;接下来我们一起了解一下。 首先&#xff0c;为了安全起见&#xff0c;WordPress的安全是非常重要的&#xff0c;禁止编辑主题和插件的许可&#xff0c;即使有人进来&#xff0c;也不…

C# 数据库 类库

一 ADO.NET 1 System.Data名称空间 2 这种访问数据库的技术叫ADO.NET 3 ADO.NET 实现数据库的访问 ①提供标准的CRUD接口&#xff1b; ② 对不同的数据库提供统一的访问接口&#xff1b; 二 ADO.NET技术的发展 1 ODBC&#xff08;Open Database Connection) 2 DAO(Data …

excel文件管理:如何进行密码保护和破解? 上篇

对于一个公司&#xff0c;或者个人来说&#xff0c;有时候我们的数据往往需要加密。比如公司的经营状况和缴纳的税收有关系&#xff0c;人事档案中有每位员工的个人信息和工资组成说明&#xff0c;这些都是不需要无关人员知道的。微软对于EXCEL的设计就比较人性化&#xff0c;考…

如何搭建一套完整的数据指标体系?

如何搭建一套完整的数据指标体系&#xff1f; 你在工作中是不是这样的经常听到这样的对话&#xff1a; 老板&#xff1a;这次宣传活动总共带来了多少流量&#xff1f;你&#xff1a;大概有一万多人吧......老板&#xff1a;这次活动反响怎么样&#xff1f;你&#xff1a;有很多…

Linux操作系统实验3——进程切换

实验要求&#xff1a; 1.编写用户态程序&#xff0c;程序中需设计能引起进程状态发生变化的流程。 2.编写内核态模块&#xff0c;定时获取输入参数指定进程的运行状态。 3.通过内核态的记录指定进程运行状态变化的情况&#xff0c;需记录三种以上。 实验原理&#xff1a; 1. l…

有向图的拓扑序列

848. 有向图的拓扑序列 - AcWing题库 昨天看了这道题L3-031 千手观音 拓扑排序哈希表_他不是混子QAQ的博客-CSDN博客 就想着也用这道题的stl方法来试下 先来我的这个笨笨的方法&#xff0c;就当练习stl了&#xff0c;后面还有一个简便的stl STL知识点(刚知道&#xff1a; 对于…

linux系统中uboot的基本原理与实现方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;U-boot的操作与实现方法。 目录 第一&#xff1a;U-boot基本简介 第二&#xff1a;u-boot烧写与启动方法 第三&#xff1a;uboot中信息查询命令 第一&#xff1a;U-boot基本简介 linux系统启动必须要有一个bootloade…

举个栗子~Tableau 技巧(248):使用参数和轴实现图表坐标轴的缩放

实际业务分析场景中&#xff0c;使用折线图来呈现业绩趋势分析时候&#xff0c;经常会遇到这样问题&#xff1a;某一段时间的业绩数值波动范围较小&#xff0c;折线图趋于平缓&#xff08;如下图&#xff09;&#xff0c;很难判断业绩的波动差异&#xff0c;也很难一眼看出哪个…

vulnhub DC系列 DC-3

总结&#xff1a;joomscan工具的使用&#xff0c;cve-2016-4557内核提权或者cve-2021-4034内核提权 下载地址 漏洞分析 信息收集 sql注入 写马 提权 反弹shell 内核提权 cve-2016-4557 cve-2021-4034 下载地址 Download:http://www.five86.com/downloads/DC-3.zip 使用…

npm发布自己的组件UI包(详细步骤,图文并茂)

目前做前端项目&#xff0c;一直采用npm install XXX 的方式去引用别人的组件包&#xff0c;调用方法。 其实在开发中&#xff0c;每个开发者基本都写过单独的组件&#xff0c;如何让自己的组件能够重复的利用&#xff0c;如何让别人也享受到您的成果&#xff0c;这里将一步一步…