目录

前言

一、引入依赖

二、提供正常的业务接口

三、自定义用户认证

3.1 编写配置类

3.2 编写UserDetailsService实现类

3.3 启动项目,完成认证功能的验证

3.4 小说明

3.5 自定义用户登录页面及访问权限基本设置

四、授权

4.1 增加的授权代码

4.2 验证

4.3 常见的授权方法

4.4 自定义403页面

五、授权(注解方式)

5.1 主启动类上添加注解

5.2 控制器方法添加相关授权注解

六、关于密码加密补充说明

七、用户注销

7.1 配置类中增加注销相关配置

7.2 编写退出超链接

7.3 测试

八、记住我功能的实现

8.1 数据库建表

8.2 编写配置类

8.3 完善登录页面

8.4 测试

九、CSRF功能

十、踢下线功能

10.1 核心代码

10.2 测试


前言

Spring Security是非常流行的安全(权限)框架,Web应用框架。

主要有两大作用:一个是认证,一个是授权

本质上,它就是Filter过滤器。而且是过滤器链。

SpringSecurity与Shiro的区别

Spring Security的特点:

  1. Spring家族的,能很好的整合Spring。

  2. 专门为Web应用开发设计的。

  3. 提供专业全面的权限。

  4. 重量级的。依赖于很多其他组件。在SSM中整合比Shiro麻烦。但在springboot中提供了自动配置方案。

Shiro的特点:

  1. 它是Apache下的轻量级的权限框架。

  2. 轻量级的,依赖少,本身的大小也相对小。

  3. 不局限于Web环境,JavaSE下也可以运行。

  4. 缺点是针对Web环境下特定需求需要手动编写代码定制。功能没有Spring Security强大。

一般来说,常见的安全管理技术栈:

SSM+shiro

Spring Boot + Spring Security

一、引入依赖

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

当然了,完整的springboot工程还会引入其他所需要的相关依赖,具体根据项目而定:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.1</version>
</dependency>
<dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.3</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.7.0</version>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

二、提供正常的业务接口

例如我这里提供的测试业务接口,具体的业务逻辑就不展示了:

三、自定义用户认证

实现通过查找数据库来获取用户名密码,完成登录功能。具体的密码校验由spring security内部完成。

3.1 编写配置类

设置使用哪个UserDetailsService 实现类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(password());}@Beanpublic PasswordEncoder password(){return new BCryptPasswordEncoder();}
}

3.2 编写UserDetailsService实现类

这个UserDetailsService接口是springsecurity内部提供的,我们只需要编写对应的实现类即可完成用户认证授权

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hssy.authoritydemo.entity.User;
import com.hssy.authoritydemo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.stereotype.Service;
import java.util.List;@Service
public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {List<GrantedAuthority> authorities =AuthorityUtils.commaSeparatedStringToAuthorityList("manager");LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUsername,username);User user = userMapper.selectOne(queryWrapper);if (user == null){throw  new UsernameNotFoundException("用户不存在");}return new org.springframework.security.core.userdetails.User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),authorities);}
}

3.3 启动项目,完成认证功能的验证

在验证之前,我们先为数据库中创建一个测试用户。

然后启动项目,我们访问任意的接口,即便是没有编写的接口,它默认都跳转到spring security自带的登录页面了。

此时,我们即可使用数据库准备好的测试用户【username:security】 【password:123456】进行验证。

假如使用错误的用户名密码,是无法登录的。

登录成功,完成跳转,由于我们没有编写对应的接口,所以如下是404白标签页面。

如果我们之前访问login接口,则登录成功会跳转到根路径,也就是localhost:8080

此时访问我们之前提供的接口,就都能正常访问了,如

3.4 小说明

通过以上案例,我们知道:

1. 系统默认会为我们提供一个登录页面和登录接口,我们不编写相应页面和接口代码也能实现。

2. 密码校验是由SpringSecurity内部完成。不需要我们来处理。我们只需要将数据库查出来的用户名和密码交给spring security提供的User类即可。

3. 如果想自定义登录页面或者登录处理接口,那么还需要增加一项配置。下面一起来看看吧。

