文章目录

  • 目录

    一、spring security 核心功能

    二、配置用户存储及自定义登录页

    三、防范CSRF攻击

    四、退出及获取登录用户信息

    五、Spring Security基本原理

    六、自定义用户详情服务基于数据库实现登录认证及授权

    1. 启用Spring Security

    2. 定义用户领域对象

    3. 使用JPA接口查询用户

    4. 自定义创建用户详情服务

    5. 保护WEB请求,用户认证与授权

    6. 定义视图控制器,指定根路径和登录页

    7. 提供测试控制器(Controller)

    8. 相关页面

    9. 工程目录结构

    总结



一、spring security 核心功能

用户认证(Authentication):系统判断用户是否能登录
用户授权(Authorization):系统判断用户是否有权限去做某些事情
攻击防护:防范CSRF攻击

权限管理中的相关概念:

名称 英文名称 概念
主体 principal 使用系统的用户或设备或从其他系统远程登录的用户等等
认证 authentication 权限管理系统(通过登录操作)确认一个主体的身份,允许主体进入系统。简单说就是“主体”证明自己是谁
授权 authorization 给用户分配权限:将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统中特定功能的能力

二、配置用户存储及自定义登录页

多年来出现了多钟配置spring security的方式,
●  有基于XML的配置;
●  使用方法注解的配置;
●  基于java的配置;
最近几个版本的spring security都支持基于java的配置。

spring security提供的多种配置用户存储的可选方案:
●  基于内存的用户存储
●  基于JDBC的用户存储
●  基于LDAP(轻量级目录访问协议)作为后端的用户存储
●  自定义用户详情服务(可基于数据库读取用户)

自定义登录页即:使用自己的登录页面替换spring security提供的默认登录页

三、防范CSRF攻击

CSRF(跨站请求伪造)是一种常见的安全攻击。让用户在一个恶意的WEB页面上填写信息,然后自动将表单以攻击受害者的身份提交到另外一个应用上。
        spring security提供内置的CSRF保护,默认就是开启的。我们需要在页面表单中有一个名为“_csrf”的字段,他会持有CSRF token。服务端获取这个token与其记录的token对比来确保安全。

禁用spring security对CSRF支持:.and().csrf().disable();

四、退出及获取登录用户信息

●  退出
http.logout().logoutUrl("/logout").logoutSuccessUrl("/").permitAll();--会退出到根目录指定的地方
        启用退出功能,需在HttpSecurity对象上调用logout方法,这样会搭建一个安全过滤器,此过滤器会拦截对"/logout"的请求。用户页面点击退出按钮后便会清空session。

●  了解登录用户是谁
常用的几种方式获取用户信息:
注入 Principal 对象到控制器方法中;
注入 Authentication 对象到控制器方法中;
使用 SecurityContextHolder 来获取安全上下文;
使用 @AuthenticationPrincipal 注解来标注方法;--最整洁易使用的方式

五、Spring Security基本原理

Spring Security本质是一个过滤器链,由许多过滤器组成,其中几个过滤器:
●  FilterSecurityInterceptor:方法级的权限过滤器, 基本位于过滤链的最底部。
●  ExceptionTranslationFilter:异常过滤器,用来处理在认证授权过程中抛出的异常
●  UsernamePasswordAuthenticationFilter:对/login(Security默认的登录请求监听路径)的 POST请求做拦截,校验表单中用户名,密码。拦截表单中的用户名和密码封装成UsernamePasswordAuthenticationToken。然后完成UsernamePasswordAuthenticationToken和UserDetails密码的对比。

六、自定义用户详情服务基于数据库实现登录认证及授权

1. 启用Spring Security

添加Spring Security起步依赖到构建文件pom.xml

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

2. 定义用户领域对象

实现UserDetails接口重写其属性或方法。不实现UserDetails也行,在自定义创建用户详情服务(MyUserDetailsService)类总采用第二种提供User对象的方式。SimpleGrantedAuthority("ROLE_USER")的角色ROLE_USER和第5步SecurityConfig类中antMatchers("/test/adduser","/test/findAllUser","/test/hello").hasRole("USER")这里指定的角色USER对应

