应用Spring Security

前面介绍了在项目开发时为什么选择Spring Security,还介绍了它的原理。本节开始动手实践Spring Security的相关技术。

实战:Spring Security入门

现在开始搭建一个新项目,实践一个Spring Security的入门程序。

(1)新建一个spring-security-demo模块,添加项目依赖,在pom.xml中添加如下依赖:

<properties>

<java.version>11</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-security</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

<!--thymeleaf对security5的支持依赖-->

<dependency>

<groupId>org.thymeleaf.extras</groupId>

<artifactId>thymeleaf-extras-springsecurity5</artifactId>

<!--<version>3.0.4.RELEASE</version>-->

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<optional>true</optional>

</dependency>

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>javax.servlet-api</artifactId>

<version>4.0.1</version>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>

(2)在application.properties中添加Spring Security配置,配置当前登录的用户名和密码,配置内容如下:

#登录的用户名

spring.security.user.name=admin

#登录的密码

spring.security.user.password=123456

(3)在resources文件夹下创建页面add.html,表示添加页面,代码如下:

<!DOCTYPE html>

<html xmlns:th="https://www.thymeleaf.org">

<head>

title></title>

</head>

<body>

add 页面

</body>

</html>

(4)添加主页home.html,代码如下:

<!DOCTYPE html>

<html xmlns:th="https://www.thymeleaf.org">

<head>

<title>主页</title>

</head>

<body>

你已经登录成功!

<form th:action="@{/logout}" action="/login" method="post">

<input type="submit" value="退出系统"/>

</form>

</body>

</html>

(5)添加login.html登录页,用于用户的登录,代码如下:

<!DOCTYPE html>

<html xmlns:th="https://www.thymeleaf.org">

<head>

<title>请登录</title>

</head>

<body>

<div>

<form th:action="@{/login}" method="post" action="/login">

<p>

<span>请输入用户名:</span>

<input type="text" id="username" name="username">

</p>

<p>

<span>请输入密码:</span>

<input type="password" id="password"

name="password">

</p>

<input type="submit" value="登录"/>

</form>

</div>

</body>

</html>

(6)在resources文件夹下创建一个css文件夹,新建一个my.css文件,内容如下:

my css file

(7)新建一个Controller包,再新建如下3个Controller。

AddController类,用于返回add页面,代码如下:

package com.example.springsecuritydemo.controller;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.GetMapping;

@Controller

public class AddController {

@GetMapping("/add")

public String ad(){

return "add";

}

}

omeController类用于访问home页面,代码如下:

package com.example.springsecuritydemo.controller;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.GetMapping;

@Controller

public class HomeController {

@GetMapping("/home")

public String home(){

return "home";

}

}

LoginController类用于用户登录,代码如下:

package com.example.springsecuritydemo.controller;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.GetMapping;

@Controller

public class LoginController {

@GetMapping("/login")

public String login(){

return "login";

}

}

(8)新建一个config包,添加Spring Security配置文件

WebSecurityConfig:

package com.example.springsecuritydemo.config;

import com.example.springsecuritydemo.service.LoginSuccessHandler;

import org.springframework.context.annotation.Configuration;

import

org.springframework.security.config.annotation.web.builders.HttpSecuri

ty;

import

org.springframework.security.config.annotation.web.configuration.WebSe

curityConfigurerAdapter;

@Configuration

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

// 关闭csrf校验

http.csrf().disable();

// 配置登录页面,用户名和密码已在配置文件中

http.formLogin().loginPage("/login").permitAll();

// 配置登录成功后的操作

http.formLogin().successHandler(new LoginSuccessHandler());

// 登录授权

http.logout().permitAll();

// 授权配置

http.authorizeRequests()

/* 所有的静态文件可以访问 */

.antMatchers("/js/**","/css/**","/images/**").permitAll()

/* 所有的以/add 开头的 add页面可以访问 */

.antMatchers("/add/**").permitAll()

.anyRequest().fullyAuthenticated();

}

}

