CAS单点登录-简介

CAS 简介

CAS ( Central Authentication Service ) 是 Yale 大学发起的一个企业级的、开源的项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方法(属于 Web SSO )。

SSO 简介

单点登录( Single Sign-On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统中,用户只需要 登录一次 就可以访问所有相互信任的应用系统。

CAS 的基本原理

从结构体系看, CAS 包括两部分: CAS Server 和 CAS Client 。(服务端/客户端)

服务端CAS Server 负责完成对用户的认证工作 , 需要独立部署 , CAS Server 会处理用户名 / 密码等凭证(Credentials) 。

客户端CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。CAS Client 负责处理对客户端受保护资源的访问请求,需要对请求方进行身份认证时,重定向到 CAS Server 进行认证。(原则上,客户端应用不再接受任何的用户名密码等 Credentials )。

基于CAS的SSO访问流程步骤:

访问服务: CAS Client 客户端发送请求访问应用系统提供的服务资源。

定向认证: CAS Client 客户端会重定向用户请求到 CAS Server 服务器。

用户认证: 用户在浏览器端输入用户验证信息,CAS Server服务端完成用户身份认证。

发放票据: CAS Server服务器会产生一个随机的 Service Ticket 。

验证票据: CAS Server服务器验证票据 Service Ticket 的合法性,验证通过后,允许客户端访问服务。

传输用户信息: CAS Server 服务器验证票据通过后,传输用户认证结果信息给客户端。

下面是 CAS 最基本的协议过程:

搭建CAS Server

首先下载cas-overlay,进入cas的git项目,选择版本,下载zip包并解压。(要求至少为jdk8,tomcat 8+)

https://github.com/apereo/cas-overlay-template

本demo中使用的cas server版本为4.1(maven版本),较高版本的server(如5.3)在代码拉取后自带了脚本,可直接用于打包构建。

(5.3之后的都是gradle项目,5.3以之前都是maven 项目,build.cmd为win版自动构建+运行的脚本,sh为linux版)

下面说明不使用脚本的server构建方式。

使用idea打开项目后,在较新的版本中,cas的很多配置都放在了cas.properties里,这个文件可以放在服务器的任何位置,但是需要修改propertyFileConfigurer.xml这个配置文件的目标路径,以告诉cas系统cas.properties需要去哪个位置加载。

下述截图标明了配置文件路径,右侧需对应修改cas.properties配置文件的存放路径,demo中我将配置文件放置于E:\prop中,直接修改路径即可。

点击package将本项目打包,会生成一个上图路径target中的war文件:cas.war。该文件即打包好的cas server文件,将该war放入tomcat安装路径下的webapps文件夹中,启动tomcat,便会同时部署web应用程序cas server。

由于我的tomcat端口由8080修改为9527,当tomcat启动后,访问路径 http://localhost:9527/cas/login ,能见到cas登录页面时,说明本demo的cas服务端已安装部署成功。此时可尝试输入测试账号及密码,测试是否能实现登录功能。(静态账号casuser,密码Mellon)

也可以自己修改账号密码,在deployerConfigContext.xml文件内进行修改,如下图

至此,CAS Server的demo服务端搭建已完成。

编写CAS Client

为验证单点登录的有效性,新建立两个springboot项目,作为客户端2和客户端3。分别完成以下代码编写。当两个客户端及服务端1均启动时,若客户端2登录后,客户端3只需刷新页面即可同时处于已登录状态。

①引入CAS client依赖

在pom.xml中引入CAS Client的依赖包。代码如下:

<dependency><groupId>net.unicon.cas</groupId><artifactId>cas-client-autoconfig-support</artifactId><version>2.3.0-GA</version></dependency>

②配置

在application.properties或者application.yml中添加相关配置,主要配置内容包括服务器的相关地址,客户端的相关地址等。我这里是application.yml,配置内容如下:

(注意配置时的端口区分,本demo中,客户端2采用端口8890,客户端3采用端口9990)

cas:
#后端服务地址
client-host-url: http://127.0.0.1:8890
#cas认证中心地址
server-url-prefix: http://127.0.0.1:9527/cas
#cas认证中心登录地址
server-login-url: http://127.0.0.1:9527/cas/login
#Ticket校验器使用Cas30ProxyReceivingTicketValidationFilter
validation-type: cas3

③在启动类中添加启用注解

//启用CAS@EnableCasClient@SpringBootApplicationpublic class SpringBootSsoApplication { //省略部分内容 }

④编写测试接口Controller层

import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;/**
* Author Haozhonghao
* Date 2021/12/20 10:00
* Version 1.0
*/
@RequestMapping("/casTest2")
@Controller
public class CASTestController {
@Value(value = "${cas.server-url-prefix}")
private String serverUrlPrefix = "";
@Value(value = "${cas.client-host-url}")
private String clientHostUrl = "";@GetMapping("/user2")
@ResponseBody
public String user(HttpServletRequest request) {
Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
String loginName = null;
if (assertion != null) {
AttributePrincipal principal = assertion.getPrincipal();
loginName = principal.getName();
System.out.println("访问者2:" + loginName);
}
return "访问者2:" + loginName;
}@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:" + serverUrlPrefix + "/logout?service=" + clientHostUrl + "/casTest2/user2";
}@GetMapping("/test2")
public String test() {
return "test2....";
}
}

