Spring Security你懂了吗

前置知识

  • 掌握Spring框架
  • 掌握SpringBoot框架
  • 掌握JavaWeb框架

下面的内容主要是用来介绍用户认证和用户的授权

两个安全认证框架对比

SpringSecurity

  • 能够和Spring进行无缝整合
  • 全面的权限控制
  • 专门为Web开发而设计的
    • 旧版本不能脱离 Web 环境使用
    • 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独
      引入核心模块就可以脱离 Web 环境

Shiro

是一款轻量级的权限控制框架

  • 轻量级。 Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求
    的互联网应用有更好表现
  • 通用性
    • 好处 不局限于 Web 环境,可以脱离 Web 环境使用。
    • 坏处 在 Web 环境下一些特定的需求需要手动编写代码定制

用户认证

简单的说指定就是系统认为用户是否能登录

用户授权

指定就是系统判断用户是否具有权限去做某些事情

SpringSecurity小测试

配置环境

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

编写Controller

@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/hello")public String add(){return "Hello Spring Security";}
}

此时启动服务进行登录之后 会出现安全认证界面

注意 这里的用户名默认是user 密码会在控制台打印出来

SpringSecurity基本原理

SpringSecurity本质是过滤器链 本文主要讲解三个过滤器

  • FilterSecurityInterceptor是一个方法级的权限过滤器, 基本位于过滤链的最底部


具体invoke方法的实现
super.beforeInvocation(fi) 表示查看之前的 filter 是否通过。
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务

  • ExceptionTranslationFilter: 是一个异常过滤器,用来处理在认证授权的过程总抛出的异常
  • UsernamePasswordAuthenticationFilte 实现对/login的POST请求的拦截,检验表单中用户名与密码

用户自定义开发的时候不能用户名与密码都是涉及数据库的 所以在自定义开发中设计到的两个重要地接口

  • UserDetailsService 用来查询数据库的用户名和密码的过程

创建类继承UsernamePasswordAuthenticationFilte ,重写attemptAuthentication successfulAuthentication unsuccessfulAuthentication三个方法

创建类实现UserDetailsService 编写查询数据库的过程,返回User对象 这个User对象是安全框架提供的对象

  • PasswordEncoder

对密码进行解密

Web权限方案

  • 认证

    • 第一种 通过配置类来实现

      spring.security.user.name=123
      spring.security.user.password=123
      
    • 第二种 通过配置类来实现

      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String password = passwordEncoder.encode("123");//可以通过auth设置登录名与密码auth.inMemoryAuthentication().withUser("123").password(password).roles("admin");}@BeanPasswordEncoder password(){return new BCryptPasswordEncoder();}
      }
      
    • 第三种 自定义编写实现类

      • 创建配置类 设置使用哪个userDetailService实现类

      • 编写实现类 返回User对象 User对象有用户名 买吗和操作权限

        /*** SpringSecurity的配置类*/
        @Configuration
        public class SecurityConfigTest extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//在设置的userDetail里面返回用户名 密码 权限auth.userDetailsService(userDetailsService).passwordEncoder(password());}@BeanPasswordEncoder password(){return new BCryptPasswordEncoder();}
        }
        @Service("userDetailsService")
        public class UserDetailService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {//如果涉及到数据库  根据name 查询数据库对应的数据List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");return new User("ljx",new BCryptPasswordEncoder().encode("123"),auths);}
        }
        

连接数据库 完成用户的认证

添加pom.xml文件

     <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.2.0</version></dependency><dependency><groupId>mysql</ groupId><artifactId>mysql-connector-java</ artifactId></ dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>

设置实体类 (包含username和password)

创建UserMapper继承BaseMapper

在UserDetailService里面根据传入的用户名获取对应的数据库对象 进行判断

@Service("userDetailsService")
public class UserDetailService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 这里面接收的username 就是security界面传递的用户名QueryWrapper<Users> qw = new QueryWrapper<>();qw.eq("username",username);Users users = userMapper.selectOne(qw);if (users==null){//认证失败throw new UsernameNotFoundException("对不起 用户名不存在");}//如果涉及到数据库  根据name 查询数据库对应的数据List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);}
}

