Spring Security 的使用

一、简介 

1.1、Spring Security 相关概念

1.过滤器链(Filter Chain)
基于Servlet过滤器(Filter)处理和拦截请求,进行身份验证、授权等安全操作。过滤器链按顺序执行,每个过滤器负责一个具体的安全功能。

2.SecurityInterceptor(安全拦截器)
根据配置的安全规则拦截请求,进行访问控制和权限验证。

3.Authentication(认证对象)
封装用户的认证信息(账户状态、用户名、密码、权限等)
Authentication常用实现类:
    UsernamePasswordAuthenticationToken:用户名密码登录的 Token
    AnonymousAuthenticationToken:针对匿名用户的 Token
    RememberMeAuthenticationToken:记住我功能的的 Token

4.AuthenticationManager (用户认证的管理类)
所有的认证请求都会封装成一个Token 给 AuthenticationManager,AuthenticationManager 调用 AuthenticationProvider.authenticate() 认证,返回包含认证信息的 Authentication 对象。

5.AuthenticationProvider(认证的具体实现类)
一个 provider 是一种认证方式实现,主流的认证方式都已经提供了默认实现,如 DAO、LDAP、CAS、OAuth2等。

6.UserDetailService(用户详细信息服务)
通过 UserDetailService 拿到数据库(或内存)中的认证信息然后和客户端提交的认证信息做校验。

7.访问决策管理器(AccessDecisionManager)
在授权过程中进行访问决策。根据用户的认证信息、请求的URL和配置的权限规则,判断用户是否有权访问资源。

8.SecurityContext(安全上下文)
认证通过后,会为这用户生成一个唯一的 SecurityContext(ThreadLocal存储),包认证信息 Authentication。
通过 SecurityContext 可获取到用户的标识 Principle 和授权信息 GrantedAuthrity。
系统任何地方只要通过 SecurityHolder.getSecruityContext() 可获取到 SecurityContext。

9.注解和表达式支持 
用在代码中声明和管理安全规则。如@Secured注解可以标记在Controller或方法上,限制权限用户才能访问。

1.2、核心的过滤器链 

1.SecurityContextPersistenceFilter 
Filter的入口和出口,将 SecurityContext (登录后的信息)对象持久到Session,同时把 SecurityContext 设置给 SecurityContextHolder 获取用户认证授权信息。

2.UsernamePasswordAuthenticationFilter 
默认拦截“/login”登录请求,处理表单提交的登录认证,将请求中的认证信息封装成 UsernamePasswordAuthenticationToken,然后调 AuthenticationManager 进行认证。

3.BasicAuthenticationFilter 
基本认证,支持 httpBasic 认证方式的Filter。

4.RememberAuthenticationFilter 
记住我功能实现的 Filter。

5.AnonymousAuthenticationFilter 
处理匿名访问的资源,如果用户未登录,会创建匿名的Token(AnonymousAuthenticationToken),通过 SecurityContextHodler 设置到 SecurityContext 中。

6.ExceptionTranslationFilter 
捕获 FilterChain 所有的异常,但只处理 AuthenticationException、AccessDeniedException 异常,其他的异常会继续抛出。

7.FilterSecurityInterceptor 
做授权的Filter,通过父类(AbstractSecurityInterceptor.beforeInvocation)调用 AccessDecisionManager.decide 方法对用户授权。

1.3、Spring Security 使用场景 

1.用户登录和认证
可处理用户的身份验证。如表单登录、基本认证、OAuth等。
2.授权和权限管理
可定义安全规则和访问控制,可用注解、表达式或配置文件来声明和管理权限,确保用户只能访问其有权访问的资源。
3.防止跨站点请求伪造(CSRF)
可生成和验证CSRF令牌,防止Web应用程序受到CSRF攻击。可在表单中自动添加CSRF令牌,并验证提交请求中的令牌值。
4.方法级安全性
允许在方法级别对方法进行安全性配置。可用注解或表达式来定义哪些用户有权调用特定方法。
5.记住我功能
允许用户在下次访问时保持登录状态,不要重新输入用户名和密码。
6.单点登录(SSO)
可与其他身份验证和授权提供程序集成,实现单点登录。
7.安全事件和审计日志
可记录安全事件和用户操作,以便进行审计和故障排查。

二、SpringBoot 中基本使用 

2.1、引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
</dependency>

2.2、外围的配置