3.5 自定义用户登录页面及访问权限基本设置

3.5.1 代码

主要是通过配置类中,重写configure(HttpSecurity http)的这个方法

    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//表示进行表单登录.loginPage("/login.html")//自定义的登录页面.loginProcessingUrl("/login")//传一个登录处理的接口,不管你传的接口地址是什么,都由Security内部完成。当然也可以自己写这个接口,这样就不会用系统来完成登录处理校验用户名密码了。还有就是如果自定义了登录页面,那么登录处理的接口loginProcessingUrl项一定要写,不管是写系统自带的,还是你自己写的处理接口都行,否则报错。.usernameParameter("username") //定义登录时的用户名的key,即表单中name的值,默认为username.passwordParameter("password") //定义登录时的密码key,即表单中name的值,默认是password//设置的这两个用户名、密码的key,如果不自己写登录页面的话,可以不用写,因为系统默认提供的页面就是这个默认值。写了的话,一定要与表单页面中定义的name值一致才行。.defaultSuccessUrl("/pages/main")//登录成功跳转到的页面或者路径。当然,如果你不是从登录页面登录的,那么拦截之后会进入到你的请求路径(或页面)中.failureUrl("/login.html")//登录失败跳转到的页面.permitAll() //指和登录表单相关的接口 都通过,不拦截.and().authorizeRequests()//开启授权请求.antMatchers("/","/pages/main","/login").permitAll()//设置哪些路径放行,不需要认证   不需要登录可以访问的.anyRequest().authenticated()//除开上面的,其他所有请求全部都需要权限验证。因为还没有用户授权,所以目前所有的接口登录后都能访问。.and().csrf().disable();//关闭csrf防护}

3.5.2 验证

此时重启项目,它就会自动跳转到我们配置的login.html页面,由于没有编写登录页的代码,它就会报错404。

我们编写一份前端代码吧

此时再次重启验证

当然了,我这里一开始地址栏输入的是访问/data-city/findAll这个接口,否则直接访问登录页的话,它就会跳转到我们配置的登录成功后的路径去。

四、授权

我们再开始之前再多写几个测试接口

前面我们在重写configure(HttpSecurity http)方法中,有开启基本的授权配置。

但是之前因为还没有用户授权,也没有配置哪些路径需要什么样的权限才能访问,所以所有的接口在登录后都能访问。

4.1 增加的授权代码

所以,我们只需要再增加哪些路径需要什么权限才能访问即可完成授权。其他不用变

.antMatchers("/security/test1").hasAuthority("admin")//表示当前登录用户,只有具有权限名称为admin时,才能访问此地址
    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//表示进行表单登录.loginPage("/login.html")//自定义的登录页面.loginProcessingUrl("/login")//传一个登录处理的接口,不管你传的接口地址是什么,都由Security内部完成。当然也可以自己写这个接口,这样就不会用系统来完成登录处理校验用户名密码了。还有就是如果自定义了登录页面,那么登录处理的接口loginProcessingUrl项一定要写,不管是写系统自带的,还是你自己写的处理接口都行,否则报错。.usernameParameter("username") //定义登录时的用户名的key,即表单中name的值,默认为username.passwordParameter("password") //定义登录时的密码key,即表单中name的值,默认是password//设置的这两个用户名、密码的key,如果不自己写登录页面的话,可以不用写,因为系统默认提供的页面就是这个默认值。写了的话,一定要与表单页面中定义的name值一致才行。.defaultSuccessUrl("/pages/main")//登录成功跳转到的页面或者路径。当然,如果你不是从登录页面登录的,那么拦截之后会进入到你的请求路径(或页面)中.failureUrl("/login.html")//登录失败跳转到的页面.permitAll() //指和登录表单相关的接口 都通过,不拦截.and().authorizeRequests()//开启授权请求.antMatchers("/","/pages/main","/login").permitAll()//设置哪些路径放行,不需要认证   不需要登录可以访问的.antMatchers("/security/test1").hasAuthority("admin")//表示当前登录用户,只有具有权限名称为admin时,才能访问此地址.anyRequest().authenticated()//除开上面的,其他所有请求全部都需要认证。.and().csrf().disable();//关闭csrf防护}