9)新建一个登录成功后的业务处理服务类LoginSuccessHandler,代码如下:

package com.example.springsecuritydemo.service;

import org.springframework.security.core.Authentication;

import org.springframework.security.web.authentication.Authentication

SuccessHandler;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

/**

* 登录成功后的业务处理类

*/

public class LoginSuccessHandler implements

AuthenticationSuccessHandler {

@Override

public void onAuthenticationSuccess(HttpServletRequest request,

HttpServletResponse response,

Authentication authentication)

throws

IOException {

System.out.println("登录成功");

//重定向到home.html页面

response.sendRedirect("/home");

}

}

(10)添加当前项目的启动类
SpringSecurityDemoApplication,使用注解@EnableWeb- Security启动Spring Security功能:

package com.example.springsecuritydemo;

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableWebSecurity

@SpringBootApplication

public class SpringSecurityDemoApplication {

public static void main(String[] args) {

SpringApplication.run(SpringSecurityDemoApplication.class,

args);

}

}

(11)执行
SpringSecurityDemoApplication启动当前项目,访问localhost:8080,因为没有登录,所以跳转到登录页,如图5.1所示。再访问localhost:8080/home,还是会自动跳转到登录页面,因为没有登录。前两次访问Spring Security时会自动判断用户还未登录,直接跳转到登录页面,提示用户登录。

再访问localhost:8080/add,可以看到add页面,如图5.2所示。之所以能够在没有登录的情况下看到add页面,是因为Spring Security配置了未登录时可以访问/add这个链接。配置在WebSecurityConfig.java的代码如下:

.antMatchers("/add/**").permitAll()

这个代码的含义是所有以/add开头的链接都允许访问,因此可以看到add页面。

同理,访问localhost:8080/css/my.css会返回项目的静态文件my.css,因为在WebSecurity-Config中配置了静态文件的访问权限。

/* 所有的静态文件可以访问 */

.antMatchers("/js/**","/css/**","/images/**").permitAll()

所以,js、css和images文件夹下的所有文件可以直接获取,不会有任何校验,访问结果如图5.3所示。

现在输入用户名admin和密码123456登录系统,登录成功后的页面如图5.4所示,因为在LoginSuccessHandler中配置了登录成功后的跳转页面代码,即response.sendRedirect ("/home"),所以登录成功后直接跳转到了home页面。

Spring Security适配器

Spring大量使用适配器模式,适配器的好处是当选择性地修改一部分配置时不用覆盖其他不相关的配置,Spring Security常用的适配器有
WebSecurityConfigurerAdapter。在开发中,可以选择覆盖部分自定义的配置,从而快速完成开发。

设计模式中适配器模式的结构如图5.5所示。

适配器模式有3个类,分别是Adapter适配者类、Target目标类和ObjectAdapter适配器,可以通过这3个类实现适配器的相关功能。

在Spring Security框架中,
WebSecurityConfigurerAdapter类图如图5.6所示。

在图5.6中,SecurityBuilder、SecurityConfigurer和SecurityBuilder这3个类非常重要,它们是用来构建过滤器链的,在用HttpSecurity实现SecurityBuilder时,传入的泛型是
DefaultSecurityFilterChain,因此SecurityBuilder.build()用来构建过滤器链,而WebSecurity- Configurer用来配置WebSecurity。


WebSecurityConfigurerAdapter中有两个方法非常重要,下面分别介绍。

(1)第一个是init()方法,其部分源码如下:

public void init(final WebSecurity web) throws Exception {

final HttpSecurity http = getHttp();

web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {

FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class);

web.securityInterceptor(securityInterceptor);

});

}

首先init()方法调用了getHttp()方法,其作用是进行HttpSecurity的初始化,其部分源码如下:

@SuppressWarnings({ "rawtypes", "unchecked" })