2.2.1、登录页面static/login.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>登陆</title>
</head>
<body>
<h1>登陆</h1>
<form method="post" action="/login">
	<div class="checkbox">
	    <label><input type="checkbox" id="rememberme" name="remember-me"/>记住我</label>
	</div>
	<div>
		用户名:<input type="text" name="username">
	</div>
	<div>
		密码:<input type="password" name="password">
	</div>
	<div>
		<button type="submit">立即登陆</button>
	</div>
</form>
</body>
</html>

2.2.2、启动类、访问接口

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

@Controller
public class AuthController {
    //登录成功后重定向地址
	@RequestMapping("/loginSuccess")
	@ResponseBody
	public String loginSuccess(){
		return "登录成功";
	}
}

2.2.3、DDL、实体类、Dao层接口

    SET FOREIGN_KEY_CHECKS=0;
    -- ----------------------------
    -- Table structure for t_permission 权限
    -- ----------------------------
    DROP TABLE IF EXISTS `t_permission`;
    CREATE TABLE `t_permission` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      `resource` varchar(255) NOT NULL,
      `state` int(11) DEFAULT NULL,
      `menu_id` bigint(20) DEFAULT NULL,
      `expression` varchar(255) NOT NULL,
      PRIMARY KEY (`id`),
      KEY `menu_id` (`menu_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        
    -- ----------------------------
    -- Table structure for t_role 角色
    -- ----------------------------
    DROP TABLE IF EXISTS `t_role`;
    CREATE TABLE `t_role` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      `sn` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Table structure for t_role_permission 角色和权限关系
    -- ----------------------------
    DROP TABLE IF EXISTS `t_role_permission`;
    CREATE TABLE `t_role_permission` (
      `role_id` bigint(20) NOT NULL,
      `permission_id` bigint(20) NOT NULL,
      PRIMARY KEY (`role_id`,`permission_id`),
      KEY `permission_id` (`permission_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Table structure for t_login 用户登录表
    -- ----------------------------
    DROP TABLE IF EXISTS `t_login`;
    CREATE TABLE `t_login` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `username` varchar(255) DEFAULT NULL COMMENT '员工用户名',
      `password` varchar(255) DEFAULT NULL COMMENT '密码',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Table structure for t_login_role 用户角色关系表
    -- ----------------------------
    DROP TABLE IF EXISTS `t_login_role`;
    CREATE TABLE `t_login_role` (
      `login_id` bigint(20) NOT NULL,
      `role_id` bigint(20) NOT NULL,
      PRIMARY KEY (`login_id`,`role_id`),
      KEY `role_id` (`role_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
	
    -- ----------------------------
    -- Table structure for persistent_logins 用户登录记住表
    -- ----------------------------
    CREATE TABLE `persistent_logins` (
      `username` varchar(64) NOT NULL DEFAULT '',
      `series` varchar(64) NOT NULL,
      `token` varchar(64) NOT NULL,
      `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      PRIMARY KEY (`series`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
	
public class Login {
	private Long id;
	private String username;
	private String password;
}

public class Permission {
	private Long id;
	private String name;
	private String sn;
	private String resource;
}


import cn.itsource.security.domain.Login;
import cn.itsource.security.domain.Permission;
import java.util.List;

public interface LoginMapper {
	Login selectByUsername(String username);
}


import cn.itsource.security.domain.Permission;
import java.util.List;

public interface PermissionMapper {
	List<Permission> selectPermissionsByUserId(Long userId);
	List<Permission> selectAll();
}

2.2.4、数据库连接、数据插入

略 . . . . . . 

2.3、配置类 WebSecurityConfigurerAdapter 

配置类 WebSecurityConfigurerAdapter
创建UserDetailService的Bean,用来加载用户认证信息。
配置编码器,通过该编码器对密码进行加密匹配。
授权规则配置,哪些资源需要什么权限。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
    @Autowired
    private UserDetailsService userDetailsService;
	
    @Autowired
    private DataSource dataSource ;
	/**
		//提供用户信息,这里没有从数据库查询用户信息,在内存中模拟
		@Bean
		public UserDetailsService userDetailsService(){
			InMemoryUserDetailsManager inMemoryUserDetailsManager = 
			new InMemoryUserDetailsManager();
			inMemoryUserDetailsManager.createUser(User.withUsername("JunSouth").password("123").authorities("admin").build());
			return inMemoryUserDetailsManager;
		}
	*/
	
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
         JdbcTokenRepositoryImpl obj = new JdbcTokenRepositoryImpl();
         obj.setDataSource(dataSource);
         //obj.setCreateTableOnStartup(false);	
         //启动创建表persistent_logs表,存token,username时会用到
         return obj;
    }
	
    @Bean
    public PasswordEncoder passwordEncoder(){
	    //return new BCryptPasswordEncoder();
	    //密码编码器:不加密
        return NoOpPasswordEncoder.getInstance();
    }
	
	//授权规则配置
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable()
			.authorizeRequests()
			.antMatchers("/login").permitAll()  //登录路径放行
			.antMatchers("/login.html").permitAll() //对登录页面跳转路径放行
			.anyRequest().authenticated() //其他路径都要拦截
			.and().formLogin()  //允许表单登录, 设置登陆页
			.successForwardUrl("/loginSuccess") // 设置登陆成功页
			.loginPage("/login.html")   //登录页面跳转地址
			.loginProcessingUrl("/login")   //登录处理地址(必须)
			.and().logout().logoutUrl("/mylogout").permitAll()    //制定义登出路径
			.logoutSuccessHandler(new MyLogoutHandler())  //登出后处理器-可以做一些额外的事情
			.invalidateHttpSession(true); //登出后session无效
			
        http.rememberMe()
			.tokenRepository(persistentTokenRepository())	//持久
			.tokenValiditySeconds(3600)	//过期时间
			.userDetailsService(userDetailsService); //用来加载用户认证信息的
	}
}

2.4、 定义 UserDetailsService 

/**
 * 提供给 security 的用户信息的 service,要复写 loadUserByUsername 方法返回数据库中的用户信息
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {

	@Autoware
	private LoginMapper loginMapper;
	
	/**
	 * 加载数据库中的认证的用户的信息:用户名、密码、用户的权限列表
	 * 通过username查询用户的信息,(密码,权限列表等)封装成 UserDetails 返回,交给 security 。
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {		
		Login userFromMysql = loginMapper.selectByUsername(username);
		if(loginFromMysql == null){
			  throw new UsernameNotFoundException("无效的用户名");
		}
	    //前台用户
		List<GrantedAuthority> permissions = new ArrayList<>();
		List<Permission> permissionSnList = systemManageClient.listByUserId(loginFromMysql.getId());
		permissionSnList.forEach(permission->{
			System.out.println("用户:"+username+" :加载权限 :"+permission.getSn());
			permissions.add(new SimpleGrantedAuthority(permission.getSn()));
		});
		//密码是基于BCryptPasswordEncoder加密的密文,User是security内部的对象,UserDetails的实现类 ,
		//用来封装用户的基本信息(用户名,密码,权限列表),四个 true 分别是账户启用、账户过期、密码过期、账户锁定
		return new User(username,loginFromMysql.getPassword(),true,true,true,true,permissions);
	}
}

2.5、认证结果处理

/**
 * 认证——成功处理
 */
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
		response.setContentType("application/json;charset=utf-8");
		Map map = new HashMap<>();
		map.put("success",true);
		map.put("message","认证成功");
		map.put("data",authentication);
		response.getWriter().print(JSON.toJSONString(map));
		response.getWriter().flush();
		response.getWriter().close();
	}
}