自定义登录页面以及设置哪些访问不需要设置就能实现访问

在配置类里面设置过滤规则

/*** SpringSecurity的配置类*/
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//在设置的userDetail里面返回用户名 密码 权限auth.userDetailsService(userDetailsService).passwordEncoder(password());}@BeanPasswordEncoder password(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().loginPage("/login.html") //登录页面设置.loginProcessingUrl("/user/login")//自定义的登录访问路径要与表单提交到地址一致.defaultSuccessUrl("/test/index").permitAll()//登录成功之后跳转的路径.and().authorizeRequests().antMatchers("/","/test/hello","/user/login").permitAll()//设置那些路径不需要进行访问.anyRequest().authenticated().and().csrf().disable();//关闭csrf防护}
}

设置默认的登录界面 路径resourcess/statis/login.html

启动测试:

localhost:8111/test/hello==>得到输出结果

localhost:8111/test/index==>就会跳转到登录界面 然后进行登录 就能够得到对应的返回值

基于角色或权限进行访问控制

hasAuthority

如果当前的用户具有指定的权限那么返回true 否则返回false

  • 在配置类里面设置哪些路径需要指定权限

  • 在UserDetailsService里面 把返回User对象设置权限

  • 在配置类中
    .antMatchers("/test/index").hasAuthority("admins")//设置当前的登录用户  只有具有了admin权限才能够访问这个路径
    
  • 在UserDetailsService中
    List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
    

hasAnyAuthority

如果当前的主体有任何任何的角色的话返回true

hasAnyAuthority("admins,admin1")

hasRole

如果用户具备给定的角色就允许访问否则出现403

.antMatchers("/test/index").hasRole("sale")底层会在sale前面加ROLE_sale所以 List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");

hasAnyRole

如果允许具备的多个角色就允许访问 否则403

.antMatchers("/test/index").hasAnyRole("sale,sale1")List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale,ROLE_sale1");
注意:在设置角色的底层会默认为其加上ROLE_ 所以在为用户赋予角色的时候也要加上ROLE_

自定义403没有权限的界面

在配置类里面
http.exceptionHandling().accessDeniedPage("/norole.html");

认证授权中注解的使用

使用注解要先开启注解的功能

@ Secured

先开启注解功能@EnableGlobalMethodSecurity(securedEnabled=true)

判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“

在控制器上也可以在方法上 针对于某一个方法

@RestController
@RequestMapping("/test")
//@Secured({"ROLE_admin123"})只有在UserDetails里面为用户设置了权限才能够进行访问
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin123");

@PreAuthrorize

开启注解@EnableGlobalMethodSecurity(prePostEnabled = true)

作用在方法上

注解适合进入方法前的权限验证,可以将登录用户的 roles/permissions 参数传到方法中

在方法上设置
@PreAuthorize("hasAnyAuthority('admin')")
在UserDetails设置
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");

@PostAuthorize

开启注解@EnableGlobalMethodSecurity(prePostEnabled = true)

在方法执行之后进行执行 基本不用 方法之后检验还有啥作用

@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
在方法上设置
@PostAuthorize("hasAnyAuthority('admin')")
在UserDetails设置
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");

@PostFilter

权限认证之后,对返回的数据进行过滤

@GetMapping("getAll")
@PreAuthorize("hasRole('ROLE_ 管理员')")
@PostFilter("filterObject.username == 'admin1'")
public List<UserInfo> getAllUser(){ArrayList<UserInfo> list = new ArrayList<>();
list.add(new UserInfo(1l,"admin1","6666"));
list.add(new UserInfo(2l,"admin2","888"));
return list;
}
只返回username为admin1的数据

@PreFilter

对进入控制器之前的数据进行过滤

@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_ 管理员')")
@PreFilter(value = "filterObject.id%2==0")
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo>list){list.forEach(t-> {System.out.println(t.getId()+"\t"+t.getUsername());
});
return list;
}
只有list集合里面Id的值是偶数的才会进入

登录之后用户的注销或者退出操作

在配置类里面添加配置退出的映射