protected final HttpSecurity getHttp() throws Exception {

if (http != null) {

return http;

}

AuthenticationEventPublisher eventPublisher =

getAuthenticationEventPublisher();

localConfigureAuthenticationBldr.authenticationEventPublisher(eventPub

lisher);

AuthenticationManager authenticationManager =

authenticationManager();

authenticationBuilder.parentAuthenticationManager(authenticationManage

r);

Map<Class<?>, Object> sharedObjects = createSharedObjects();

http = new HttpSecurity(objectPostProcessor, authenticationBuilder,

sharedObjects);

if (!disableDefaults) {

// @formatter:off

http

.csrf().and()

.addFilter(new WebAsyncManagerIntegrationFilter())

.exceptionHandling().and()

.headers().and()

.sessionManagement().and() .securityContext().and()

.requestCache().and()

.anonymous().and()

.servletApi().and()

.apply(new DefaultLoginPageConfigurer<>()).and()

.logout();

// @formatter:on

ClassLoader classLoader = this.context.getClassLoader();

List<AbstractHttpConfigurer> defaultHttpConfigurers =

SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class,

classLoader);

for (AbstractHttpConfigurer configurer : defaultHttpConfigurers)

{

http.apply(configurer);

}

}

configure(http);

return http;

}

在初始化完成后,init()方法调用了configure()方法配置默认的拦截器,当完成HttpSecurity初始化后,将HttpSecurity放入WebSecurity中,最终保存在WebSecurity的
securityFilterChainBuilders集合中。configure()方法的部分源码如下:

/**

* 覆盖此方法以配置{@link HttpSecurity}。

通常子类不建议通过调用super来调用此方法,因为它可能会覆盖它们的配置。默认配置如下:

*

* <pre>

*

http.authorizeRequests().anyRequest().authenticated().and().formLogin(

).and().httpBasic();

* </pre> *

* 任何需要防御常见漏洞的端点都可以在这里指定,包括公共的端点

* See {@link HttpSecurity#authorizeRequests} and the

`permitAll()`authorization rule

* 更多关于公共端点的详细信息

*

* @param http the {@link HttpSecurity} to modify

* @throws Exception if an error occurs

*/

// @formatter:off

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests().anyRequest().authenticated().and()

.formLogin().and().httpBasic();

}

(2)另外一个非常重要的方法是前面提到的configure()方法。可以看到,抽象类
WebSecurityConfigurerAdapter中的configure是个protect()方法,开发者可以新建类或继承此类后实现该方法,从而实现业务逻辑。

在当前项目中,自定义的WebSecurityConfig类继承了
WebSecurityConfigurerAdapter()方法,实现了空的configure()方法,并配置了当前项目的登录和拦截信息。当前方法的入参是HttpSecurity,可以使用HttpSecurity的builder构建方式来灵活制定访问策略。Http- Security的常用方法参见表5.1。

表5.1 HttpSecurity的常用方法

实战:用户授权

在Spring Security中可以设置不同的用户拥有不同的角色,同时不同的角色有不同的权限。下面举例说明。

修改HomeController.java文件,增加一个/home2方法,增加的代码如下:

@GetMapping("/home2")

public String home2(){ return "home2";

}

修改WebSecurityConfig.java文件,增加配置属性:

/**

* 授权,赋予用户角色,基于内存授权

*/

@Override

protected UserDetailsService userDetailsService() {

InMemoryUserDetailsManager manager = new

InMemoryUserDetailsManager();

manager.createUser(User.withUsername("admin").password("123456").

roles("admin").build());

return manager;

}

修改WebSecurityConfig.java中的configure()方法,增加/home2连接的

角色权限配置:

// 授权配置

http.authorizeRequests()

//可以访问所有静态文件

.antMatchers("/js/**","/css/**","/images/**").permitAll()

//可以访问所有以/add开头的add页面

.antMatchers("/add/**").permitAll()

.antMatchers("/home2").hasRole("user")

.anyRequest().fullyAuthenticated();

重启项目,登录之后访问localhost:8080/home2,结果如图5.7所示。因为当前用户没有权限,所以访问报错。

Spring Security核心类

Spring Security框架中最核心的接口类是AuthenticationManager,其部分源码如下:

Authentication authenticate(Authentication authentication)

throws AuthenticationException;

AuthenticationManager是用来处理认证(Authentication)请求的基本接口。这个接口定义了方法authenticate(),此方法只接收一个代表认证请求的Authentication对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的Authentication对象,否则认证无法通过。

AuthenticationManager接口有两个重要的实现。


AuthenticationManagerDelegator是一个委托类,由SecurityBuilder接口的子类来配置生成一个身份管理器;另外一个实现类是ProviderManager,此类的部分源码如下:

public class ProviderManager implements AuthenticationManager, Message

SourceAware,

InitializingBean {

private List<AuthenticationProvider> providers =

Collections.emptyList();

private AuthenticationManager parent;public ProviderManager(AuthenticationProvider... providers) {

this(Arrays.asList(providers), null);

}

/**

* 使用给定的{@link AuthenticationProvider}构造一个{@link

ProviderManager}

*

* @param providers the {@link AuthenticationProvider}s to use

*/

public ProviderManager(List<AuthenticationProvider> providers) {

this(providers, null);

}

/**

* 使用给定的参考构造一个{@link ProviderManager}

*

* @param providers the {@link AuthenticationProvider}s to use

* @param parent a parent {@link AuthenticationManager} to fall back

to

*/

public ProviderManager(List<AuthenticationProvider> providers,

AuthenticationManager parent) {

Assert.notNull(providers, "providers list cannot be null");

this.providers = providers;

this.parent = parent;

checkState();

}

}

在此类中,构造函数有List<AuthenticationProvider> providers,它的作用是真正地完成认证工作。Spring Security有多种认证方式,如邮箱登录、手机号登录和第三方登录等,只要一个认证成功了,就表示认证成功。

Spring Security的验证机制

核心类AuthenticationManager调用其他的实现类进行认证。在SpringSecurity中提供认证功能的接口是
org.springframework.security.authentication.AuthenticationProvider。

该接口有两个方法:authenticate()方法用来认证处理,返回一个authentication的实现类,代表认证成功;supports()方法表示当前身份提供者支持认证什么类型的身份信息,如果支持返回true,才会执行authenticate()方法进行身份认证。该接口的部分源码如下:

public interface AuthenticationProvider {

Authentication authenticate(Authentication authentication)

throws AuthenticationException;

boolean supports(Class<?> authentication);

}

AuthenticationProvider接口有几个常用的实现类,用来实现认证类型的具体方式,包括
AbstractUserDetailsAuthenticationProvider、DaoAuthenticationProvider和RememberMe- AuthenticationProvider。

DaoAuthenticationProvider的作用是从数据源中加载身份信息,其类图如图5.8所示。
RememberMeAuthenticationFilter的作用是当用户没有登录而直接访问资源时,首先从cookie中查找用户信息,如果Spring Security能够识别出用户提供的remember me cookie,则不用再输入用户名和密码,表示用户已经认证成功。如图5.9所示为RememberMeAuthenticationProvider类图。

Spring Security的认证流程如下:

(1)从
WebSecurityConfigurerAdapter认证配置的configure(HttpSecurity http)方法进入,并添加拦截器addFilterBefore。

(2)进入
AbstractAuthenticationProcessingFilter拦截器的attemptAuthentication方法,指定认证对象AbstractAuthenticationToken。

(3)执行AuthenticationProvider认证逻辑,根据supports的判断对认证的目标对象选择一个拦截器进行认证,进入具体的认证逻辑方法authenticate()。

(4)如果认证成功,则进入拦截器的successfulAuthentication()方法;如果认证失败,则进入拦截器的
unsuccessfulAuthentication方法()。

(5)对认证结果进行处理。

认证成功的逻辑:进入
SimpleUrlAuthenticationSuccessHandler的onAuthentication-Success()方法。

认证失败的逻辑:进入
SimpleUrlAuthenticationFailureHandler的onAuthentication-Failure()方法。

(6)将数据封装在ObjectMapper对象中后即可返回结果。

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

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