package com.securitydemo.entity;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import javax.persistence.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Set;/*** 用户领域实体bean数据表映射*/
@Entity
@Table(name = "SYS_USER", schema = "PRODUCTION", catalog = "")
public class User implements UserDetails {private long id;private String password;private String username;private Date addtime;//实现UserDetails接口重写的属性或方法//private Set<GrantedAuthority> authorities;//角色private Boolean accountNonExpired;//账户没有过期private Boolean accountNonLocked;//账户没有锁定private Boolean credentialsNonExpired;//密码没有过期private Boolean enabled;//账户可用@SequenceGenerator(name = "generator", sequenceName = "SEQ_A_TBL", allocationSize = 1)@Id@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")@Column(name = "ID", unique = true, nullable = false, scale = 0)public long getId() {return id;}public void setId(long id) {this.id = id;}@Basic@Column(name = "PASSWORD")public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Basic@Column(name = "USERNAME")public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@Basic@Temporal(TemporalType.TIMESTAMP)@Column(name = "ADDTIME")public Date getAddtime() {return addtime;}public void setAddtime(Date addtime) {this.addtime = addtime;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;User that = (User) o;if (id != that.id) return false;if (password != null ? !password.equals(that.password) : that.password != null) return false;if (username != null ? !username.equals(that.username) : that.username != null) return false;if (addtime != null ? !addtime.equals(that.addtime) : that.addtime != null) return false;return true;}@Overridepublic int hashCode() {int result = (int) (id ^ (id >>> 32));result = 31 * result + (password != null ? password.hashCode() : 0);result = 31 * result + (username != null ? username.hashCode() : 0);result = 31 * result + (addtime != null ? addtime.hashCode() : 0);return result;}//实现UserDetails接口重写的属性或方法@Transient  //@Transient注释意思是不会被Spring Data JPA框架序列化到数据库,单纯的作为一个临时字段,接收完数据后就暂且用不上了@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));}//    public void setAuthorities(Set<GrantedAuthority> authorities) {
//        this.authorities = authorities;
//    }@Column(name = "ACCOUNTNONEXPIRED")public Boolean getAccountNonExpired() {return accountNonExpired;}public void setAccountNonExpired(Boolean accountNonExpired) {this.accountNonExpired = accountNonExpired;}@Column(name = "ACCOUNTNONLOCKED")public Boolean getAccountNonLocked() {return accountNonLocked;}public void setAccountNonLocked(Boolean accountNonLocked) {this.accountNonLocked = accountNonLocked;}@Column(name = "CREDENTIALSNONEXPIRED")public Boolean getCredentialsNonExpired() {return credentialsNonExpired;}public void setCredentialsNonExpired(Boolean credentialsNonExpired) {this.credentialsNonExpired = credentialsNonExpired;}@Column(name = "ENABLED")public Boolean getEnabled() {return enabled;}public void setEnabled(Boolean enabled) {this.enabled = enabled;}@Transient@Overridepublic boolean isAccountNonExpired() {return accountNonExpired;}@Transient@Overridepublic boolean isAccountNonLocked() {return accountNonLocked;}@Transient@Overridepublic boolean isCredentialsNonExpired() {return credentialsNonExpired;}@Transient@Overridepublic boolean isEnabled() {return enabled;}
}

3. 使用JPA接口查询用户

package com.securitydemo.repository;import com.securitydemo.entity.User;
import org.springframework.data.repository.CrudRepository;/*** spring data JPA继承CrudRepository接口,用于Spring Security登录用户认证查询*/
public interface MyUserRepository extends CrudRepository<User, Long> {/*** 方法命名查询* @param username 用户名* @return*/User findByUsername(String username);}

4. 自定义创建用户详情服务

实现security提供的UserDetailsService接口。用户登录时,security会调用loadUserByUsername方法去数据库查询用户数据。