实现CAS Client单点登录过程

测试过程:

初步测试时,开启服务端1、客户端2和客户端3。打开浏览器,输入地址http://127.0.0.1:9990/casTest3/user3,出现如下报错:(输入客户端2的接口地址同样报错)

显示错误信息:权限配置问题

  1. Application Not Authorized to Use CAS
  2. The application you attempted to authenticate toisnot authorized to use CAS.

解决办法:

修改Tomcat/webapps/cas/WEB-INF/classes/services目录下的HTTPSandIMAPS-10000001.Json文件:直接复制替换

​
{"@class": "org.jasig.cas.services.RegexRegisteredService","serviceId" : "^(https|http|imaps)://.*","name": "https://localhost","id": 1,"evaluationOrder": 0,"logoutType": "BACK_CHANNEL","proxyPolicy" : {"allowedToProxy": true,"@class" : "org.jasig.cas.services.RegexMatchingRegisteredServiceProxyPolicy","pattern" : "^(https?)://localhost.*"}}​

修改后重启服务端及客户端

此时访问客户端2接口地址,能够正常显示如下界面,允许输入用户名及密码,便于测试仍然使用默认账号及密码(账号:casuser,密码:Mellon)

输入相应用户名及密码后,登陆成功:

输入客户端3接口地址后访问,直接登陆成功

单点登录简易demo效果至此实现成功。为形成完整的登录登出流程,后续还需要实现cas单点登出。

注:图所显示的错误Non-secure Connection,是由于没有使用HTTPS协议的关系,而默认的登陆界面有对此进行验证的代码,而在实际项目中的登陆界面一般需要自己写,通过修改webapps\cas\WEB-INF\view\jsp\default\ui下的casLoginView.jsp即可。将下图所示代码删掉即可去除错误警告。

实现CAS Client单点登出过程

实现单点登出,需要在前文代码及配置的基础上进行。

首先需要添加服务端配置。

CAS服务端需进行相应的更改,在cas.properties中将下述配置改为true(若没有下述条目配置,则直接复制粘贴入文件中),添加配置后方可实现登出后的重定向,重定向地址为测试接口Controller层中编写的地址,当调用logout登出后,cas服务端发现该客户端不在登录状态,就会再次跳转到登录页,成功登录之后,便会再次跳转到目标接口路径。

# Specify whether CAS should redirect to the specified service parameter on /logout requests

cas.logout.followServiceRedirects=true

如果不添加此配置,就无法实现登出后的跳转,效果图如下(仅显示登出成功,而不会跳转到登录页)

其次是要注意测试接口Controller层中的注解使用。注意不要使用@RestController注解本类,而是使用@Controller注解,否则会导致重定向地址仅仅渲染在页面上,而不会实现页面跳转。

原因分析

@RestController注解相当于@ResponseBody + @Controller合在一起的作用

重定向失效即是@ResponseBody注解引起的。 @ResponseBody是作用在方法上的,@ResponseBody 表示该方法的返回结果直接写入 HTTP response body 中,一般在异步获取数据时使用【也就是AJAX】,在使用 @RequestMapping后,返回值通常解析为跳转路径,但是加上 @ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。 比如异步获取 json 数据,加上 @ResponseBody 后,会直接返回 json 数据。@RequestBody 将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。

不会被解析成跳转路径,那么 return "redirect:” 重定向也就失效了。

因此解决方法就是把@RestController改成@Controller注解。

解决上述两点问题之后,重新启动服务端1、客户端2、客户端3。

实现单点登录操作后,任选其中一方,访问其logout接口:

访问成功后,客户端2页面成功跳转至登录界面,回到客户端3,刷新页面,由于单点登出的效果,客户端3也处于离线状态,单点登出效果实现成功!

实际cas单点登录的应用场景,不可能只使用一个测试账号登录,因此需要实现cas与数据库相结合,实现自定义账号的单点登录登出,方有实际使用意义。

结合mysql数据库,实现CAS单点登出的客户及服务的交互过程

主要修改部分为 CAS Server的配置部分,通过在服务端配置数据源及相关验证,实现自定义用户及密码的登录替换,具体步骤及实施方式如下:

①新增cas登录的相关库表,具体库表语句如下:

# 创建测试数据库

CREATE DATABASE cas;USE cas;# 创建测试表CREATE TABLE `cas_t_user` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_name` varchar(50) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;# 插入两条测试用户数据INSERT INTO `cas_t_user` VALUES (1,'admin','96e79218965eb72c92a549dd5a330112');INSERT INTO `cas_t_user` VALUES (2,'user','96e79218965eb72c92a549dd5a330112');

②修改配置文件内容,并新增部分配置内容

修改%tomcat_home%/webapps/cas/WEB_INF/deployerConfigContext.xml

首先注释掉下述代码(注释默认用户名和密码)

<!-- 配置数据库验证bean,注释原有handler,取消默认用户名及密码-->
<!-- <bean id="primaryAuthenticationHandler"-->
<!-- class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">-->
<!-- <property name="users">-->
<!-- <map>-->
<!-- <entry key="casuser" value="Mellon"/>-->
<!-- </map>-->
<!-- </property>-->
<!-- </bean>-->

添加下述代码,MD5密码验证,数据库配置,认证类配置

<!-- 配置passwordEncoder -->
<bean id="MD5PasswordEncoder"
class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-arg index="0" value="MD5"/>
</bean><!-- 添加数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/cas?characterEncoding=UTF-8&amp;autoReconnect=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean><!-- 配置认证类 -->
<bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<!--dataSource指向上面配置的dataSource bean-->
<property name="dataSource" ref="dataSource"></property>
<property name="sql" value="select password from cas_t_user where user_name=?"></property>
<!--passwordEncoder 指向上面配置的 passwordEncoder bean-->
<property name="passwordEncoder" ref="MD5PasswordEncoder"></property>
</bean>

③添加相关jar包

  • 目录:apache-tomcat-8.5.73\webapps\cas\WEB-INF\lib
  • 在上面目录下添加以下三个jar包:

mysql-connector-java-5.1.46.jar

cas-server-support-jdbc-4.1.0.jar

c3p0-0.9.5.5.jar

(最开始使用了cas-server-support-jdbc-4.0.0.jar,发现输入正确的用户及密码时,无法实现正常页面跳转,且密码栏会被清空,切换为与本demo中的casServer版本完全匹配的jar后此问题被修复)

重启tomcat的服务端1,重启客户端2、客户端3进行测试

输入admin/111111

  • 其中用户的密码为MD5之后的值
  • “111111”的MD5值为:“96e79218965eb72c92a549dd5a330112”

效果如下图

可见,此时我们自己在数据库中添加的用户可以实现单点登录及单点登出。

客户端采用cas-client-core 3.5.0版本依赖,实现cas单点登录登出效果

前述文中阐述了使用模板依赖cas-client-autoconfig-support 2.3.0-GA的客户端实现方式:

<dependency><groupId>net.unicon.cas</groupId><artifactId>cas-client-autoconfig-support</artifactId><version>2.3.0-GA</version></dependency>

而实际上,cas-client的客户端接入方式不止一种,下面将简述另一种客户端接入方式:采用cas-client-core包的客户端接入。

首先引入依赖:

<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.5.0</version>
</dependency>

与前文 SpringBoot 集成 CAS Client 略有不同,本集成不需要在 SpringBoot 启动类上加入启用 CAS Client 的 @EnableCasClient 注解。如果加了这个注解, CAS Client是不能实现根据配置文件开关的,每次想要关掉,必须修改代码,注释调注解才行。

然后编写配置类:

import lombok.extern.slf4j.Slf4j;
import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;/**
* Author Haozhonghao
* Date 2022/1/10 10:34
* Version 1.0
* CAS集成核心配置类
*/
@Configuration
@Slf4j
@ConditionalOnProperty(value = "cas.loginType", havingValue = "cas")
public class CasFilterConfig {/**
* 需要走cas拦截的地址(/* 所有地址都拦截)
*/
@Value("${cas.urlPattern}")
private String filterUrl;/**
* 默认的cas地址,防止通过 配置信息获取不到
*/
@Value("${cas.server-url-prefix}")
private String casServerUrl;/**
* 应用访问地址(这个地址需要在cas服务端进行配置)
*/
@Value("${cas.authentication-url}")
private String authenticationUrl;/**
* 应用访问地址(这个地址需要在cas服务端进行配置)
*/
@Value("${cas.client-host-url}")
private String appServerUrl;@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean() {
log.info(" \n cas 单点登录配置 \n appServerUrl = " + appServerUrl + "\n casServerUrl = " + casServerUrl);
log.info(" servletListenerRegistrationBean ");
ServletListenerRegistrationBean listenerRegistrationBean = new ServletListenerRegistrationBean();
listenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener());
listenerRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return listenerRegistrationBean;
}/**
* 单点登录退出
*/
@Bean
public FilterRegistrationBean singleSignOutFilter() {
log.info(" servletListenerRegistrationBean ");
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new SingleSignOutFilter());
registrationBean.addUrlPatterns(filterUrl);
registrationBean.addInitParameter("casServerUrlPrefix", casServerUrl);
registrationBean.setName("CAS Single Sign Out Filter");
registrationBean.setOrder(1);
return registrationBean;
}/**
* 单点登录认证
*/
@Bean
public FilterRegistrationBean AuthenticationFilter() {
log.info(" AuthenticationFilter ");
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new AuthenticationFilter());
registrationBean.addUrlPatterns(filterUrl);
registrationBean.setName("CAS Filter");
registrationBean.addInitParameter("casServerLoginUrl", casServerUrl);
registrationBean.addInitParameter("serverName", appServerUrl);
registrationBean.setOrder(1);
return registrationBean;
}/**
* 单点登录校验
*/
@Bean
public FilterRegistrationBean Cas30ProxyReceivingTicketValidationFilter() {
log.info(" Cas30ProxyReceivingTicketValidationFilter ");
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
registrationBean.addUrlPatterns(filterUrl);
registrationBean.setName("CAS Validation Filter");
registrationBean.addInitParameter("casServerUrlPrefix", authenticationUrl);
registrationBean.addInitParameter("serverName", appServerUrl);
registrationBean.setOrder(1);
return registrationBean;
}/**
* 单点登录请求包装
*/
@Bean
public FilterRegistrationBean httpServletRequestWrapperFilter() {
log.info(" httpServletRequestWrapperFilter ");
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new HttpServletRequestWrapperFilter());
registrationBean.addUrlPatterns(filterUrl);
registrationBean.setName("CAS HttpServletRequest Wrapper Filter");
registrationBean.setOrder(1);
return registrationBean;
}
}

接着,编写yml配置文件:

server:
port: 9797
cas:
# 认证中心登录页面地址
server-url-prefix: http://127.0.0.1:9527/cas/login
# 应用地址,也就是自己的系统地址。
client-host-url: http://127.0.0.1:9797
# 认证中心地址
authentication-url: http://127.0.0.1:9527/cas
# 动态开启 cas 单点登录
loginType: cas
# cas 验票拦截路径
urlPattern: /*

最后编写测试接口Controller层:

import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;/**
* Author Haozhonghao
* Date 2022/1/10 10:45
* Version 1.0
*/
@RequestMapping("/casTest5")
@Controller
public class CASDemoController {
/**
* cas 单点登录
*/
@Value(value = "${cas.server-url-prefix}")
private String serverUrlPrefix = "";
@Value(value = "${cas.client-host-url}")
private String clientHostUrl = "";@GetMapping("/user5")
@ResponseBody
public String user(HttpServletRequest request) {
Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
String loginName = null;
if (assertion != null) {
AttributePrincipal principal = assertion.getPrincipal();
loginName = principal.getName();
System.out.println("访问者5:" + loginName);
}
return "访问者5:" + loginName;
}@GetMapping("/logout")
public String logout(HttpSession session) {
//将session设置为失效
session.invalidate();
return "redirect:" + serverUrlPrefix + "/logout?service=" + clientHostUrl + "/casTest5/user5";
}
}

进行测试,cas-client-core3.5.0版本比较稳定,可以兼容多个版本的 CAS Server,包括前文我们在本地构建的cas-server-4.1.0。

登录,输入admin/111111,登录效果如图,实现完成。