相关文章

FPGA基础 -- Verilog行为级建模之alawys语句

Verilog 中的 always 语句块&#xff0c;这是行为级建模的核心结构之一&#xff0c;在 RTL 级设计中广泛用于时序逻辑和组合逻辑的建模。 一、什么是 always 语句&#xff1f; ✅ 定义&#xff1a; always 语句用于描述可综合的硬件行为逻辑&#xff0c;表示一个**“事件驱动…

【力扣 简单 C】704. 二分查找

目录 题目 解法一&#xff1a;二分查找 题目 解法一&#xff1a;二分查找 int find(const int* nums, int size, int target) {int left 0, right size - 1;while (left < right){int mid (left right) / 2;if (nums[mid] < target)left left 1;else if (nums[m…

Java并发编程实战 Day 30:并发编程未来展望与最佳实践总结

【Java并发编程实战 Day 30】并发编程未来展望与最佳实践总结 文章简述 经过30天的系统学习&#xff0c;我们从Java并发编程的基础知识逐步深入到高并发系统的架构设计与性能优化。本文作为“Java并发编程实战”系列的收官之作&#xff0c;将全面回顾整个系列的核心内容&#…

量化面试绿皮书:23. 醉酒乘客

文中内容仅限技术学习与代码实践参考&#xff0c;市场存在不确定性&#xff0c;技术分析需谨慎验证&#xff0c;不构成任何投资建议。 23. 醉酒乘客 100名乘客排队登机&#xff0c;每人持有一张对应座位的机票&#xff08;第n位乘客的座位号为n&#xff09;。 第一位乘客喝醉后…

AntV G6入门教程

以下教程聚焦于 AntV G6 的 数据操作 API,详细介绍各个方法的用途、参数以及完整的使用示例,帮助你在图实例上精细地读取、修改和管理节点/边/组合等数据。文中示例代码均基于 G6 v5.0.47 官方文档 ([g6.antv.antgroup.com][1])。 一、获取完整图数据 1.1 graph.getData() …

67、数据访问-crud实验-分页数据展示

67、数据访问-crud实验-分页数据展示 分页数据展示是数据访问中常见的功能&#xff0c;用于将大量数据分割成多个页面显示&#xff0c;提升用户体验和系统性能。以下是分页数据展示的相关介绍&#xff1a; #### 基本原理 1. **确定每页显示数量**&#xff1a;设定每页显示的数…

常见 Web 服务器

Web 服务器有很多种&#xff0c;功能和用途略有不同&#xff0c;下面我会分类介绍主流的 Web 服务器&#xff08;包含静态/动态/反向代理支持&#xff09;并重点说明类似 Tomcat 的 Java 支持型。 常见 Web 服务器分类 类型名称描述与特点&#x1f310; 静态资源服务器Nginx高…

【MacOS】M3 Pro芯片MacBook极速搭建Kubernetes

M3 Pro 芯片 MacBook 2023上使用 Colima 安装 Kubernetes。 Colima 轻量、高效&#xff0c;并且在 Apple Silicon 架构上表现出色。 下面是详细的、一步一步的安装和配置指南。 核心思路 我们将通过以下步骤完成整个过程&#xff1a; 准备工作: 安装必要的工具&#xff0c;…

import { Add, Dongdong, UserAdd } from ‘@nutui/icons-react‘ 使用图标导入库报错

import { Add } from "nutui/icons-react-taro"; 官网的导入的库名字不全&#xff0c;后面要加-taro&#xff0c;就行了

猿人学js逆向比赛第一届第七题

分析响应 看到响应体里面的data是个字体加密&#xff0c;于是这里可以看到woff文件也给返回了&#xff0c;这里现分析这个文件。 打开可以看到这里a351对应的是3和页面中的3是对应的&#xff0c;于是用ddddocr动态识别字体文件中的字体&#xff0c;然后对应对应的字体替换是不…

股票心理学习篇:交易的人性弱点 - 频繁交易

