文章目录

  • 前言
    • 分布式架构为什么需要token?
    • 服务架构简介
  • 1.环境基本配置和InMemory模式实现
    • 1.0授权中心
      • 属性解释
    • 1.1客户端详情(ClientDetailsService)配置
      • 属性解释
    • 1.2配置Token访问端点(AuthorizationServerEndpoints)
      • 1.2.1Token管理服务(AuthorizationServerTokenServices)
        • 配置TokenStore
        • 配置AuthorizationServerTokenServices
      • 1.2.2配置其他所需类的Bean
        • 注入AuthorizationCodeServices
        • 注入1.2.1节配置的AuthorizationServerTokenServices Bean
        • 注入从SpringSecurity搬过来的UserDetailsService
      • 1.2.3正题:配置AuthorizationServerEndpoints
      • 属性解释
        • **四大授权类型(Grant Types)**
        • **配置授权端点的URL(Endpoint URLs)**
    • 1.3Token端点安全约束(AuthorizationServerSecurity)
    • 1.4SpringSecurity配置
    • 跑不起来?
  • 2.五种基本授权模式测试
    • 2.1授权码模式
      • 获取授权码链接
      • 获取令牌
    • 2.2.简化模式
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U19l4cZB-1666257626416)(C:\Users\zjy\Documents\MD\OAuth\简化模式.png)]
    • 2.3密码模式
    • 2.4客户端模式
    • 2.5资源模式
  • 3.资源服务器改造
    • 依赖
    • token解析服务
    • 配置类
  • 4.JWT令牌
    • 什么是JWT?
    • JWT令牌的优点:
    • JWT令牌的缺点:
    • JWT令牌结构
      • Header
      • Payload
      • Signature

前言

分布式架构为什么需要token?

Session是存储在服务器端的,当浏览器第一次请求Web服务器,服务器会产生一个Session存放在服务器里(可持久化到数据库中),然后通过响应头的方式将SessionID返回给浏览器写入到Cookie中,浏览器下次请求就会将SessiondID以Cookie形式传递给服务器端,服务器端获取SessionID后再去寻找对应的Session。如果找到了则代表用户不是第一次访问,也就记住了用户。

但需要注意的是,若服务器做了负载均衡,用户的下一次请求可能会被定向到其它服务器节点,若那台节点上没有用户的Session信息,就会导致会话验证失败。所以Session默认机制下是不适合分布式部署的

服务架构简介

OAuth 2.0为用户和应用定义了如下角色:

  • 资源拥有者
  • 资源服务器
  • 客户端应用
  • 授权服务器
  • 这些角色在下图中表示为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8T06jGlW-1666257626413)(https://atts.w3cschool.cn/attachments/image/wk/oauth2/3.1.png)]

1.环境基本配置和InMemory模式实现

导入依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId><version>2.2.5.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId></dependency>

1.0授权中心

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q53f98rL-1666257626414)(C:\Users\zjy\Documents\MD\授权中心(AuthorizationServerConfigurerAdapter).png)]

这里我们先继承该类,重写和内容丰富将在下面的章节完成

package com.zjy.security.config;import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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;@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {public AuthorizationServerConfig() {super();}//客户端详情配置,可以写死也可以通过数据库查询@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {}// 配置Token访问端点(url)和Token服务(生成Token)@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {}//Token端点安全约束@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {}}

属性解释

  • ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在 这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
  • AuthorizationServerEndpointsConfigurer:用来配置令牌(token)的访问端点和令牌服务(token services)。
  • AuthorizationServerSecurityConfigurer:用来配置令牌端点的安全约束.

1.1客户端详情(ClientDetailsService)配置

该配置通过重写授权中心中方法完成

//客户端详情配置,可以写死也可以通过数据库查询@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("Clinet01").secret(new BCryptPasswordEncoder().encode("password"))//秘钥.resourceIds("resource01").authorizedGrantTypes() //客户端默认权限类型.scopes("all") //授权范围.authorities() //可缺省,看需求.autoApprove(false) //是否不跳转到授权页面.redirectUris("/index");//验证回调地址}

属性解释

  • clientId:(必须的)用来标识客户的Id。

  • secret:(需要值得信任的客户端)客户端安全码,如果有的话。

  • scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。

  • authorizedGrantTypes:此客户端可以使用的授权类型,默认为空。

    authorization_code — 授权码模式(即先登录获取code,再获取token)

    password — 密码模式(将用户名,密码传过去,直接获取token)
    client_credentials — 客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向’服务端’获取资源)
    implicit — 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)

    refresh_token — 刷新access_token

    ​ (这里的关键字我在源代码并没有找到,希望能有大佬能指点一下这些关键字(大概率是枚举)的位置)

  • authorities:此客户端可以使用的权限(基于Spring Security authorities)