一.  此处User对象是com.securitydemo.entity.User自定义实体领域,其中实现了UserDetails接口。 自定义User实体领域实现UserDetails接口可以扩展除UserDetails接口中几个属性以外的许多其他属性,如用户的添加时间,备注,手机号等属性。

二.  如果自定义User实体领域不继承或者实现UserDetails接口,也可以通过用户名查询出用户信息后填装到org.springframework.security.core.userdetails.User 默认提供的User对象中返回。

不管怎样返回User对象,最终都要返回一个UserDetails。

package com.securitydemo.service.impl;import com.securitydemo.entity.User;
import com.securitydemo.repository.MyUserRepository;
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.stereotype.Service;import java.util.List;/*** 自定义创建用户详情服务,实现security提供的UserDetailsService接口*/
@Service
public class MyUserDetailsService implements UserDetailsService {/*** 注入UserRepository获取数据库用户信息*/private MyUserRepository myUserRepo;/*** MyUserDetailsService类通过构造器将MyUserRepository注入进来* @param userRepo*/@Autowiredpublic MyUserDetailsService(MyUserRepository userRepo){this.myUserRepo=userRepo;}/*** 用户登录时,security会调用loadUserByUsername方法去数据库查询数据* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {System.out.println("username==="+username);//根据页面登录的用户名查询数据库用户信息进行身份认证start/*** 一.* 此处User对象是com.securitydemo.entity.User自定义实体领域,其中实现了UserDetails接口。* 自定义User实体领域实现UserDetails接口可以扩展除UserDetails接口中几个属性以外的许多其他属性,如用户的添加时间,备注,手机号等属性。** 二.* 如果自定义User实体领域不继承或者实现UserDetails接口,也可以通过用户名查询出用户信息后填装到org.springframework.security.core.userdetails.User* 默认提供的User对象中返回。* 不管怎样返回User对象,最终都要返回一个UserDetails。*/User user=myUserRepo.findByUsername(username);System.out.println("user-====="+user);//判断if(user!=null){System.out.println(user.getUsername()+"---====---"+user.getPassword()+"----"+user.getAddtime());return user;}throw new UsernameNotFoundException(username+"用户名不存在!");//根据页面登录的用户名查询数据库用户信息进行身份认证end/*** 二. 通过用户名查询出用户信息后填装到org.springframework.security.core.userdetails.User默认提供的User对象中返回。*/
//        //手动设置了权限及角色,也可以通过数据库查询获取
//        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("addUser,findAll,ADMIN,USER");  //配置权限及角色
//        //根据页面登录的用户名查询数据库用户信息进行身份认证
//        return new User(atblUserLv.getUsername(),atblUserLv.getPassword(),auths);}
}

5. 保护WEB请求,用户认证与授权

启动@Configuration和@EnableWebSecurity注解

继承WebSecurityConfigurerAdapter类,使用AuthenticationManagerBuilder认证用户(将数据库中查询到的用户详情传递进去)HttpSecurity保护WEB请求,添加自定义登录页面与权限控制代码