/**
 * 认证——失败处理
 */
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
		response.setContentType("application/json;charset=utf-8");
		Map map = new HashMap<>();
		map.put("success",false);
		map.put("message","认证失败");
		response.setStatus(HttpStatus.UNAUTHORIZED.value());
		response.getWriter().print(JSON.toJSONString(map));
		response.getWriter().flush();
		response.getWriter().close();
	}
}

2.6、授权结果处理

/**
 * 授权失败——定义认证检查失败处理
 */
public class DefaultAccessDeniedHandler implements AccessDeniedHandler {
	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
		String result = JSON.toJSONString(AjaxResult.me().setSuccess(false).setMessage("无访问权限"));
		response.setContentType("text/html;charset=utf-8");
		PrintWriter writer = response.getWriter();
		writer.print(result);
		writer.flush();
		writer.close();
	}
}

/**
 * 授权失败——定义匿名用户访问无权处理
 */
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
	@Override
	public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
		e.printStackTrace();
		httpServletResponse.setContentType("application/json;charset=utf-8");
		Map<String,Object> result = new HashMap<>();
		result.put("success",false);
		result.put("message","登录失败,用户名或密码错误["+e.getMessage()+"]");
		httpServletResponse.getWriter().print(JSONUtils.toJSONString(result));
	}
}