http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
此时修改一下 登录成功的跳转界面
.defaultSuccessUrl("/success.html").permitAll()//登录成功之后跳转的路径
在success.html里面
<a href="/logout">退出</a>

CSRF

跨站请求伪造,默认情况下回启动CSRF保护,以防止CSRF攻击应用,SpringSecurity会针对PATCH POST PUT DELETE方法进行防护

SpringSecurity微服务权限方案

  • 基于Session 那么Spring-Security会对cookie里面的sessionId进行解析,找到服务器存储的session信息,然后判断当前的用户是否符合请求的要求
  • 如果是token 那么就要解析出token然后将当前请求加入到Spring-Security管理的权限信息中

用户登录成功之后 查询对应的用户权限列表—>将用户相关信息保存在Redis里面(Key:用户名 value:用户的权限列表)—>根据用户名生成token(使用JWT)—>将token放到cookie里面 在header放token---->Spring Security 从header中获取token 那token获取用户名 那这个用户名查询对应的权限列表(Redis里面)

微服务权限管理案例的主要功能

  1. 登录(人证 )
  2. 添加角色
  3. 为角色分配菜单
  4. 添加用户
  5. 为用户分配角色

权限管理数据模型

菜单表 角色菜单表 角色表 用户角色表 用户表

案例涉及到的技术

Maven SpringBoot MybatisPlus SpringCloud(GetWay Nacos) Redis Swagger

创建一个父工程: acl_parent 管理依赖的版本

在父工程下面创建子模块

  • common

    • service_base: 工具类
    • spring_security: 权限配置
  • infrastructure
    • api_getway: 网关
  • service
    • service_acl 权限管理模块


启动Redis和Nacos服务

Redis相当于一个数据库 用来存储数据

Nacos 就是一个注册中心


项目操作步骤

  • 编写service_base里面的工具类的内容
  • 编写spring_security认证授权的工具类

DefaultPasswordEncoder 密码处理

//密码处理工具类
public class DefaultPasswordEncoder implements PasswordEncoder {public DefaultPasswordEncoder(){this(-1);}public DefaultPasswordEncoder (int strength){}//实现加密  进行MD5加密@Overridepublic String encode(CharSequence charSequence) {MD5.encrypt(charSequence.toString());return null;}//进行比对/**** @param charSequence  传入的密码* @param encodingPassword 加密之后的密码* @return*/@Overridepublic boolean matches(CharSequence charSequence, String encodingPassword) {return encodingPassword.equals(MD5.encrypt(charSequence.toString()));}
}

TokenLogoutHandle 退出处理器

//退出处理器
public class TokenLogoutHandle implements LogoutHandler {//删除Token 根据token获取用户名在Redis里面进行删除private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenLogoutHandle(TokenManager tokenManager, RedisTemplate redisTemplate) {this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overridepublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {//在Header里面获取token// token不为空  移除token 从Redis删除tokenString token = request.getHeader("token");if (token!=null){//删除TokentokenManager.removeToken(token);//从Token获取用户名String username = tokenManager.getUserInfoFromToken(token);redisTemplate.delete(username);}ResponseUtil.out(response, R.ok());}
}

TokenManager token操作工具类

// token操作工具类
public class TokenManager {//token有效时长private long tokenEcpiration=24*60*60*100;//编码秘钥private String tokenSignKey="123456";//根据用户名生成Tokenpublic String createToken(String username){String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration)).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}//根据Token得到用户信息public  String getUserInfoFromToken(String token){String userInfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();return userInfo;}//删除Tokenpublic void removeToken(String token){}
}

UnauthorizedEntryPoint 未授权统一处理

//未授权统一处理类
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {ResponseUtil.out(response, R.error());}
}

TokenLoginFilter认证过滤器