package com.securitydemo.Config;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.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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;/*** 自定义用户名密码* 方式一:通通过配置类进行配置实现身份认证登录(基于内存的用户存储)* 方式二:自定义实现类实现身份认证登录(基于数据库的用户存储)*/
@Configuration //表明是一个配置类bean
@EnableWebSecurity //此注解1: 加载了WebSecurityConfiguration配置类, 配置安全认证策略。2: 加载了AuthenticationConfiguration, 配置了认证信息。
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 方式一:通过配置类进行配置实现身份认证登录(基于内存的用户存储)* @param auth* @throws Exception*/
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        // 创建密码解析器
//        BCryptPasswordEncoder pe =new BCryptPasswordEncoder();
//        // 对密码进行加密
//        String password = pe.encode("a123456");
//        auth.inMemoryAuthentication()
//                .passwordEncoder(pe)  //默认没有,需要手动设置BCryptPasswordEncoder
//                .withUser("user01")
//                .password(password)
//                .roles("admin")
//                .and().withUser("user02").password(password).roles("user");
//
//    }/*** 方式二:自定义实现类实现身份认证登录,实现类MyUserDetailsService根据页面登录的用户名从数据库查询用户进行判断*/@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {System.out.println("SecurityConfig run...configure(AuthenticationManagerBuilder auth)");/*** 将自动装配到SecurityConfig中的UserDetailsService实例传递进去* 也就是将从数据库查询到的用户详情传递进去*/auth.userDetailsService(userDetailsService);//.passwordEncoder(encoder());}/*** 指定装备一个转码器使用注解@Bean* @return*/@Beanpublic PasswordEncoder encoder(){return new BCryptPasswordEncoder();//进行转码 //NoOpPasswordEncoder.getInstance(); 不进行转码}/*** 方式二:自定义实现类实现身份认证登录,继续添加自定义登录页面与权限控制代码*/@Overrideprotected void configure(HttpSecurity http) throws Exception {System.out.println("SecurityConfig run...configure(HttpSecurity http)");//配置没有权限访问跳转自定义页面//http.exceptionHandling().accessDeniedPage("/error.html");//添加退出的映射地址http.logout().logoutUrl("/logout").logoutSuccessUrl("/").permitAll();http.authorizeRequests().antMatchers("/test/adduser","/test/findAllUser","/test/hello").hasRole("USER") //具备指定权限的用户且认证通过才能访问指定请求。hasRole方法中默认加有"ROLE_"前缀.antMatchers("/","/**").permitAll() //其他请求允许所有用户访问,不需要认证 "/**".and().formLogin().loginPage("/logins") //登陆页面设置.loginProcessingUrl("/user/login") //登陆访问路径,监听此路径来处理登录信息的提交.defaultSuccessUrl("/test/findAllUser") //默认登录成功后跳转路径.and().csrf().disable();//        http.formLogin()   //自定义自己编写的登陆页面
//                .loginPage("/logins.html")  //登陆页面设置
//                .loginProcessingUrl("/user/login")  //登陆访问路径
//                .defaultSuccessUrl("/test/hello").permitAll() //登陆成功后跳转路径
//                .and().authorizeRequests() //授权
//                .antMatchers("/","/user/login").permitAll() //设置那些路径可以直接访问,不需要认证
//                .antMatchers("/test/adduser").hasAuthority("addUser") //当前用户只有具有addUser权限时才能访问该路径,需要在启动类或配置类中开启基于方法的安全认证机制
//                .antMatchers("/test/findAllUser").hasAnyAuthority("addUser,findAll")//具备其中任意一个权限.antMatchers("/test/hiRole").hasRole("admin").antMatchers("/test/hiAnyRole").hasAnyRole("admin,user")
//                .anyRequest().authenticated() //任何请求都必须经过身份验证
//                .and().csrf().disable() ;  //关闭csrf的保护}}

6. 定义视图控制器,指定根路径和登录页

package com.securitydemo.Config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 定义视图控制器,访问根目录时跳转到指定页面*/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {System.out.println("WebConfig run...");registry.addViewController("/").setViewName("home");registry.addViewController("/logins");}}

7. 提供测试控制器(Controller)

@AuthenticationPrincipal注解获取认证后登录的用户信息。

"/test/adduser","/test/findAllUser","/test/hello"这三个请求路径需要登录认证成功,且获得指定权限才能访问,其他两个方法未登录认证也可以访问。