2.7、登出处理 

/**
 * 登出处理器
 */
public class MyLogoutHandler implements LogoutHandler {
	@Override
	public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
		try {
			//登出成功,响应结果给客户端,通常是一个JSON数据
			response.getWriter().println("logout success");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

2.8、操作—注册、登录 

http://localhost:8080/login,
进入登录页面,输入账号:JunSouth 密码 123 完成登录

public class PasswordTest {
	@Test
	public void testPassword(){
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		String enPass = bCryptPasswordEncoder.encode("123");
		System.out.println(enPass);
		System.out.println(bCryptPasswordEncoder.matches("123", enPass));
	}
}

2.9、操作—获取用户信息 

2.10、白名单、资源定制访问、不同级别访问

三、关键且重要的

3.1、角色

3.2、加密

3.3、权限定制与绑定

四、登陆方式

4.2、单点登录(手机、微信、支付宝)

4.3、用户名登陆

4.4、其它定制登录

五、注意事项

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

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

相关文章

什么是网络可视化?网络可视化工具有用吗

网络可视化定义是自我描述的&#xff0c;因为它在单个屏幕上重新创建网络布局&#xff0c;以图形和图表的形式显示有关网络设备、网络指标和数据流的信息&#xff0c;为 IT 运营团队提供一目了然的理解和决策。 网络是复杂的实体&#xff0c;倾向于持续进化&#xff0c;随着业…

利用MCMC 获得泊松分布

写出概率流方程如下 if state 0: if np.random.random() < min([Lambda/2, 1]):state 1else:passelif state 1:if choose_prob_state[i] < 0.5:#选择 1 -> 0&#xff0c;此时的接受概率为min[2/Lambda, 1]if np.random.random() < min([2/Lambda, 1]…

STM32USART+DMA实现不定长数据接收/发送

STM32USARTDMA实现不定长数据接收 CubeMX配置代码分享实践结果 这一期的内容是一篇代码分享&#xff0c;CubeMX配置介绍&#xff0c;关于基础的内容可以往期内容 夜深人静学32系列11——串口通信夜深人静学32系列18——DMAADC单/多通道采集STM32串口重定向/实现不定长数据接收 …

『PyTorch学习笔记』分布式深度学习训练中的数据并行(DP/DDP) VS 模型并行

分布式深度学习训练中的数据并行(DP/DDP) VS 模型并行 文章目录 一. 介绍二. 并行数据加载2.1. 加载数据步骤2.2. PyTorch 1.0 中的数据加载器(Dataloader) 二. 数据并行2.1. DP(DataParallel)的基本原理2.1.1. 从流程上理解2.1.2. 从模式角度理解2.1.3. 从操作系统角度看2.1.…

【ESP32】手势识别实现笔记:红外温度阵列 | 双三次插值 | 神经网络 | TensorFlow | ESP-DL

目录 一、开发环境搭建与新建工程模板1.1、开发环境搭建与卸载1.2、新建工程目录1.3、自定义组件 二、驱动移植与应用开发2.1、I2C驱动移植与AMG8833应用开发2.2、SPI驱动移植与LCD应用开发2.3、绘制温度云图2.4、启用PSRAM&#xff08;可选&#xff09;2.5、画面动静和距离检测…

力扣:1419. 数青蛙

题目&#xff1a; 代码&#xff1a; class Solution { public:int minNumberOfFrogs(string croakOfFrogs){string s "croak";int ns.size();//首先创建一个哈希表来标明每个元素出现的次数&#xff01;vector<int>hash(n); //不用真的创建一个hash表用一个数…

事务--02---TCC模式

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 TCC模式两阶段提交 的模型 1.流程分析阶段一&#xff08; Try &#xff09;&#xff1a;阶段二&#xff08;Confirm)&#xff1a;阶段二(Canncel)&#xff1a; 2.事…

java编程:⼀个⽂件中存储了本站点下各路径被访问的次数,请编程找出被访问次数最多的10个路径

题目 编程题&#xff1a;⼀个⽂件&#xff08;url_path_statistics.txt&#xff09;中存储了本站点下各路径被访问的次数 请编程找出被访问次数最多的10个路径时间复杂是多少&#xff0c;是否可以优化&#xff08;假设路径数量为n&#xff09;如果路径访问次数⽂件很⼤&#x…

unity3d模型中缺失animation

在 模型的Rig-Animationtype 设置成Legacy https://tieba.baidu.com/p/2293580178

解决WPS拖动整行的操作

如上图&#xff0c;想要把第4行的整行内容&#xff0c;平移到第1行。 1.选中第4行的整行 2.鼠标出现如图的样子时&#xff0c;按住鼠标左键&#xff0c;上移到第1行位置后&#xff0c;放开左键即可。

vue项目和wx小程序

wx:key 的值以两种形式提供&#xff1a; 1、字符串&#xff0c;代表在 for 循环的 array 中 item 的某个 property&#xff0c;该 property 的值需要是列表中唯一的字符串或数字&#xff0c;且不能动态改变。 2、保留关键字 this 代表在 for 循环中的 item 本身&#xff0c;这种…

测试与管理 Quota

用myquota1创建一个大的文件测试 理论猜想&#xff1a;超过soft可以&#xff0c;但是超过hard就不行了&#xff0c;最大值就是hard&#xff0c;如果超过soft&#xff0c;过了17天不处理&#xff0c;最后限制值会被强制设置成soft。修改设置成hard值 切换测试用户&#xff0c;m…

易宝OA ExecuteSqlForSingle SQL注入漏洞复现

0x01 产品简介 易宝OA系统是一种专门为企业和机构的日常办公工作提供服务的综合性软件平台&#xff0c;具有信息管理、 流程管理 、知识管理&#xff08;档案和业务管理&#xff09;、协同办公等多种功能。 0x02 漏洞概述 易宝OA ExecuteSqlForSingle接口处存在SQL注入漏洞&a…

苹果TF签名全称TestFlight签名,需要怎么做才可以上架呢?

如果你正在开发一个iOS应用并准备进行内测&#xff0c;TestFlight是苹果提供的一个免费的解决方案&#xff0c;它使开发者可以邀请用户参加应用的测试。以下是一步步的指南&#xff0c;教你如何利用TestFlight进行内测以便于应用后续可以顺利上架App Store。 1: 准备工作 在测…

项目设计---网页五子棋

文章目录 一. 项目描述二. 核心技术三. 需求分析概要设计四. 详细设计4.1 实现用户模块4.1.1 约定前后端交互接口4.1.2 实现数据库设计4.1.3 客户端页面展示4.1.4 服务器功能实现 4.2 实现匹配模块4.2.1 约定前后端交互接口4.2.2 客户端页面展示4.2.3 服务器功能实现 4.3 实现对…

2023年计网408

第33题 33.在下图所示的分组交换网络中&#xff0c;主机H1和H2通过路由器互连&#xff0c;2段链路的带宽均为100Mbps、 时延带宽积(即单向传播时延带宽)均为1000bits。若 H1向 H2发送1个大小为 1MB的文件&#xff0c;分组长度为1000B&#xff0c;则从H1开始发送时刻起到H2收到…

Windows系列:windows2003-建立域

windows2003-建立域 Active Directory建立DNS建立域查看日志xp 加入域 Active Directory 活动目录是一个包括文件、打印机、应用程序、服务器、域、用户账户等对象的数据库。 常见概念&#xff1a;对象、属性、容器 域组件&#xff08;Domain Component&#xff0c;DC&#x…

如何在Docker环境下安装Firefox浏览器并结合内网穿透工具实现公网访问

文章目录 1. 部署Firefox2. 本地访问Firefox3. Linux安装Cpolar4. 配置Firefox公网地址5. 远程访问Firefox6. 固定Firefox公网地址7. 固定地址访问Firefox Firefox是一款免费开源的网页浏览器&#xff0c;由Mozilla基金会开发和维护。它是第一个成功挑战微软Internet Explorer浏…

信贷销售经理简历模板

这份简历内容&#xff0c;以信贷销售经理招聘需求为背景&#xff0c;我们制作了1份全面、专业且具有参考价值的简历案例&#xff0c;大家可以灵活借鉴。 信贷销售经理简历模板在线编辑下载&#xff1a;百度幻主简历 求职意向 求职类型&#xff1a;全职 意向岗位&#xff…

TQ2440开发板-LED全亮全灭控制程序设计

目录 什么是GPIOS3C2440的GPIO访问和控制方式&#xff1a;3种寄存器 TQ2440的LED灯底板原理图---LED测试部分核心板原理图----GPIO部分 LED控制---设计思想整体代码 && 代码研读配置GPIO端口为输出模式控制LED的全亮和全灭 真就是从零学起。 什么是GPIO GPIO&#xff…