cas客户端demo至此功能已全部实现。

CAS4.1单点登录实现(包含原理配置实现及简易demo)相关推荐

  1. java domino 单点登录_Domino单点登录LTPAtoken生成原理

    Domino单点登录LTPAtoken生成原理 一.WebSphere与Domino之间的SSO 首先让我们来了解一下Websphere与Domino之间是怎么完成SSO的: 1.Web用户向Webs ...

  2. 2、cas4.0 单点登录 之 cas-client

    cas4.0 单点登录 之 cas-client cas4.0 单点登录 之 https证书 已经做好了证书的准备工作,现在结合cas-server来配置单点登录: 一.安装cas服务端(cas-se ...

  3. 1、cas4.0 单点登录 之 https证书

    cas4.0 单点登录 之 https证书 公司项目使用分布式部署,使用单点登录可使各节点无状态,达到业务与用户认证解耦:cas-server与cas-client通讯安全完全基于https,需要ss ...

  4. 单点登录CAS-03:cas配置01-配置文件类型

    单点登录CAS-03:cas配置01-配置文件类型 1.前言 2.配置文件分类 3.application.properties详解 3.1springboot相关参数 3.1.1 内置容器配置 3. ...

  5. 单点登录SSO的原理和实现

    背景 SSO,英文全称Single Sign On,单点登录,一般应用于多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统的保护资源.如登录访问 blog.baidu.com 后,对于 ...

  6. 单点登录之CAS原理和实现(转载)

    转载源:https://www.jianshu.com/p/613c615b7ef1 单点登录之CAS原理和实现 来源于作者刘欣的<码农翻身> + 自己的备注理解 这家集团公司财大气粗,竟 ...

  7. 关于单点登录的简单原理和实现步骤

    关于单点登录的简单原理和实现步骤 一.单系统登录机制 1.http无状态协议 web应用采用browser/server架构,http作为通信协议.http是无状态协议,浏览器的每一次请求,服务器会独 ...

  8. 单点登录CAS-03:cas配置02-开启/status

    单点登录CAS-03:cas配置02-开启/status 1.前言 2.开启/status/dashboard 2.1找到相关的配置项参数 2.1.1 Spring Boot Endpoints属性列 ...

  9. 手撕一套sso(单点登录)系统之原理篇1

    在手撕之前,你首先要了解一些原理,我写的案例成品可以访问zauth,语言是Java8. 目录 1.关于Http 2.用户信息怎么存?存什么?存在哪? 2.1 使用前端存储技术Storage或index ...

最新文章

  1. 昨天,我用 Python 写了一个婚介模型
  2. memcached ---- 学习笔记
  3. C语言时间管理小程序,写了一个时间管理的微信小程序
  4. 减少图片HTTP 请求的方案
  5. nginx安装编译,动态添加模块及其各模块的作用
  6. HP DL388G5 安装64位linux虚拟系统出错!
  7. python跨文件复制sheet_Python办公自动化-工作表复制(可跨文件)
  8. ubuntu关闭服务需要身份验证
  9. EF+MVC+Bootstrap 项目实践 Day11
  10. 如何做好需求变更管理?——需求变更流程规范
  11. vue router hash和history的区别_react-router-v4
  12. atitit.系统架构图 的设计 与工具 attilax总结
  13. 大数据:大一整年感悟及总结
  14. 还原/修改XP任务栏
  15. Data()笔记之getDay()的基本用法
  16. 视频直播,音频直播,m3u8
  17. 【SPUSKU】简述
  18. ionic4开发微信小程序_15个适用于Ionic应用程序开发人员的资源
  19. linux下安装以太坊(ETH/ETC)节点
  20. labview声音信号采集和分离

热门文章

  1. 操作系统学习初步--用GNU汇编创建最简单的OS
  2. 6.5 地理数据可视化
  3. android底部导航栏凹凸设计,快速实现底部导航栏
  4. C语言实现-华为太空人手表
  5. 人工ai迪丽热巴第二部_迪丽热巴恋爱了?已公开媒体曝光男友?
  6. 计算机硬件入门 之 译码器(以74LS138为例)
  7. java实现医嘱管理系统_基于SSM框架的JAVA医嘱管理系统
  8. layui 给table里面的添加图标_layui怎么添加icon图标?
  9. 自动驾驶数据服务进入2.0时代
  10. 【调剂】中国科学院深圳先进技术研究院 脑科学与神经技术团队 招收调剂硕士生...