OAuth2.0二 JWT以及Oauth2实现SSO

一 JWT

1.1 什么是JWT

JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
官网: https://jwt.io/
标准: https://tools.ietf.org/html/rfc7519

JWT令牌的优点:

  1. jwt基于json,非常方便解析。
  2. 可以在令牌中自定义丰富的内容,易扩展。
  3. 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
  4. 资源服务使用JWT可不依赖授权服务即可完成授权。

缺点:
JWT令牌较长,占存储空间比较大。

1.2 JWT组成

一个JWT实际上就是一个字符串,它由三部分组成,头部(header)、载荷(payload)与签名(signature)。
在这里插入图片描述
头部(header)
头部用于描述关于该JWT的最基本的信息:类型(即JWT)以及签名所用的算法(如HMACSHA256或RSA)等。
这也可以被表示成一个JSON对象:

{"alg": "HS256","typ": "JWT"
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

载荷(payload)
第二部分是载荷,就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:

  • 标准中注册的声明(建议但不强制使用)
    iss: jwt签发者
    sub: jwt所面向的用户
    aud: 接收jwt的一方
    exp: jwt的过期时间,这个过期时间必须要大于签发时间
    nbf: 定义在什么时间之前,该jwt都是不可用的.
    iat: jwt的签发时间
    jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
  • 公共的声明
    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
  • 私有的声明
    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
    定义一个payload:
{"sub": "1234567890","name": "John Doe","iat": 1516239022
}

然后将其进行base64加密,得到Jwt的第二部分:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

签名(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret(盐,一定要保密)

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分:

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);var signature = HMACSHA256(encodedString, 'fox'); // khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

如何应用
一般是在请求头里加入Authorization,并加上Bearer标注:

fetch('api/user/1', {headers: {'Authorization': 'Bearer ' + token}
})

服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:
在这里插入图片描述

1.3 JJWT

JJWT是一个提供端到端的JWT创建和验证的Java库,永远免费和开源(Apache License,版本2.0)。JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

快速开始
引入依赖

<!--JWT依赖-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

创建token
创建测试类,生成token

@Test
public void test() {//创建一个JwtBuilder对象JwtBuilder jwtBuilder = Jwts.builder()//声明的标识{"jti":"666"}.setId("666")//主体,用户{"sub":"Fox"}.setSubject("Fox")//创建日期{"ita":"xxxxxx"}.setIssuedAt(new Date())//签名手段,参数1:算法,参数2:盐.signWith(SignatureAlgorithm.HS256, "123123");//获取tokenString token = jwtBuilder.compact();System.out.println(token);//三部分的base64解密System.out.println("=========");String[] split = token.split("\\.");System.out.println(Base64Codec.BASE64.decodeToString(split[0]));System.out.println(Base64Codec.BASE64.decodeToString(split[1]));//无法解密System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
}

运行结果
在这里插入图片描述
token的验证解析
在web应用中由服务端创建了token然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。

@Test
public void testParseToken(){//tokenString token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiJGb3giLCJpYXQiOjE2MDgyNzI1NDh9" +".Hz7tk6pJaest_jxFrJ4BWiMg3HQxjwY9cGmJ4GQwfuU";//解析token获取载荷中的声明对象Claims claims = Jwts.parser().setSigningKey("123123").parseClaimsJws(token).getBody();System.out.println("id:"+claims.getId());System.out.println("subject:"+claims.getSubject());System.out.println("issuedAt:"+claims.getIssuedAt());
}

在这里插入图片描述
试着将token或签名秘钥篡改一下,会发现运行时就会报错,所以解析token也就是验证token
在这里插入图片描述
token过期校验
有很多时候,我们并不希望签发的token是永久生效的,所以我们可以为token添加一个过期时间。原因:从服务器发出的token,服务器自己并不做记录,就存在一个弊端:服务端无法主动控制某个token的立刻失效。
在这里插入图片描述
当未过期时可以正常读取,当过期时会引发io.jsonwebtoken.ExpiredJwtException异常。
在这里插入图片描述
自定义claims
我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以自定义claims。

@Test
public void test() {//创建一个JwtBuilder对象JwtBuilder jwtBuilder = Jwts.builder()//声明的标识{"jti":"666"}.setId("666")//主体,用户{"sub":"Fox"}.setSubject("Fox")//创建日期{"ita":"xxxxxx"}.setIssuedAt(new Date())//设置过期时间   1分钟.setExpiration(new Date(System.currentTimeMillis()+60*1000))//直接传入map// .addClaims(map).claim("roles","admin").claim("logo","xxx.jpg")//签名手段,参数1:算法,参数2:盐.signWith(SignatureAlgorithm.HS256, "123123");//获取tokenString token = jwtBuilder.compact();System.out.println(token);//三部分的base64解密System.out.println("=========");String[] split = token.split("\\.");System.out.println(Base64Codec.BASE64.decodeToString(split[0]));System.out.println(Base64Codec.BASE64.decodeToString(split[1]));//无法解密System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
}@Test
public void testParseToken(){//tokenString token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiJGb3giLCJpYXQiOjE2MDgyNzYzMTUsImV4cCI6MTYwODI3NjM3NSwicm9sZXMiOiJhZG1pbiIsImxvZ28iOiJ4eHguanBnIn0.Geg2tmkmJ9iWCWdvZNE3jRSfRaXaR4P3kiPDG3Lb0z4";//解析token获取载荷中的声明对象Claims claims = Jwts.parser().setSigningKey("123123").parseClaimsJws(token).getBody();System.out.println("id:"+claims.getId());System.out.println("subject:"+claims.getSubject());System.out.println("issuedAt:"+claims.getIssuedAt());DateFormat sf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println("签发时间:"+sf.format(claims.getIssuedAt()));System.out.println("过期时间:"+sf.format(claims.getExpiration()));System.out.println("当前时间:"+sf.format(new Date()));System.out.println("roles:"+claims.get("roles"));System.out.println("logo:"+claims.get("logo"));
}

结果
在这里插入图片描述

1.4 Spring Security Oauth2整合JWT

整合JWT
在之前的spring security Oauth2的代码基础上修改
引入依赖

<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.0.9.RELEASE</version>
</dependency>

添加配置文件JwtTokenStoreConfig.java

@Configuration
public class JwtTokenStoreConfig {@Beanpublic TokenStore jwtTokenStore(){return new JwtTokenStore(jwtAccessTokenConverter());}@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter(){JwtAccessTokenConverter accessTokenConverter = newJwtAccessTokenConverter();//配置JWT使用的秘钥accessTokenConverter.setSigningKey("123123");return accessTokenConverter;}
}

在授权服务器配置中指定令牌的存储策略为JWT
在这里插入图片描述
用密码模式测试

在这里插入图片描述
发现获取到的令牌已经变成了JWT令牌,将access_token拿到https://jwt.io/ 网站上去解析下可以获得其中内容。
在这里插入图片描述
扩展JWT中的存储内容
有时候我们需要扩展JWT中存储的内容,这里我们在JWT中扩展一个 key为enhance,value为enhance info 的数据。
继承TokenEnhancer实现一个JWT内容增强器

public class JwtTokenEnhancer implements TokenEnhancer {@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken,OAuth2Authentication authentication) {Map<String, Object> info = new HashMap<>();info.put("enhance", "enhance info");((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);return accessToken;}
}

创建一个JwtTokenEnhancer实例

@Bean
public JwtTokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}

在授权服务器配置中配置JWT的内容增强器

@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//配置JWT的内容增强器TokenEnhancerChain enhancerChain = new TokenEnhancerChain();List<TokenEnhancer> delegates = new ArrayList<>();delegates.add(jwtTokenEnhancer);delegates.add(jwtAccessTokenConverter);enhancerChain.setTokenEnhancers(delegates);endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置.tokenStore(tokenStore)  //配置存储令牌策略.accessTokenConverter(jwtAccessTokenConverter).tokenEnhancer(enhancerChain) //配置tokenEnhancer.reuseRefreshTokens(false)  //refresh_token是否重复使用.userDetailsService(userService) //刷新令牌授权包含对用户信息的检查.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
}