package com.securitydemo.controller;import com.securitydemo.entity.AtblUserLv;
import com.securitydemo.entity.User;
import com.securitydemo.service.UserService;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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.annotation.Resource;
import java.util.Date;
import java.util.Iterator;
import java.util.List;/*** 测试用控制器(Controller)*/
@Controller //这个注解可返回显示视图页面。@RestController注解相当于@ResponseBody + @Controller合在一起的作用,方法返回字符串值
@RequestMapping("/test")
public class TestController {@Resourceprivate UserService userService;@GetMapping("/hello")@ResponseBody //这个注解此方法表示返回字符串内容hello securitypublic String hello(@AuthenticationPrincipal User user){System.out.println("了解登录用户是谁:"+user); //使用AuthenticationPrincipal注解,User是用户详情服务中的用户对象if(user!=null){System.out.println("了解登录用户是谁:"+user.getUsername()+"||"+user.getPassword()+"||"+user.getEnabled());}System.out.println("-----hello----");return "hello security";}@GetMapping("/adduser")public String addUser(@AuthenticationPrincipal User user){System.out.println("了解登录用户是谁:"+user); //使用AuthenticationPrincipal注解,User是用户详情服务中的用户对象if(user!=null){System.out.println("了解登录用户是谁:"+user.getUsername()+"||"+user.getPassword()+"||"+user.getEnabled());}System.out.println("-----添加用户----");AtblUserLv atblUserLv=new AtblUserLv();atblUserLv.setUsername("user005");atblUserLv.setPassword("12345");atblUserLv.setAddtime(new Date());userService.insertUser(atblUserLv);return "success";}@GetMapping("/findAllUser")public String findAllUsers(@AuthenticationPrincipal User user){System.out.println("了解登录用户是谁:"+user); //使用AuthenticationPrincipal注解,User是用户详情服务中的用户对象if(user!=null){System.out.println("了解登录用户是谁:"+user.getUsername()+"||"+user.getPassword()+"||"+user.getEnabled());}List<AtblUserLv> list= userService.findAllUser();Iterator<AtblUserLv> iter = list.iterator();while (iter.hasNext()){AtblUserLv atblUserLv=iter.next();System.out.println(atblUserLv.getId()+"||"+atblUserLv.getUsername()+"||"+atblUserLv.getPassword());}return "success";}@GetMapping("/hiRole")@ResponseBody //这个注解此方法表示返回字符串内容hello securitypublic String helloRole(){System.out.println("-----helloRole----");return "hello Role!";}@GetMapping("/hiAnyRole")@ResponseBody //这个注解此方法表示返回字符串内容hello securitypublic String helloAnyRole(){System.out.println("-----helloAnyRole----");return "hello AnyRole!";}
}

8. 相关页面

登录页logins.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><form action="/user/login" method="post"><!--注意:页面提交方式必须为 post 请求,用户名,密码必须为username,password可以通过 usernameParameter()和 passwordParameter()方法修改默认配置-->用户名:<input type="text" name="username"><br/>密码:<input type="text" name="password"><br/><input type="submit" value="login"></form>
</body>
</html>

主页home.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h3>欢迎您的到来!</h3>
</body>
</html>

成功页success.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h3>success 欢迎你的到来!你成功访问此方法...</h3>
<br> <a href="/logout">退出</a>
</body>
</html>

错误页error.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h3>error 欢迎你的到来!你没有权限访问...</h3>
</body>
</html>

9. 工程目录结构


总结

主要注意配置类SecurityConfig,用户详情实现类MyUserDetailsService和User类。

User类实现了UserDetails接口,MyUserDetailsService类查询数据库用户返回一个UserDetails详情对象User。UserDetails详情对象User装配到SecurityConfig(AuthenticationManagerBuilder)中与登录页面输入的用户名和密码进行匹配验证。SecurityConfig(HttpSecurity)进行自定义登录页面与权限控制的指定。

代码运行效果应该是:"/test/adduser","/test/findAllUser","/test/hello"这三个请求路径需要登录认证成功,且获得指定权限才能访问,其他两个方法未登录认证也可以访问。