以下内容为学习时的笔记整理&#xff0c;视频作者来自B站&#xff1a;老猫与指标 视频链接&#xff1a;频繁交易必死&#xff1f;底层逻辑深度剖析&#xff0c;老猫的的破局心法与实战策略分享 交易的人性弱点 - 频繁交易 主讲人&#xff1a; 老猫 1. 引言&#xff1a;问题的…

WPF入门 #1 WPF布局基础、WPF样式基础、WPF数据模板、WPF绑定

WPF当中有许多的布局容器控件&#xff0c;例如<Grid>、<StackPanel>、<WrapPanel>、<DockPanel>、<UniformGrid>。接下来分别介绍一下各个布局容器控件。 布局基础 Grid <Grid><Grid.RowDefinitions><RowDefinition Height&qu…

开源大型语言模型的文本记忆新突破!

在现代科技的推动下&#xff0c;人工智能领域正在不断地突破人类认知的极限。今年&#xff0c;由斯坦福大学、康奈尔大学和西弗吉尼亚大学的计算机科学家们&#xff0c;与法律学者共同展开了一项引人入胜的研究&#xff0c;聚焦于开源大型语言模型的文本记忆表现。这项研究不仅…

LeetCode 3090.每个字符最多出现两次的最长子字符串

题目链接 https://leetcode.cn/problems/maximum-length-substring-with-two-occurrences/ 题目描述 给定一个字符串 s&#xff0c;找出满足每个字符最多出现两次的最长子字符串&#xff0c;并返回其长度。 示例 输入&#xff1a;s "aabba" 输出&#xff1a;5解…

使用开源NVIDIA cuOpt加速决策优化

使用开源NVIDIA cuOpt加速决策优化 文章目录 使用开源NVIDIA cuOpt加速决策优化决策优化的现实挑战供应链优化的复杂性实时决策的挑战计算复杂性的挑战 NVIDIA cuOpt&#xff1a;GPU加速的决策优化解决方案cuOpt的核心技术架构支持的优化问题类型性能优势分析 实际应用案例&…

【JVM 09-垃圾回收】

垃圾回收 笔记记录 1. 如何判断对象可以回收1.1 引用计数法1.1.1 缺点 1.2 可达性分析算法1.2.1 可达分析、根对象1.2.2 优缺点 1.3 四种引用(强软弱虚)1.3.1 软引用的实际使用案例1.3.2 软引用-引用队列1.3.3 弱引用的实际使用案例 2. 垃圾回收算法2.1 标记清除算法2.2 标记整…

《二叉搜索树》

引言&#xff1a; 上次我们结束了类和对象的收尾&#xff0c;之后我们就要学习一些高级的数据结构&#xff0c;今天我们先来看一个数据结构-- 二叉搜索树。 一&#xff1a; 二叉搜索树的概念(性质) 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是…

【Redis】Sentinel哨兵

&#x1f6e1;️ 深入理解 Redis Sentinel&#xff1a;高可用架构的守护者 在实际开发中&#xff0c;我们常用 Redis 构建缓存系统或数据中间件。然而&#xff0c;主从复制虽然能实现数据同步&#xff0c;但无法自动故障转移&#xff08;failover&#xff09;&#xff0c;这就…

Shell脚本应用及实战演练

文章目录 一、Shell脚本语言的基本结构1、Shell脚本的用途&#xff1a;2、 Shell脚本基本结构&#xff1a;3、 创建Shell脚本过程4、 脚本注释规范 二、Shell脚本语言的变量用法详解位置与预定义变量 三、 Shell字符串详解1、Shell字符串拼接2、Shell字符串截取3、 Shell的格式…

软件工程瀑布模型学习指南

软件工程瀑布模型学习指南 一、瀑布模型核心概念 1.1 定义与特点 瀑布模型是一种经典的软件开发流程,将项目划分为顺序性的阶段,每个阶段有明确的输入和输出,如同瀑布流水般单向推进。其特点包括: 阶段间具有明确的顺序性和依赖性强调文档驱动和阶段评审适合需求明确、稳…