4.2 验证

重启项目,然后登录跳转,发现403,说明成功了,我们的页面没有权限,403表示无权限禁止访问。

如果给我们的用户增加权限呢,就是再我们的UserDetailsService实现类中,重写方法增加这个权限即可。

当然呢,正常情况下,不同的用户会有不同的权限,我们可以通过在数据库添加权限,然后查询数据库的权限,传递过来即可。就不用写死。

我们再重启测试一下,发现就能访问了。

4.3 常见的授权方法

除了上述案例中的使用到了第一个授权方法:

1. hasAuthority(String authority)

意思是如果当主体具有指定的权限,则返回true,否则返回false。

2. hasAnyAuthority(String... authorities)

如果当前主体具有任意一个权限,则返回true,否则返回false。

3. hasRole(String role)

如果当前主体具有指定的角色,则返回true,否则返回false。

需要注意的是如果是hasRole,那么在userDetailsService实现类中的角色名前面一定要添加ROLE_

4. hasAnyRole(String... roles)

如果当前主体具备任何一个角色,则返回true,否则返回false。

4.4 自定义403页面

通过以上的授权方法,我们可以完成授权功能。当我们的用户没有相应的权限时,则会出现403白标签页面。

为了更加友好的展示,我们会选择自定义403页面。

具体的做法也很简单。

4.4.1 修改访问配置类

增加代码:

http.exceptionHandling().accessDeniedPage("/unauth");

4.4.2 添加对应控制器方法

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class SystemController {@GetMapping("/unauth")public String unauth(){return "当前用户无权限访问";}
}

 4.4.3 重启测试

待访问/security/test2接口,我们配置一下,不给测试用户相应的权限,就无权限访问。

.antMatchers("/security/test2").hasAnyAuthority("fang1","fang2")

4.4.4 其他写法

我们的控制器方法,也可以是正常的返回一个Result对象,这样如果是前后端分离的,根据Result对象由前端去生成相应的页面也可以。

或者,后端也可以提供一个页面,返回一个转发试图。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;//@RestController
@Controller
public class SystemController {@GetMapping("/unauth")public String unauth(){//    return "当前用户无权限访问";return "forward:403.html";}
}

五、授权(注解方式)

除了上述的在配置文件中通过配置hasAuthority、hasAnyAuthority、hasRole、hasAnyRole这些方法配置外,我们也可以在对应的控制器方法上,添加对应的注解来进行授权访问。

5.1 主启动类上添加注解

@EnableGlobalMethodSecurity(securedEnabled=true)

5.2 控制器方法添加相关授权注解

@Secured

判断是否具有某个角色

另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。

以上说明拥有teacher或者student角色的用户才能访问该方法。

但是如果要求同时满足拥有这两个角色的用户才能访问,@Secured注解就无能为力了。

@PreAuthorize(重点)

判断是否具有某个角色权限,也判断是否同时具有某些角色权限

它比@Secured的能力更大,@Secured只能判断是否具有某个角色

//拥有normal或者admin角色的用户都可以方法helloUser()方法。
@GetMapping("/helloUser")
@PreAuthorize("hasAnyRole('normal','admin')")
public String helloUser() {
return "hello,user";
}//同时拥有normal和admin角色的用户才能访问
@GetMapping("/helloUser")
@PreAuthorize("hasRole('normal') AND hasRole('admin')")
public String helloUser() {
return "hello,user";
}

六、关于密码加密补充说明

前面我们代码中使用到了两处

一处是配置类中注入了PaswordEncoder的bean对象

第二处是UserDetailsService实现类中,返回User对象时,第二个形参中设置的密码加密。

其实,通常而言,在配置类中注入PaswordEncoder的bean对象是必须的,因为Spring Security 要求容器中必须有 PasswordEncoder 实例,才能加密。所以当我们手动加入自定义登录逻辑时,要求必须给容器注入PaswordEncoder的bean对象。不写会报错,如:There is no PasswordEncoder mapped for the id ”null"

当然了,如果不想使用它自带的加密方式,也可以使用自己的。写一个类实现PasswordEncoder接口。

但是第二处,也就是UserDetailsService实现类中,返回User对象时,第二个形参其实最好不要再加密一次。这就不得不提这个User对象的作用了。总而言之,如果此时再加密,就相当于了解密,也就意味着数据库中必须是明文的形式。如果此时返回的User对象密码不加密,也就意味着数据库中的密码必须是密文的形式。实际开发中,肯定是希望数据库中的密码为密文了,这样更加安全。比如用户通过输入密码1234567,传到后台被spring security拦截,它首先通过配置文件中注入的PaswordEncoder的bean对象进行加密,然后内部会通过我们UserDetailsService实现类中查询数据库返回的User对象,进行用户名和密码进行比对。所以UserDetailsService实现类中的User对象不要进行加密了。

我们修改后重新测试一下,

先手动给数据库生成一个测试用户

只有用户自己知道真实的密码是多少,其他人仅通过数据库是无法知晓真实密码的。

然后修改UserDetailsService实现类

重启测试

假如我们使用数据库中存储的密文,进行登录,此时是不能登录成功的。

另外,我们不难发现,同一个字符串,通过加密生成的字符串每次都不一样,但是尽管每次都不一样,也都不会匹配失败。换句话说,同一个密码生成的密文每次都不一样,但是无论是哪个密文,最终都能解析成功。

七、用户注销

7.1 配置类中增加注销相关配置