运行项目后使用密码模式来获取令牌,之后对令牌进行解析,发现已经包含扩展的内容。
在这里插入图片描述
解析JWT
添加依赖

<!--JWT依赖-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

修改UserController类,使用jjwt工具类来解析Authorization头中存储的JWT内容

@GetMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication,HttpServletRequest request) {String header = request.getHeader("Authorization");String token = null;if(header!=null){token = header.substring(header.indexOf("bearer") + 7);}else {token = request.getParameter("access_token");}return Jwts.parser().setSigningKey("123123".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token).getBody();
}

将令牌放入Authorization头中,访问如下地址获取信息:
http://localhost:8080/user/getCurrentUser
在这里插入图片描述
刷新令牌
http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=client&client_secret=123123&refresh_token=[refresh_token值]
在这里插入图片描述

二 Spring Secuirty Oauth2实现SSO

创建客户端:oauth2-sso-client-demo
引入依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency><!--JWT依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

修改application.properties

server.port=8081
#防止Cookie冲突,冲突会导致登录验证不通过
server.servlet.session.cookie.name=OAUTH2-CLIENT-SESSIONID01
#授权服务器地址
oauth2-server-url: http://localhost:8080
#与授权服务器对应的配置
security.oauth2.client.client-id=client
security.oauth2.client.client-secret=123123
security.oauth2.client.user-authorization-uri=${oauth2-serverurl}/oauth/authorize
security.oauth2.client.access-token-uri=${oauth2-server-url}/oauth/token
security.oauth2.resource.jwt.key-uri=${oauth2-server-url}/oauth/token_key