1.2配置Token访问端点(AuthorizationServerEndpoints)

1.2.1Token管理服务(AuthorizationServerTokenServices)

这个Bean的创建根本上是为了下一步配置Token访问节点的需要。

自己可以创建 AuthorizationServerTokenServices 这个接口的实现,则需要继承 DefaultTokenServices 这个类。而持久化令牌是委托一个 TokenStore 接口来实现。

配置TokenStore

package com.zjy.security.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;@Configuration
public class TokenConfig {@Beanpublic TokenStore tokenStore(){return  new InMemoryTokenStore();}
}

配置AuthorizationServerTokenServices

在授权中心创建Token管理服务(AuthorizationServerTokenServices)的Bean

    @Resourceprivate TokenStore tokenStore;@Resourceprivate ClientDetailsService clientDetailsService;@Bean//令牌管理服务public AuthorizationServerTokenServices tokenServices() {DefaultTokenServices services = new DefaultTokenServices();services.setClientDetailsService(clientDetailsService);services.setSupportRefreshToken(true);services.setTokenStore(tokenStore);services.setAccessTokenValiditySeconds(3600);services.setRefreshTokenValiditySeconds(259200);return services;}

哪来的ClientDetailsService Bean呢?其实这里@EnableAuthorizationServer注解中会引入配置类ClientDetailsServiceConfiguration,该配置类会创建一个默认的ClientDetailsService

这里我们如果想要自定义一个ClientDetailsService的Bean,需要在类里加上@Primary把该类当成是主类,否则会导致循环。样例如下:

@Service
@Primary
public class CustomClientDetailService implements ClientDetailsService {@Resourceprivate PasswordEncoder passwordEncoder;@Overridepublic ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {BaseClientDetails clientDetails = new BaseClientDetails(clientId, "resource_id", "all,select", "authorization_code,client_credentials,refresh_token,password", "aut", "http://www.baidu.com");clientDetails.setClientSecret(passwordEncoder.encode("secret_" + clientId));return clientDetails;}
}

1.2.2配置其他所需类的Bean

注入AuthorizationCodeServices

这里我们选择"authorization_code" 授权码类型模式。同样是在授权中心,配置Bean,当场注入

@Resourceprivate AuthorizationCodeServices authorizationCodeServices;@Bean
public AuthorizationCodeServices authorizationCodeServices(){return new InMemoryAuthorizationCodeServices();
}

注入1.2.1节配置的AuthorizationServerTokenServices Bean

@Resource
private AuthorizationServerTokenServices tokenServices;

注入从SpringSecurity搬过来的UserDetailsService

代码如下