//认证过滤  指定就是进行认证登录
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private TokenManager tokenManager;private RedisTemplate redisTemplate;private AuthenticationManager authenticationManager;public TokenLoginFilter(TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationManager authenticationManager) {this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;this.authenticationManager = authenticationManager;this.setPostOnly(false);//设置登录的路径和提交方式this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));}//获取表单提交的用户名和密码@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {try {User user = new ObjectMapper().readValue(request.getInputStream(), User.class);return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>()));} catch (IOException e) {e.printStackTrace();throw new RuntimeException();}}//认证成功调用的方法@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {// 认证成功  得到认证成功之后用户信息SecurityUser user = (SecurityUser)authResult.getPrincipal();//根据用户名生成tokenString token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());//将用户名和用户权限放到权限列表里面redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());//返回tokenResponseUtil.out(response, R.ok().data("token",token));}//认证失败调用的方法@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {ResponseUtil.out(response,R.error());}
}

TokenAuthenticationFilter授权过滤器

//授权过滤
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint, TokenManager tokenManager, RedisTemplate redisTemplate) {super(authenticationManager, authenticationEntryPoint);this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {//获取当前认证乘公共用户的授权信息//获取当前认证成功用户权限信息UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);//判断如果有权限信息,放到权限上下文中if(authRequest != null) {SecurityContextHolder.getContext().setAuthentication(authRequest);}chain.doFilter(request,response);}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){//从Header中获取TokenString token = request.getHeader("token");if (token!=null){//从Token中获取用户名String username = tokenManager.getUserInfoFromToken(token);//从redis获取对应的权限列表List<String> permissionValueList = (List<String>)redisTemplate.opsForValue().get(username);Collection<GrantedAuthority> authority = new ArrayList<>();for(String permissionValue : permissionValueList) {SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);authority.add(auth);}return new UsernamePasswordAuthenticationToken(username,token,authority);}return null;}
}

TokenWebSecurityConfig核心配置类

//核心配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {private TokenManager tokenManager;private RedisTemplate redisTemplate;private DefaultPasswordEncoder defaultPasswordEncoder;private UserDetailsService userDetailsService;@Autowiredpublic TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,TokenManager tokenManager, RedisTemplate redisTemplate) {this.userDetailsService = userDetailsService;this.defaultPasswordEncoder = defaultPasswordEncoder;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}/*** 配置设置* @param http* @throws Exception*///设置退出的地址和token,redis操作地址@Overrideprotected void configure(HttpSecurity http) throws Exception {http.exceptionHandling().authenticationEntryPoint(new UnauthorizedEntryPoint())//没有权限访问.and().csrf().disable().authorizeRequests().anyRequest().authenticated().and().logout().logoutUrl("/admin/acl/index/logout")//退出路径.addLogoutHandler(new TokenLogoutHandle(tokenManager,redisTemplate)).and().addFilter(new TokenLoginFilter( tokenManager, redisTemplate,authenticationManager())).addFilter(new TokenAuthenticationFilter(authenticationManager(),tokenManager, redisTemplate)).httpBasic();}//调用userDetailsService和密码处理@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);}//不进行认证的路径,可以直接访问@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/api/**");}
}

编写UserDetailServiceImpl

@Service("userDetailsService")  这个名字要与配合类里面定义的名字一致
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserService userService;@Autowiredprivate PermissionService permissionService;  关于权限的@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名查询数据User user = userService.selectByUsername(username);//判断if(user == null) {throw new UsernameNotFoundException("用户不存在");}User curUser = new User();BeanUtils.copyProperties(user,curUser);//根据用户查询用户权限列表List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());SecurityUser securityUser = new SecurityUser();securityUser.setCurrentUserInfo(curUser);securityUser.setPermissionValueList(permissionValueList);return securityUser;}
}

整体流程是

先进行认证: attemptAuthentication–>成功successfulAuthentication/失败unsuccessfulAuthentication–>在进行授权doFilterInternal

自我理解

在将前台的登录界面设置为不拦截的请求 前台界面登录执行登录请求之后 就会与Security的核心配置类里面设置的登录路径包含登录方法相匹配 如果匹配合格就会进行认证和授权的检测 检测是否能够进行登录并对其进行授权

其他具体相关代码在这里不在讲述 想要了解的请点击我的项目地址,如有不对的地方请指出公共学习
项目地址