在启动类上添加@EnableOAuth2Sso注解来启用单点登录功能

@EnableOAuth2Sso单点登录的原理简单来说就是:标注有@EnableOAuth2Sso的OAuth2 Client应用在通过某种OAuth2授权流程获取访问令牌后(一般是授权码流程),通过访问令牌访问userDetails用户明细这个受保护资源服务,获取用户信息后,将用户信息转换为Spring Security上下文中的认证后凭证Authentication,从而完成标注有@EnableOAuth2Sso的OAuth2 Client应用自身的登录认证的过程。整个过程是基于OAuth2的SSO单点登录

@SpringBootApplication
@EnableOAuth2Sso  
public class Oauth2SsoClientDemoApplication {public static void main(String[] args) {SpringApplication.run(Oauth2SsoClientDemoApplication.class, args);}}

添加接口用于获取当前登录用户信息

@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getCurrentUser")public Object getCurrentUser(Authentication authentication) {return authentication;}
}

授权服务器:oauth2-jwt-demo
修改授权服务器中的AuthorizationServerConfig类,将绑定的跳转路径为http://localhost:8081/login,并添加获取秘钥时的身份认证
在这里插入图片描述

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {//允许表单认证security.allowFormAuthenticationForClients()// 获取密钥需要身份认证,使用单点登录时必须配置.tokenKeyAccess("isAuthenticated()");
}@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()//配置client_id.withClient("client")//配置client-secret.secret(passwordEncoder.encode("123123"))//配置访问token的有效期.accessTokenValiditySeconds(3600)//配置刷新token的有效期.refreshTokenValiditySeconds(864000)//配置redirect_uri,用于授权成功后跳转.redirectUris("http://localhost:8081/login")//自动授权配置.autoApprove(true)//配置申请的权限范围.scopes("all")/*** 配置grant_type,表示授权类型* authorization_code: 授权码* password: 密码* client_credentials: 客户端* refresh_token: 更新令牌*/.authorizedGrantTypes("authorization_code","password","refresh_token");
}

测试
启动授权服务和客户端服务;
访问客户端需要授权的接口http://localhost:8081/user/getCurrentUser
会跳转到授权服务的登录界面;

在这里插入图片描述
授权后会跳转到原来需要权限的接口地址,展示登录用户信息
在这里插入图片描述
模拟两个客户端8081,8082
修改application.properties配置

server.port=8082
#防止Cookie冲突,冲突会导致登录验证不通过
server.servlet.session.cookie.name=OAUTH2-CLIENT-SESSIONID${server.port}

修改授权服务器配置,配置多个跳转路径

//配置redirect_uri,用于授权成功后跳转
.redirectUris("http://localhost:8081/login","http://localhost:8082/login")

8081登录成功之后,8082无需再次登录就可以访问http://localhost:8082/user/getCurrentUser

三 Spring Cloud中如何实现SSO

网关整合 OAuth2.0 有两种思路,一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断权限等操作;另一种是由各资源服务处理,网关只做请求转发。 比较常用的是第一种,把API网关作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息给微服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。

网关在认证授权体系里主要负责两件事:
(1)作为OAuth2.0的资源服务器角色,实现接入方权限拦截。
(2)令牌解析并转发当前登录用户信息(明文token)给微服务
微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
(1)用户授权拦截(看当前用户是否有权访问该资源)
(2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)
在这里插入图片描述
核心代码,网关自定义全局过滤器进行身份认证