        //退出配置http.logout().logoutUrl("/logout")//退出登录的处理接口随便写,系统帮你实现。和.loginProcessingUrl类似.logoutSuccessUrl("/login.html")//退出成功跳转的页面或接口.permitAll();

7.2 编写退出超链接

目的是通过点击超链接,跳转到配置类中设置的退出登录处理接口。

之前我们一直没写主页面,要不这次我们就编写一个主页面,然后在主页面完成退出超链接跳转吧。

7.3 测试

八、记住我功能的实现

什么是记住我功能?

比如说,我们进行登录之后,把浏览器关闭掉。下次再访问网站时,不需要重新进行登录。

比如说,163邮箱,它有一个十天内免登录,当我们勾选了以后,那么十天内都不用重新输入密码进行登录了。

正常而言,我们关闭浏览器后,默认cookie会消失,不信可以试试。

然后关闭浏览器,重新访问测试接口

提示我们需要重新登录

正常而言,我们关闭浏览器后,默认cookie会消失。但是如果设置了记住我,那么它会生成一个叫remember me的cookie,这个cookie包含了用户信息,且不会消失,直到我们设置的过期时间到了才会消失。

因此我们关闭后,再次请求,它会带着这个rememberme(就是token)到我们的服务器中查询建立的新的数据库表信息。【系统会在内存中自动创建表,但是处于安全考虑,我们一般会在自己的数据库建表。】

8.1 数据库建表

CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`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;

8.2 编写配置类

当然也可以在原来那个配置类中写。也可以新建一个配置类。都可以。

    /*** 自动登录 配置类中,注入数据源和配置操作数据库对象*///注入数据源@Autowiredprivate DataSource dataSource;//注入操作数据库的对象JdbcTokenRepositoryImpl,用它来创建token// 当然最好选择返回上层接口。我们一般是这样的,因为多态方便后续修改维护。@Beanpublic PersistentTokenRepository persistentTokenRepository(){JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();//  赋值数据源jdbcTokenRepository.setDataSource(dataSource);//  自动创建表 , 第一次执行会创建,以后要执行就要删除掉!//jdbcTokenRepository.setCreateTableOnStartup(true);//这里我们是自己创建的数据库。所以不要这句return jdbcTokenRepository;}@Overrideprotected void configure(HttpSecurity http) throws Exception {.......and().rememberMe()//开启记住我功能.tokenRepository(persistentTokenRepository())//把操作数据库对象传进来.tokenValiditySeconds(60)//表示自动登录,60s内有效.userDetailsService(userDetailsService)//查询数据库的service......}

8.3 完善登录页面

在登录页面添加复选框,要求name值必须为remember-me,否则SpringSecurity找不到。

8.4 测试

这样在60s内关闭浏览器后,重新打开是不需要登录的。只有超过这个时间才需要重新登录。

当每次自动登录时,会在浏览器中将token存cookie值。同时会在persistent_logins这张表中生成相应的数据。注意这些都是SpringSecurity帮我们实现的。

我们关闭浏览器,再次打开发现,不用我们登录了。这就是因为开启了remember-me。就是SpringSecurity帮我们存了token到cookie中。当然设置的过期时间到了还是要重新登录的,你也可以不设置过期时间,永不过期。但是不建议。

我们可以通过如下方法设置这个时间。比如说7天内免登录,那就是60*60*24*7

 .tokenValiditySeconds(60*60*24*7)

九、CSRF功能

前面配置文件中,我们配置过一项,就是

那么什么是CSRF呢?

即跨站请求伪造(Cross-site request forgery)

跨站请求位置默认开启。针对 PATCH,POST,PUT 和 DELETE 方法进行防护。

想要实现该功能,只需要在配置类中开启CSRF的情况下,在前端中设置如下:

<input type="hidden"th:if="${_csrf}!=null"th:value="${_csrf.token}"name="_csrf"/>

一般我们测试的时候,免得在前端还要加上这个代码。都选择关闭CSRF功能。如果你不关闭,那么在前端表单登录的代码中一定要加上上面这段。否则你自己写的登录页面(属于跨站),POST提交就会被进行防护。

十、踢下线功能

10.1 核心代码

只需要在配置类中增加session相关配置

        //踢下线配置http.sessionManagement().maximumSessions(1) // 表示同一个用户最大登录客户端的数量为1.maxSessionsPreventsLogin(false) // 阻止登录策略,如果为true,表示已经登录就不允许在别的地方登录了。如果为false,则表示在其他地方登录后,就会踢出之前其他地方登录的该账号。.expiredSessionStrategy(new SessionInformationExpiredStrategy() {// 方法一:页面跳转的方式处理//private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();// 当发现session超时,或者session被踢下线之后,要进行的处理//@Override//public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {//    redirectStrategy.sendRedirect(event.getRequest(),event.getResponse(),"/forced");//}// 方法二:前后端分离的情况下,一般是返回json数据// 可以使用springboot默认的jackson的json处理对象,当然你也可以使用其他json工具private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {Map<String, Object> map = new HashMap<>();map.put("code",50009);map.put("data",null);map.put("msg","您已在其他地方进行了登录,请核实是否为本人操作!");String json = objectMapper.writeValueAsString(map);event.getResponse().setContentType("application/json;charset=utf-8");event.getResponse().getWriter().write(json);}});

10.2 测试

先在谷歌浏览器上测试

换用其他浏览器登录同一账号

此时回到谷歌浏览器,点击刷新页面,就提示在其他地方进行了登录了。被迫下线!

一篇文章轻松搞定SpringSecurity权限框架!相关推荐

  1. 搭建简易个人博客(一篇文章就搞定!)

      搭建我的个人博客这个想法很早就有了,之前仅仅了解到在CSDN和博客园上可以创建我自己的主页,但感觉很没有趣味性,也就没有实施.偶然在B站上看到搭建动态和静态博客的教程,维护和建设自己亲手搭建好的网 ...

  2. Xposed精品连载 | 一篇文章彻底搞定安卓刷机与Root

    前言 adb与fastboot 开启开发者选项 开启USB调试 Bootloader解锁 如何进入Bootloader模式 刷机 Root 安卓是基于Linux内核的一个移动操作系统.在Linux这种 ...

  3. mybatis-plus团队新作:mybatis-mate 轻松搞定数据权限

    欢迎关注方志朋的博客,回复"666"获面试宝典 今天介绍一个 MyBatis - Plus 官方发布的神器:mybatis-mate 为 mp 企业级模块,支持分库分表,数据审计. ...

  4. git使用教程-一篇文章全搞定哦

    Git使用教程 Git是什么 文章转载自 代码飞:https://code.bywind.cn/2018/07/14/170/ Git是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项 ...

  5. 一篇文章 轻松搞懂 AC自动机

    索引 概念 前后缀匹配 Trie树 AC自动机的实现 初始化 Fail指针的构建 匹配字符串 一名蒟蒻向您问好. 概念 这是 AC自动机,不是自动AC机, 是一个十分常用的多模式字符串匹配算法 (也就 ...

  6. ununtu20.04系统中如何划词翻译_如何高效阅读PDF外文文献,这一篇文章全搞定

    对于很多初学者而言,阅读外文文献时非常痛苦的.句型复杂.生词多以及读者本身对外文文献的排斥感,都是造成阅读障碍的主要因素. 当然,如果你学会以下小编分享的一些技巧,可能并不觉得阅读那么难熬. 首先:建 ...

  7. 【Linux】一篇文章彻底搞定信号!

    信号 1.信号是什么? 2.信号的种类 3.信号的产生 3.1硬件产生 3.2软件产生 4.信号的注册 4.1非可靠信号的注册 4.2可靠信号的注册 5.信号的注销 5.1非可靠信号的注销 5.2可靠 ...

  8. 如何高效阅读PDF外文文献,这一篇文章全搞定!

    对于很多初学者而言,阅读外文文献时非常痛苦的.句型复杂.生词多以及读者本身对外文文献的排斥感,都是造成阅读障碍的主要因素. 当然,如果你学会以下小编分享的一些技巧,可能并不觉得阅读那么难熬. 首先:建 ...

  9. linux脚本pend什么意思,【Linux】一篇文章彻底搞定信号!

    信号 1.信号是什么? 2.信号的种类 3.信号的产生 3.1硬件产生 3.2软件产生 4.信号的注册 4.1非可靠信号的注册 4.2可靠信号的注册 5.信号的注销 5.1非可靠信号的注销 5.2可靠 ...

最新文章

  1. linux 关闭开机 ftp,解决linux ftp匿名上传、下载开机自启问题
  2. linuv创建文件的命令_ECS实践案例丨逻辑卷的创建和扩容操作指导
  3. java成果_JAVA WEB期末项目第二阶段成果
  4. 黄聪:WordPress判断当前用户是否为管理员登录
  5. python语言学完后学什么_学完Python语言可以做什么?发展前景怎么样?
  6. php二维数组 xml,xml 怎样通过php解析到二维数组里面
  7. 湖南计算机非全学校推荐,小心!湖南这7所大学害人不浅,毕业证一文不值,各位家长注意了...
  8. 你需掌握的CSS知识都在这了(长文建议收藏,文末有福利)
  9. 在浏览器上运行Qt应用 emscripten-qt
  10. 书单|互联网企业面试案头书之数据分析师篇
  11. ubuntu20.04安装交叉编译工具链流程
  12. python多行注释出错_python 有多行注释运行报错
  13. Excel2016 保存\复制 卡死问题解决
  14. JSTL 标签大全详解
  15. excel拼接换行符:char(10)
  16. 【Swoole系列3.5】进程池与进程管理器
  17. 网页数据获取小技巧(小白)
  18. 蓝桥杯练习 数的读法
  19. java web二手书店,基于jsp的二手书交易-JavaEE实现二手书交易 - java项目源码
  20. java fresh 水果,《蓝莓甜杏面包/ Fresh Apricot and Blueberry Tea Bread》把水果做进面包里...

热门文章

  1. 来鑫干货解读:迅雷链到底有什么了不起?
  2. python 爬虫-养生之道
  3. mysql源码包多大_MySQL源码包安装
  4. 基于stm32的智能婴儿床(毕业设计)
  5. 华为芯片鸿蒙的由来,华为“鸿蒙”真的来了!看完这些商标来历,网友们又激动了...
  6. 48页智慧城市大数据可视化平台建设方案
  7. 不忘初心,能偷懒就偷懒:C#操作Word文件
  8. android看黑白电子书软件,如何优雅解决App启动黑白屏
  9. 等保2.0:这些等保测评要求,你都知道吗?
  10. 长隆大马戏机器人_腾讯长隆打造智能主题酒店 小Q机器人全面部署智慧旅游