spirng框架之spring security(一)相关推荐

  1. 【安全框架】Spring Security安全框架

    Spring Security 是针对Spring项目的安全框架,仅需要引入 spring-boot-starter-sercurity 模块. WebSecurityConfigurerAdapte ...

  2. 【Spring Security】Spring Security框架详解

    文章目录 前言 一.框架概述 Spring Security的架构 Spring Security的主要特点 二.认证 HTTP Basic认证 表单登录 OpenID Connect 三.授权 基于 ...

  3. 安全框架Spring Security(认证和授权)

    Spring Security 今天从以下几个内容介绍: Spring Security简介 Spring Security的Maven依赖 Spring Security使用 Spring Secu ...

  4. Spring security防止跨站请求伪造(CSRF防护)

    因为使用了spring security 安全性框架 所以spring security 会自动拦截站点所有状态变化的请求(非GET,HEAD,OPTIONS和TRACE的请求),防止跨站请求伪造(C ...

  5. 这个安全平台结合Spring Security逆天了,我准备研究一下

    最近想要打通几个应用程序的用户关系,搞一个集中式的用户管理系统来统一管理应用的用户体系.经过一番调研选中了红帽开源的Keycloak,这是一款非常强大的统一认证授权管理平台.之所以选中了Keycloa ...

  6. Spring Security与Maven教程

    1.简介 在这篇文章中,我们将演示如何针对非常特定的用例将Maven依赖项用于Spring Security. 我们使用的所有库的最新版本都可以在Maven Central上找到. 在项目中,了解Ma ...

  7. Spring Security学习总结

    2019独角兽企业重金招聘Python工程师标准>>> 1.Spring Security介绍  一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权 ...

  8. 学成在线-第16天-讲义- Spring Security Oauth2 JWT RSA加解密

    学成在线-第16天-讲义- Spring Security Oauth2 JWT 1 用户认证需求分析 1.1 用户认证与授权 ​ 截至目前,项目已经完成了在线学习功能,用户通过在线学习页面点播视频进 ...

  9. 基于Spring Security与JWT实现单点登录

    基于RBAC的权限管理 RBAC(Role-Based Access Control):基于角色的访问控制 当前项目中,RBAC具体的表现为: 管理员表:ams_admin 角色表:ams_role ...

最新文章

  1. MegaRAID阵列卡配置RAID阵列 - WebBIOS - CLI
  2. git编辑器选哪个_对比了3款markdown编辑器,哪一款适合你呢?来看看吧
  3. Python科学计算扩展库NumPy之np.array()与np.asarray()区别
  4. 8 分钟了解 Kubernetes
  5. FPGA异步时钟设计中的同步策略
  6. Linux下mysql设置密码
  7. 计算机辅助设计A卷,《计算机辅助设计》考试试卷A.doc
  8. B Graph(异或最小生成树)
  9. 递归算法思路以及题目总结(未完待续...)
  10. Oracle 2021年度安全警报: Critical Patch Update 发布8个数据库警告
  11. C语言main函数带参数在VC6下的调试方法
  12. 【咸鱼教程】一个简单的画布(阴阳师画符)
  13. c#对接科大讯飞平台--语音转写
  14. Windows Server 2016 路由和远程访问
  15. 路由器设置虚拟服务器utorrent,路由器用户PT站“可连接:否”最简解决办法
  16. 交易偏见--《别做正常的傻瓜》摘记2
  17. 生命如此脆弱——2012观后感
  18. 如何打开小米,oppo,华为等手机的系统应用的指定页面
  19. IP广播无法登陆服务器系统,数字IP广播系统操作指南
  20. UPS不间断电源测试技巧有哪些?

热门文章

  1. python 网络教育 百度传课_传课网 - 课程
  2. Android03_事件处理一
  3. 2021年11月21日
  4. 北大和人大两年整理出来的书单(经济&管理&商业)
  5. java对接PayPal支付-自动续费功能
  6. 在手机上就能修改PDF中出现的错误,手机怎么修改PDF文本?
  7. APG优化非负矩阵分解(NeNMF)
  8. 机器学习系列文章——算法的实现(knn,朴素贝叶斯,决策树,模型评估)
  9. POJ 3253 - Fence Repai ( 优先队列 )
  10. 深度学习损失函数不下降的解决方法