@Component
@Order(0)
public class AuthenticationFilter implements GlobalFilter, InitializingBean {@Autowiredprivate RestTemplate restTemplate;private static Set<String> shouldSkipUrl = new LinkedHashSet<>();@Overridepublic void afterPropertiesSet() throws Exception {// 不拦截认证的请求shouldSkipUrl.add("/oauth/token");shouldSkipUrl.add("/oauth/check_token");shouldSkipUrl.add("/user/getCurrentUser");}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String requestPath = exchange.getRequest().getURI().getPath();//不需要认证的urlif(shouldSkip(requestPath)) {return chain.filter(exchange);}//获取请求头String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");//请求头为空if(StringUtils.isEmpty(authHeader)) {throw new RuntimeException("请求头为空");}TokenInfo tokenInfo=null;try {//获取token信息tokenInfo = getTokenInfo(authHeader);}catch (Exception e) {throw new RuntimeException("校验令牌异常");}exchange.getAttributes().put("tokenInfo",tokenInfo);return chain.filter(exchange);}private boolean shouldSkip(String reqPath) {for(String skipPath:shouldSkipUrl) {if(reqPath.contains(skipPath)) {return true;}}return false;}private TokenInfo getTokenInfo(String authHeader) {// 获取token的值String token = StringUtils.substringAfter(authHeader, "bearer ");HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);headers.setBasicAuth(MDA.clientId, MDA.clientSecret);MultiValueMap<String, String> params = new LinkedMultiValueMap<>();params.add("token", token);HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);ResponseEntity<TokenInfo> response = restTemplate.exchange(MDA.checkTokenUrl, HttpMethod.POST, entity, TokenInfo.class);return response.getBody();}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python web 开发与 Node.js + Express 创建web服务器入门

目录 1. Node.js Express 框架简介 2 Node.js Express 和 Python 创建web服务器的对比 3 使用 Node.js Express 创建web服务器示例 3.1 Node.js Express 下载安装 3.2 使用Node.js Express 创建 web服务器流程 1. Node.js Express 框架简介 Node.js Express 是一种…

机器学习---决策树的划分依据(熵、信息增益、信息增益率、基尼值和基尼指数)

1. 熵 物理学上&#xff0c;熵 Entropy 是“混乱”程度的量度。 系统越有序&#xff0c;熵值越低&#xff1b;系统越混乱或者分散&#xff0c;熵值越⾼。 1948年⾹农提出了信息熵&#xff08;Entropy&#xff09;的概念。 从信息的完整性上进⾏的描述&#xff1a;当系统的有序…

myspl使用指南

mysql数据库 使用命令行工具连接数据库 mysql -h -u 用户名 -p -u表示后面是用户名-p表示后面是密码-h表示后面是主机名&#xff0c;登录当前设备可省略。 如我们要登录本机用户名为root&#xff0c;密码为123456的账户&#xff1a; mysql -u root -p按回车&#xff0c;然后…

大数据组件-Flume集群环境的启动与验证

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 个人主页&#xff1a;beixi 本文章收录于专栏&#xff08;点击传送&#xff09;&#xff1a;【大数据学习】 &#x1f493;&#x1f493;持续更新中&#xff0c;感谢各位前辈朋友们支持…

gitlab-rake gitlab:backup:create 执行报错 Errno::ENOSPC: No space left on device

gitlab仓库备份执行 gitlab-rake gitlab:backup:create报错如下&#xff1a; 问题分析&#xff1a;存储备份的空间满 解决方法&#xff1a; 方法1&#xff1a;清理存放路径&#xff0c;删除不需要文件&#xff0c;释放空间。 方法2&#xff1a;创建一个根目录的挂载点&#x…

八一参考文献:[八一新书]许少辉.乡村振兴战略下传统村落文化旅游设计[M]北京:中国建筑出版传媒,2022.

八一参考文献&#xff1a;&#xff3b;八一新书&#xff3d;许少辉&#xff0e;乡村振兴战略下传统村落文化旅游设计&#xff3b;&#xff2d;&#xff3d;北京&#xff1a;中国建筑出版传媒&#xff0c;&#xff12;&#xff10;&#xff12;&#xff12;&#xff0e;

机器视觉工程师,有哪几种类型

1.光学实验室&#xff08;打光机器视觉工程师&#xff0c;一般此职位&#xff0c;要求有光学学历的背景最佳&#xff09; 2.机器视觉算法开发工程师&#xff08;此职位国内稀缺&#xff09;3.机器视觉工程师/机器视觉开发工程师&#xff08;MV工程师/MV工程师&#xff09;&…

常见项目管理中npm包操作总结

前言 我们在日常工作中&#xff0c;可能需要下载包、创建包、发布包等等。本篇推文将记录日常项目中关于npm包的操作。 引用包 npm仓库公开的包我们都可以通过npm install的命令进行引用下载。 而我们开发的业务公共组件需要在公司内部项目公共引用&#xff0c;而不希望公开为外…

Android——基本控件(下)(二十)

1. 树型组件&#xff1a;ExpandableListView 1.1 知识点 &#xff08;1&#xff09;掌握树型组件的定义&#xff1b; &#xff08;2&#xff09;可以使用事件对树操作进行监听。 2. 具体内容 既然这个组件可以完成列表的功能&#xff0c;肯定就需要一个可以操作的数据&…

el-select 选择一条数据后,把其余数据带过来

1. 案例&#xff1a; ps: 票号是下拉框选择&#xff0c;风险分类、场站名称以及开始时间是选择【票号】后带过来的。 2. 思路: 使用官网上给的方法&#xff0c;选择之后&#xff0c;触发change方法从而给其余字段赋值 3. 代码 <el-form-itemlabel"票号&#xff1a;&…

(leetcode1761一个图中连通三元组的最小度数,暴力+剪枝)-------------------Java实现

&#xff08;leetcode1761一个图中连通三元组的最小度数&#xff0c;暴力剪枝&#xff09;-------------------Java实现 题目表述 给你一个无向图&#xff0c;整数 n 表示图中节点的数目&#xff0c;edges 数组表示图中的边&#xff0c;其中 edges[i] [ui, vi] &#xff0c;…

2023_Spark_实验四:SCALA基础

一、在IDEA中执行以下语句 或者用windows徽标R 输入cmd 进入命令提示符 输入scala直接进入编写界面 1、Scala的常用数据类型 注意&#xff1a;在Scala中&#xff0c;任何数据都是对象。例如&#xff1a; scala> 1 res0: Int 1scala> 1.toString res1: String 1scala…

linux安装firefox

1.下载对应包 https://www.mozilla.org/en-US/firefox/all/#product-desktop-release 2. 挂载桌面链接(如果/usr/bin/firefox下有的话,先删除) ln -s /opt/firefox/firefox /usr/bin/firefox 3.执行以下命令&#xff0c;即可启动Firefox客户端&#xff1a; firefox

mybatis源码学习-3-解析器模块

写在前面,这里会有很多借鉴的内容,有以下三个原因 本博客只是作为本人学习记录并用以分享,并不是专业的技术型博客笔者是位刚刚开始尝试阅读源码的人,对源码的阅读流程乃至整体架构并不熟悉,观看他人博客可以帮助我快速入门如果只是笔者自己观看,难免会有很多弄不懂乃至理解错误…

WGCNA分析教程 | 代码四

写在前面 WGCNA的教程&#xff0c;我们在前期的推文中已经退出好久了。今天在结合前期的教程的进行优化一下。只是在现有的教程基础上&#xff0c;进行修改。其他的其他并无改变。 前期WGCNA教程 WGCNA分析 | 全流程分析代码 | 代码一 WGCNA分析 | 全流程分析代码 | 代码二 …

SpringCloudGateway集成SpringDoc

SpringCloudGateway集成SpringDoc 最近在搞Spring版本升级&#xff0c;按客户要求升级Spring版本&#xff0c;原来用着SpringBoot 2.2.X版本&#xff0c;只需要升级SpringBoot 2.X最新版本也就可以满足客户Spring版本安全要求&#xff0c;可是好像最新的SpringBoot 2.X貌似也不…

Ubuntu入门03——Ubuntu用户操作

1.Ubuntu如何进入root用户 进入ROOT用户的指令&#xff1a; Linux用su命令来切换用户&#xff1a; su root执行命令后&#xff0c;会提示你输入密码&#xff0c;而Ubuntu是没有设置root初始密码的。 若su命令不能切换root&#xff0c;提示su: Authentication failure&#x…

React笔记(三)类组件(1)

一、组件的概念 使用组件方式进行编程&#xff0c;可以提高开发效率&#xff0c;提高组件的复用性、提高代码的可维护性和可扩展性 React定义组件的方式有两种 类组件&#xff1a;React16.8版本之前几乎React使用都是类组件 函数组件:React16.8之后&#xff0c;函数式组件使…

C++面试题(期)-数据库(二)

目录 1.3 事务 1.3.1 说一说你对数据库事务的了解 1.3.2 事务有哪几种类型&#xff0c;它们之间有什么区别&#xff1f; 1.3.3 MySQL的ACID特性分别是怎么实现的&#xff1f; 1.3.4 谈谈MySQL的事务隔离级别 1.3.5 MySQL的事务隔离级别是怎么实现的&#xff1f; 1.3.6 事…

机器学习基础17-基于波士顿房价(Boston House Price)数据集训练模型的整个过程讲解

机器学习是一项经验技能&#xff0c;实践是掌握机器学习、提高利用机器学习 解决问题的能力的有效方法之一。那么如何通过机器学习来解决问题呢&#xff1f; 本节将通过一个实例来一步一步地介绍一个回归问题。 本章主要介绍以下内容&#xff1a; 如何端到端地完成一个回归问题…