Spring Security到底是什么相关推荐

  1. Spring Security到底在哪里进行密码方式认证

    一 Spring Security比较好的教程 http://www.spring4all.com/article/428 二 基于数据库的密码认证 http://www.spring4all.com ...

  2. Spring Security实现RememberMe功能以及原理探究

    在大多数网站中,都会实现RememberMe这个功能,方便用户在下一次登录时直接登录,避免再次输入用户名以及密码去登录,下面,主要讲解如何使用Spring Security实现记住我这个功能以及深入源 ...

  3. .netcore 如何获取系统中所有session_集群化部署,Spring Security 要如何处理 session 共享?

    前面和大家聊了 Spring Security 如何像 QQ 一样,自动踢掉已登录用户(Spring Boot + Vue 前后端分离项目,如何踢掉已登录用户?),但是前面我们是基于单体应用的,如果我 ...

  4. oauth2_带有Spring Security的OAuth 2.0快速指南

    oauth2 "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验 ...

  5. Spring Security太复杂?试试这个轻量、强大、优雅的权限认证框架!

    各位程序猿小伙伴们,中秋快乐~在节日欢快的气氛中大家是不是还在奋笔疾书.沉浸在学习的海洋中呢? 小编这两天休息在家一直在想一个问题,那就是我们在开发SpringBoot项目的时候,该怎么做好权限认证呢 ...

  6. Spring Security 实战干货:路径Uri中的 Ant 风格

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | juejin.im/post/5c6b6b12 ...

  7. spring security源码分析之web包分析

    Spring 是一个非常流行和成功的 Java 应用开发框架.Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案.一般来说,Web 应用的安全性包括 ...

  8. [转载]spring security 的 logout 功能

    原文地址:spring security 的 logout 功能作者:sumnny 转载自:http://lengyun3566.iteye.com/blog/1114464 理解退出功能 术语退出( ...

  9. Spring boot+Spring Security 4配置整合实例

    本例所覆盖的内容: 1. 使用Spring Security管理用户身份认证.登录退出 2. 用户密码加密及验证 3. 采用数据库的方式实现Spring Security的remember-me功能 ...

最新文章

  1. 服务注册发现与kit实践
  2. mysql load data on duplicate_带有ON DUPLICATE KEY UPDATE的MySQL LOAD DATA INFILE
  3. 自画菜单中如何触发MeasureItem事件的问题及解决办法
  4. Flutter开发之PageView指示器(31)
  5. 虚拟机:Centos 7 安装JDK8(亲测)
  6. cat查看tomcat日志 linux_方法篇:tomcat日志切割和定期删除
  7. SpringBoot单元测试运行时报错:Failed to load ApplicationContext
  8. 人人网 校内- 日志分享
  9. ICH E2B | ICSR 电子传输网关对接解决方案
  10. Docker 容器学习完整笔记
  11. java2048移动算法_2048游戏通关算法
  12. java多态的练习 ,定义三个类,父类GeometricObject代表几何形状,子类Circle代表圆形,MyRectangle代表矩形。 定义一个测试类GeometricTest,编写equals
  13. 国内多家视频下载网站关闭:或为暂避风头
  14. win10下安装matlab r2018a破解版
  15. 【性能测试】轻商城-项目实战3
  16. 兔子数列规律怎么讲_神奇兔子数列
  17. SpringBoot图片上传失败
  18. 买家用投影仪应该关注哪些数据?
  19. 网络接口层协议:ATM
  20. MT7687芯片资料MT7687原理图资料

热门文章

  1. Android RecyclerView实现图片瀑布流
  2. ☀️机器学习实战☀️基于 YOLO网络 的人脸识别 |(文末送机器学习书籍~)
  3. 人工神经网络反向传播,神经网络后向传播
  4. 基于SSM的高校助学贷款申报审批系统
  5. PS打开PSD文档服务器未响应,优化你的 PSD 文件防止 Photoshop 崩溃卡死 - 文章教程...
  6. cad地图转成shp方法
  7. 【ansys workbench】11.圣维南原理和模型简化
  8. 用java编写输出直角三角型、倒直角三角形
  9. 搞的谁还不会爬福利美女跳舞视频一样,用我这个方法非常简单。
  10. 力扣(SQL)595. 大的国家