package com.zjy.security.service;import com.zjy.security.mapper.UserMapper;
import com.zjy.security.pojo.MyUser;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.List;@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {@Resourceprivate UserMapper usersMapper;AuthenticationManager authenticationManager(){return new OAuth2AuthenticationManager();}@Overridepublic UserDetails loadUserByUsername(String username) throwsUsernameNotFoundException {MyUser user=usersMapper.getUserByName(username);if(user == null) {throw new UsernameNotFoundException("用户名不存在!");}System.out.println(user);//角色需要前缀“ROLE_”,权限不需要,角色和权限全部写进一个字符串,用逗号分隔List<GrantedAuthority> auths =AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_vip1,ROLE_vip2");//**********************************//如果权限和角色也写进数据库呢?//查出角色和权限后,遍历进行如下写入://auths.add(new SimpleGrantedAuthority(" "));//***********************************return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auths);}
}

授权中心注入:

@Resource
private MyUserDetailsService userDetailsService;

1.2.3正题:配置AuthorizationServerEndpoints

接下来就是正题:配置AuthorizationServerEndpoints

    // 配置Token访问端点(url)和Token服务(生成Token)@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(new OAuth2AuthenticationManager()).authorizationCodeServices(authorizationCodeServices).tokenServices(tokenServices).userDetailsService(userDetailsService).allowedTokenEndpointRequestMethods(HttpMethod.GET)//.pathMapping("/oauth/authorize","/myAuth/authorize");}

属性解释

四大授权类型(Grant Types)

AuthorizationServerEndpointsConfigurer 通过设定以下属性决定支持的授权类型(Grant Types):

  • authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置 这个属性注入一个 AuthenticationManager 对象。
  • userDetailsService:如果你设置了这个属性的话,那说明你有一个自己的 UserDetailsService 接口的实现, 或者你可以把这个东西设置到全局域上面去(例如 GlobalAuthenticationManagerConfigurer 这个配置对 象),当你设置了这个之后,那么 “refresh_token” 即刷新令牌授权类型模式的流程中就会包含一个检查,用来确保这个账号仍然有效。
  • authorizationCodeServices:这个属性是用来设置授权码服务的(即 AuthorizationCodeServices 的实例对 象),主要用于 “authorization_code” 授权码类型模式。
  • implicitGrantService:这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。
  • tokenGranter:当你设置了这个东西(即 TokenGranter 接口实现),那么授权将会交由你来完全掌控,并 且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的 需求的时候,才会考虑使用这个。

配置授权端点的URL(Endpoint URLs)

AuthorizationServerEndpointsConfigurer 这个配置对象有一个叫做 pathMapping() 的方法用来配置端点URL链 接,它有两个参数:

  • 第一个参数:String 类型的,这个端点URL的默认链接。
  • 第二个参数:String 类型的,你要进行替代的URL链接。

以上的参数都将以 “/” 字符为开始的字符串,框架的默认URL链接如下列表,可以作为这个 pathMapping() 方法的 第一个参数:

  • /oauth/authorize:授权端点。
  • /oauth/token:令牌端点。
  • /oauth/confirm_access:用户确认授权提交端点。
  • /oauth/error:授权服务错误信息端点。
  • /oauth/check_token:用于资源服务访问的令牌解析端点。
  • /oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。

需要注意的是授权端点这个URL应该被Spring Security保护起来只供授权用户访问.

1.3Token端点安全约束(AuthorizationServerSecurity)

//Token端点安全约束
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()")//具体权限写法应该同security.allowFormAuthenticationForClients();//申请Token}

1.4SpringSecurity配置

package com.zjy.security.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;import javax.annotation.Resource;
import javax.sql.DataSource;@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/static/index.html").permitAll().antMatchers("/level1/**").hasRole("vip1").antMatchers("/level2/**").hasRole("vip2").antMatchers("/level3/**").hasRole("vip3");//配置认证http.formLogin().loginPage("/index") // 配置哪个 url 为登录页面.loginProcessingUrl("/login") // 设置哪个是登录的 url。.successForwardUrl("/success") // 登录成功之后跳转到哪个 url.failureForwardUrl("/fail").and().logout().deleteCookies().invalidateHttpSession(true).and().rememberMe().tokenValiditySeconds(60*60);}
}

跑不起来?

尝试加入下述配置

<dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>3.0.2</version>
</dependency>

至此,授权中心配置完毕。

2.五种基本授权模式测试

2.1授权码模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifnNZl2b-1666257626415)(C:\Users\zjy\Documents\MD\OAuth\授权码模式.png)]

由于配置时我选择的获取令牌请求为get(方便测试),这里我们通过在浏览器直接键入URL发起请求。

获取授权码链接

(我这里redirect_uri=www.baidu.com是为了与授权中心配置的redirectUris一致)

http://localhost:8080/oauth/authorize?client_id=Clinet01&response_type=code&scope=all&redirect_uri=www.baidu.com

转到,跳转到页面:

http://localhost:8080/oauth/www.baidu.com?code=U58iIN

得到授权码:U58iIN

获取令牌

http://localhost:8080/oauth/token?client_id=Clinet01&client_secret=password&grant_type=authorization_code&code=U58iIN&redirect_uri=www.baidu.com

页面显示:

{"access_token":"这里是字符串","token_type":"bearer","expires_in":3599,"scope":"all"}

2.2.简化模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U19l4cZB-1666257626416)(C:\Users\zjy\Documents\MD\OAuth\简化模式.png)]

请求:

http://localhost:8080/oauth/authorize?client_id=Clinet01&response_type=token&scope=all&redirect_uri=www.baidu.com

页面显示:

http://localhost:8080/oauth/www.baidu.com#access_token=e1df8f0b-e430-429b-a9d9-4405bab301f0&token_type=bearer&expires_in=3599

直接获取了令牌

2.3密码模式

http://localhost:8080/oauth/token?client_id=Clinet01&client_secret=password&grant_type=password&username=111&password=111&redirect_uri=www.baidu.com

2.4客户端模式

http://localhost:8080/oauth/token?client_id=Clinet01&client_secret=password&grant_type=client_credentials&redirect_uri=www.baidu.com

2.5资源模式

3.资源服务器改造

基于事先写过的一个常规三层架构provider,将其改造成符合OAuth的资源服务器。实际上并不需要更改其中原有代码,只需要加入两个配置类

依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId><version>2.2.5.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId></dependency>

token解析服务

package com.zjy.dataprovider8000.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;@Configuration
public class TokenResolveServiceConfig {@Bean(name="tokenServices")public RemoteTokenServices tokenServices(){RemoteTokenServices tokenServices=new RemoteTokenServices();tokenServices.setClientSecret("password");tokenServices.setCheckTokenEndpointUrl("http://localhost:9500"+"/oauth/check_token");tokenServices.setClientId("Clinet01");return tokenServices;}}

配置类

package com.zjy.dataprovider8000.config;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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.RemoteTokenServices;import javax.annotation.Resource;@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@ResourceRemoteTokenServices tokenServices;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.resourceId("resource01")//这里要和授权中心配置的资源名相对应.tokenServices(tokenServices)//验证令牌服务.stateless(true);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/swagger-ui.html","/swagger-resources/**","/v2/api-docs","/oauth/check_token").permitAll().antMatchers("/**").access("#oauth2.hasScope('all')").and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//关闭session}
}

按照oauth2.0协议要求,请求资源需要在Header里携带token,具体如下:
token的参数名称为:Authorization,值为:Bearer [token值]

手动获取token后用postman发起请求,通过,验证成功。

这里如果报401:nobody之类的错误,优先检查自己资源服务器和授权中心写死的验证信息是否有误,笔者这里单词写错导致第一次测试不通过

如果需要更细粒度的权限控制,在授权中心可以给令牌配置不同权限,在资源服务器处用antMatchers()拦截。

4.JWT令牌

什么是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)JWT令牌较长,占存储空间比较大。

JWT令牌结构

通过学习JWT令牌结构为自定义jwt令牌打好基础。
JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz

Header

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

将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。

Payload

第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比 如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
一个例子:

{"sub": "1234567890", "name": "456", "admin": true
}

Signature

第三部分是签名,此部分用于防止jwt内容被篡改。
这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明 签名算法进行签名。
一个例子:

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)

OAuth2实现安全登录(一)环境配置相关推荐

  1. 使用Vue-cli从零开始搭建Vue全家桶(仿b站客户端)项目(1.环境配置、实现登录功能)

    1.前言         技术栈:Vue全家桶+Element.ui组件库+Axios 功能:具有登录.配置个人信息.修改个人头像.发布评论.发布动态等功能 话不多说,先看成品动图,也可点击此链接进行 ...

  2. Spring Security OAuth2 SSO 单点登录

    基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...

  3. linux 编译opencl,OpenCL编译环境配置(VS+Nvidia)

    英伟达的显卡首先要下载安装CUDA开发包,可以参考这里的步骤:   VS2015编译环境下CUDA安装配置 安装好CUDA之后,OpenCL的配置就已经完成了80%了,剩下的工作就是把OpenCL的路 ...

  4. 安装JDK1.8+环境配置

    安装JDK1.8+环境配置 1.下载JDK 2.安装JDK 3.环境配置 3.1 新建系统变量 3.2 添加Path路径 3.3 使用cmd命令行验证是否环境配置成功 1.下载JDK 直接官网下载:h ...

  5. Docker环境配置指南!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 来源:Tianchi,内容:Docker环境配置 Docker是一个开源 ...

  6. 【踩坑之路】CentOS 下 LNMN 环境配置

    2019独角兽企业重金招聘Python工程师标准>>> LNMN 环境代表 Linux 系统下 Nginx + MySQL + Nodejs 网站服务器架构.本文档介绍 CentOS ...

  7. Laravel教程 一:安装及环境配置

    Laravel教程 一:安装及环境配置 此文章为原创文章,未经同意,禁止转载. Homestead 最近在SF上面看到越来越多的Laravel相关的问题,而作为一个Laravel的脑残粉,本来打算有机 ...

  8. Mysql-cluster环境配置

    Mysql-cluster环境配置 硬件环境 4台机器 192.168.1.10       管理节点 192.168.1.11           存储节点 SQL节点 192.168.1.12   ...

  9. 海思芯片固件修改工具_地平线X3开发板开箱、固件刷写、环境配置

    经过一个多月,我终于抽出时间去写这个系列的文章.很开心可以参加这次地平线举办的开发板体验活动.最开始拿到的是1.1.6版本的工具链以及103版本的镜像,而官方在8月发布了0827的镜像以及1.1.12 ...

最新文章

  1. 杭电多校第三场-H-Game
  2. 《淘宝网开店 SEO 推广 营销 爆款 实战200招》——1.5 开一家适合你自身发展的店铺...
  3. 初中学历做开发,3年在北京买了房,超过了99%的程序员!
  4. fedora27安装谷歌浏览器Chrome
  5. EMC -- DFS篇(Documentum Foundation Services)
  6. java中断线程_Java线程中断机制-如何中断线程
  7. 第17讲 | 去中心化与区块链交易性能
  8. java referencemap_Java中关于WeakReference和WeakHashMap的理解
  9. HTMLCSS入门学习
  10. Web全栈工程师应该会什么
  11. 前端基础:通过HTML技术布局《李白诗词赏析》
  12. Mac电脑 如何在任意目录快速打开终端并定位到当前目录
  13. Java校招基础知识总结(横扫BAT,就业经验交流会演讲稿)
  14. 解决Linux下adb devices找不到设备
  15. 基于Apache APISIX,新浪微博API网关的定制化开发之路
  16. 我妹妹成了我的女儿——难道我真的中年大叔了?
  17. Java实现输出特殊偏旁的汉字的功能
  18. java任务监控进度控制
  19. 【其他】Tensorflow分布式使用简介
  20. 解决minGW32-make 编译opencv时 error: ‘std::_hypot‘ has not been declared using std::hypot;

热门文章

  1. oa项目经验描述_简历中项目经验模版
  2. Flutter访问webservice
  3. Android百度地图
  4. 十分nb且详细的Elasticsearch教程
  5. ORACLE的连接模式——专用服务器和共享服务器
  6. Redis Stream
  7. clientX,offsetX,pageX,screenX的异同以Chrome浏览器测试
  8. python运行出错时打印错误提示信息
  9. Civil3D 2018-02 点
  10. 如何动手搭建一套简单的直播系统