springboot整合spring security最完整,只看这一篇就够了

QianTLL 2021-04-08 11:46:07
spring SpringBoot Security 整合 完整


本人结合其他博客和自己查询的资料,一步一步实现整合了security安全框架,其中踩过不少的坑,也有遇到许多不懂的地方,为此做个记录。

开发工具:ide、数据库:mysql5.7、springboot版本:2.3.7

个人对Spring Security的执行过程大致理解(仅供参考)

 

 

使用Spring Security很简单,只要在pom.xml文件中,引入spring security的依赖就可以了

pom配置:

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

这个时候我们不在配置文件中做任何配置,随便写一个Controller 

@RestController
public class TestController {
@GetMapping("/hello")
public String request() {
return "hello";
}
}

启动项目,我们会发现有这么一段日志

此时表示Security生效,默认对项目进行了保护,我们访问该Controller中的接口(http://localhost:8080/securitydemo/hello),会见到如下登录界面(此界面为security框架自带的默认登录界面,后期不用可以换成自定义登录界面)

 这里面的用户名和密码是什么呢?此时我们需要输入用户名:user,密码则为之前日志中的"19262f35-9ded-49c2-a8f6-5431536cc50c",输入之后,我们可以看到此时可以正常访问该接口

 

在老版本的Springboot中(比如说Springboot 1.x版本中),可以通过如下方式来关闭Spring Security的生效,但是现在Springboot 2中已经不再支持

security:
basic:
enabled: false

springboot2.x后可以在启动类中设置

1、配置基于内存的角色授权和认证信息

1.1目录

1.2 WebSecurityConfg配置类

Spring Security的核心配置类是 WebSecurityConfigurerAdapter抽象类,这是权限管理启动的入口,这里我们自定义一个实现类去它。

/**
* @Author qt
* @Date 2021/3/25
* @Description SpringSecurity安全框架配置
*/
@Configuration
@EnableWebSecurity//开启Spring Security的功能
//prePostEnabled属性决定Spring Security在接口前注解是否可用@PreAuthorize,@PostAuthorize等注解,设置为true,会拦截加了这些注解的接口
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfg extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
* 基于内存的方式,创建两个用户admin/123456,user/123456
* */
auth.inMemoryAuthentication()
.withUser("admin")//用户名
.password(passwordEncoder().encode("123456"))//密码
.roles("ADMIN");//角色
 auth.inMemoryAuthentication()
.withUser("user")//用户名
.password(passwordEncoder().encode("123456"))//密码
.roles("USER");//角色
 }
/**
* 指定加密方式
*/
@Bean
public PasswordEncoder passwordEncoder(){
// 使用BCrypt加密密码
return new BCryptPasswordEncoder();
}
}

1.3 MainController控制器接口

/**
* @Author qt
* @Date 2021/3/25
* @Description 主控制器
*/
@RestController
public class MainController {
@GetMapping("/hello")
public String printStr(){
System.out.println("hello success");
return "Hello success!";
}
}

这样重新运行后我们就可以通过admin/123456、user/123456两个用户登录了。

当然,你也可以基于配置文件创建用户账号,在pom.xml中添加:

 2、配置基于数据库的认证信息和角色授权

2.1 目录

  2.2  CustomUserDetailsService实现类

UserDetailsService是需要实现的登录用户查询的service接口,实现loadUserByUsername()方法,这里我们自定义CustomUserDetailsService实现类去实现UserDetailsService接口

/**
* @Author qt
* @Date 2021/3/25
* @Description
*/
@Component
public class CustomUserDetailsService implements UserDetailsService {
@Resource
private UserInfoService userInfoService;
@Resource
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
/**
* 1/通过userName 获取到userInfo信息
* 2/通过User(UserDetails)返回details。
*/
//通过userName获取用户信息
UserInfo userInfo = userInfoService.getUserInfoByUsername(userName);
if(userInfo == null) {
throw new UsernameNotFoundException("not found");
}
//定义权限列表.
List<GrantedAuthority> authorities = new ArrayList<>();
// 用户可以访问的资源名称(或者说用户所拥有的权限) 注意:必须"ROLE_"开头
authorities.add(new SimpleGrantedAuthority("ROLE_"+ userInfo.getRole()));
User userDetails = new User(userInfo.getUserName(),passwordEncoder.encode(userInfo.getPassword()),authorities);
return userDetails;
}
}
WebSecurityConfg配置类:
/**
* @Author qt
* @Date 2021/3/25
* @Description SpringSecurity安全框架配置
*/
@Configuration
@EnableWebSecurity//开启Spring Security的功能
//prePostEnabled属性决定Spring Security在接口前注解是否可用@PreAuthorize,@PostAuthorize等注解,设置为true,会拦截加了这些注解的接口
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfg extends WebSecurityConfigurerAdapter {
/**
* 指定加密方式
*/
@Bean
public PasswordEncoder passwordEncoder(){
// 使用BCrypt加密密码
return new BCryptPasswordEncoder();
}
}

对于通过userName获取用户信息的服务层,持久层和数据库语句就不介绍了,这里使用的是SSM框架,使用mybaits。

 3、自定义表单认证登录

3.1 目录

  3.2  WebSecurityConfg核心配置类

/**
* @Author qt
* @Date 2021/3/25
* @Description spring-security权限管理的核心配置
*/
@Configuration
@EnableWebSecurity//开启Spring Security的功能
//prePostEnabled属性决定Spring Security在接口前注解是否可用@PreAuthorize,@PostAuthorize等注解,设置为true,会拦截加了这些注解的接口
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfg extends WebSecurityConfigurerAdapter {
@Resource
private AuthenticationSuccessHandler loginSuccessHandler; //认证成功结果处理器
 @Resource
private AuthenticationFailureHandler loginFailureHandler; //认证失败结果处理器
//http请求拦截配置
 @Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();//开启运行iframe嵌套页面

http//1、配置权限认证
 .authorizeRequests()
//配置不拦截路由
.antMatchers("/500").permitAll()
.antMatchers("/403").permitAll()
.antMatchers("/404").permitAll()
.antMatchers("/login").permitAll()
.anyRequest() //任何其它请求
.authenticated() //都需要身份认证
 .and()
//2、登录配置表单认证方式
 .formLogin()
.loginPage("/login")//自定义登录页面的url
.usernameParameter("username")//设置登录账号参数,与表单参数一致
.passwordParameter("password")//设置登录密码参数,与表单参数一致
// 告诉Spring Security在发送指定路径时处理提交的凭证,默认情况下,将用户重定向回用户来自的页面。登录表单form中action的地址,也就是处理认证请求的路径,
// 只要保持表单中action和HttpSecurity里配置的loginProcessingUrl一致就可以了,也不用自己去处理,它不会将请求传递给Spring MVC和您的控制器,所以我们就不需要自己再去写一个/user/login的控制器接口了
.loginProcessingUrl("/user/login")//配置默认登录入口
.defaultSuccessUrl("/index")//登录成功后默认的跳转页面路径
.failureUrl("/login?error=true")
.successHandler(loginSuccessHandler)//使用自定义的成功结果处理器
.failureHandler(loginFailureHandler)//使用自定义失败的结果处理器
 .and()
//3、注销
 .logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
.permitAll()
.and()
//4、session管理
 .sessionManagement()
.invalidSessionUrl("/login") //失效后跳转到登陆页面
//单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线
//.maximumSessions(1).expiredSessionStrategy(expiredSessionStrategy())
//单用户登录,如果有一个登录了,同一个用户在其他地方不能登录
//.maximumSessions(1).maxSessionsPreventsLogin(true) ;
 .and()
//5、禁用跨站csrf攻击防御
 .csrf()
.disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
//配置静态文件不需要认证
web.ignoring().antMatchers("/static/**");
}
/**
* 指定加密方式
*/
@Bean
public PasswordEncoder passwordEncoder(){
// 使用BCrypt加密密码
return new BCryptPasswordEncoder();
}
}

踩坑点1:登录页面接口/login和登录验证接口/user/login,这里是自己之前一直搞错的重点,这里就用网上的图片展示了

踩坑点2:springboot配置spring security 静态资源不能访问

security的配置:在类WebSecurityConfig继承WebSecurityConfigurerAdapter,这个类是我们在配置security的时候,对我们请求的url及权限规则的一些认证配置。具体的不说了,这里主要是静态资源的问题。

在这个类中我们会重写一些方法,其中就有一个方法,可以为我们配置一下静态资源不需要认证。

@Override
public void configure(WebSecurity web) throws Exception {
//配置静态文件不需要认证
web.ignoring().antMatchers("/static/**");
}

页面的引用如下:

 <link rel="stylesheet" th:href="@{static/layui/css/layui.css}">

之后我们启动项目:看到css并没有生效

 这时候仅仅通过spring security配置是不够的,我们还需要去重写addResourceHandlers方法去映射下静态资源,这个方法应该很熟悉了,我们通过springboot添加拦截器的时候就会用到这个。

写一个类WebMvcConfig继承WebMvcConfigurationSupport,注意spring boot2版本和1版本是不一样的,spring boot1版本继承的WebMvcConfigurerAdapter在spring boot2版本中已经提示过时了

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 配置静态资源
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
super.addResourceHandlers(registry);
}
}

现在重新启动项目:css文件已经引用成功。

3.3  ErrorPageConfig 配置错误页面

/**
* @Author qt
* @Date 2021/3/25
* @Description 配置错误页面 403 404 500 适用于 SpringBoot 2.x
*/
@Configuration
public class ErrorPageConfig {
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
WebServerFactoryCustomizer<ConfigurableWebServerFactory> webCustomizer = new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
@Override
public void customize(ConfigurableWebServerFactory factory) {
ErrorPage[] errorPages = new ErrorPage[] {
new ErrorPage(HttpStatus.FORBIDDEN, "/403"),
new ErrorPage(HttpStatus.NOT_FOUND, "/404"),
new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500"),
};
factory.addErrorPages(errorPages);
}
};
return webCustomizer;
}
}

 3.4 MainController 控制器

/**
* @Author qt
* @Date 2021/3/25
* @Description 主控制器
*/
@Controller
public class MainController {
private Logger logger = LoggerFactory.getLogger(getClass());
@GetMapping("/login")
public String loginPage(){
System.out.println("login page");
return "login";
}
@GetMapping("/index")
@PreAuthorize("hasAnyRole('USER','ADMIN')")
public String index(){
System.out.println("index page");
return "index";
}
@GetMapping("/admin")
@PreAuthorize("hasAnyRole('ADMIN')")
public String printAdmin(){
System.out.println("hello admin");
return "admin";
}
@GetMapping("/user")
@PreAuthorize("hasAnyRole('USER','ADMIN')")
public String printUser(){
System.out.println("hello user");
return "user";
}
/**
* 找不到页面
*/
@GetMapping("/404")
public String notFoundPage() {
return "/error/404";
}
/**
* 未授权
*/
@GetMapping("/403")
public String accessError() {
return "/error/403";
}
/**
* 服务器错误
*/
@GetMapping("/500")
public String internalError() {
return "/error/500";
}
}

3.5 UserInfoController 用户控制器

/**
* @Author qt
* @Date 2021/3/25
* @Description
*/
@Controller
@RequestMapping("/user")
public class UserInfoController {
private Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private UserInfoService userInfoService;
@GetMapping("/getUserInfo")
@ResponseBody
public User getUserInfo(@RequestParam String username){
return userInfoService.getUserInfoByUsername(username);
}
}

SMM框架的其他部分就省略了,非这里重点。

3.6 CustomAccessDecisionManager 自定义权限决策管理器

/**
* @Author qt
* @Date 2021/3/31
* @Description 自定义权限决策管理器
*/
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager {
/**
* @Author: qt
* @Description: 取当前用户的权限与这次请求的这个url需要的权限作对比,决定是否放行
* auth 包含了当前的用户信息,包括拥有的权限,即之前UserDetailsService登录时候存储的用户对象
* object 就是FilterInvocation对象,可以得到request等web资源。
* configAttributes 是本次访问需要的权限。即上一步的 MyFilterInvocationSecurityMetadataSource 中查询核对得到的权限列表
**/
@Override
public void decide(Authentication auth, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = collection.iterator();
while (iterator.hasNext()) {
if (auth == null) {
throw new AccessDeniedException("当前访问没有权限");
}
ConfigAttribute ca = iterator.next();
//当前请求需要的权限
String needRole = ca.getAttribute();
if ("ROLE_LOGIN".equals(needRole)) {
if (auth instanceof AnonymousAuthenticationToken) {
throw new BadCredentialsException("未登录");
} else
return;
}
//当前用户所具有的权限
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}

3.7 CustomLogoutSuccessHandler 注销登录处理

/**
* @Author qt
* @Date 2021/3/31
* @Description 注销登录处理
*/
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("注销成功!");
//这里写你登录成功后的逻辑
 response.setStatus(HttpStatus.OK.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("注销成功!");
}
}

3.8 LoginFailureHandler 登录失败处理

/**
* @Author qt
* @Date 2021/3/24
* @Description 登录失败处理
*/
@Component("loginFailureHandler")
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
logger.info("登录失败");
this.saveException(request, exception);
this.getRedirectStrategy().sendRedirect(request, response, "/login?error=true");
}
}

 3.9 LoginSuccessHandler 登录成功处理

/** @Author qt 
* @Date 2021/3/24 * @Description 登录成功处理
*/ @Component("loginSuccessHandler") public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @Resource private ObjectMapper objectMapper; private RequestCache requestCache; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { // 获取前端传到后端的全部参数 Enumeration enu = request.getParameterNames(); while (enu.hasMoreElements()) { String paraName = (String) enu.nextElement(); System.out.println("参数- " + paraName + " : " + request.getParameter(paraName)); } logger.info("登录认证成功"); //这里写你登录成功后的逻辑,可以验证其他信息,如验证码等。
response.setContentType("application/json;charset=UTF-8"); JSONObject resultObj = new JSONObject(); resultObj.put("code", HttpStatus.OK.value()); resultObj.put("msg","登录成功"); resultObj.put("authentication",objectMapper.writeValueAsString(authentication)); response.getWriter().write(resultObj.toString()); this.getRedirectStrategy().sendRedirect(request, response, "/index");//重定向 } }

3.10 login.html 登录页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" type="text/css" th:href="@{static/layui/css/layui.css}">
</head>
<body>
<form method="POST" th:action="@{/user/login}">
<div>
用户名:<input type="text" name="username" id="username">
</div>
<div>
密码:<input type="password" name="password" id="password">
</div>
<div>
<button type="submit">立即登陆</button>
</div>
<!-- 以下为显示认证失败等提示信息(th:if=""一定要写 )-->
<span style="color: red;" th:if="${param.error}" th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></span>
</form>
</body>
</html>

 3.11 效果图片

登录失败

 

 登录成功

 4、自定义ajax请求认证登录

本人比较喜欢使用ajax的登录认证方式,这个比较灵活。

  4.1 目录

  4.2、较表单登录认证的改变

LoginFailureHandler 登录失败处理

/**
* @Author qt
* @Date 2021/3/24
* @Description 登录失败处理
*/
@Component("loginFailureHandler")
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
logger.info("登录失败");
response.setContentType("application/json;charset=UTF-8");
//这里写你登录失败后的逻辑,可加验证码验证等
String errorInfo = "";
if (exception instanceof BadCredentialsException ||
exception instanceof UsernameNotFoundException) {
errorInfo = "账户名或者密码输入错误!";
} else if (exception instanceof LockedException) {
errorInfo = "账户被锁定,请联系管理员!";
} else if (exception instanceof CredentialsExpiredException) {
errorInfo = "密码过期,请联系管理员!";
} else if (exception instanceof AccountExpiredException) {
errorInfo = "账户过期,请联系管理员!";
} else if (exception instanceof DisabledException) {
errorInfo = "账户被禁用,请联系管理员!";
} else {
errorInfo = "登录失败!";
}
logger.info("登录失败原因:" + errorInfo);
//ajax请求认证方式
JSONObject resultObj = new JSONObject();
resultObj.put("code", HttpStatus.UNAUTHORIZED.value());
resultObj.put("msg",errorInfo);
resultObj.put("exception",objectMapper.writeValueAsString(exception));
response.getWriter().write(resultObj.toString());
//表单认证方式
//this.saveException(request, exception);
//this.getRedirectStrategy().sendRedirect(request, response, "/login?error=true");
 }
}
LoginSuccessHandler 登录成功处理
/**
* @Author qt
* @Date 2021/3/24
* @Description 登录成功处理
*/
@Component("loginSuccessHandler")
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
response.setContentType("application/json;charset=UTF-8");
// 获取前端传到后端的全部参数
Enumeration enu = request.getParameterNames();
while (enu.hasMoreElements()) {
String paraName = (String) enu.nextElement(); System.out.println("参数- " + paraName + " : " + request.getParameter(paraName));
}
logger.info("登录认证成功");
//这里写你登录成功后的逻辑,可加验证码验证等
//ajax请求认证方式
JSONObject resultObj = new JSONObject();
resultObj.put("code", HttpStatus.OK.value());
resultObj.put("msg","登录成功");
resultObj.put("authentication",objectMapper.writeValueAsString(authentication));
response.getWriter().write(resultObj.toString());
//表单认证方式
//this.getRedirectStrategy().sendRedirect(request, response, "/index");//重定向
 }
}

login.html 登录页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" type="text/css" th:href="@{static/layui/css/layui.css}">
</head>
<body>
<form method="POST" action="">
<div>
用户名:<input type="text" name="username" id="username">
</div>
<div>
密码:<input type="password" name="password" id="password">
</div>
<div>
<input type="button" name="login" id="login" th:value="立即登陆" onclick="mylogin()">
</div>
</form>
<script type="text/javascript" charset="utf-8" th:src="@{static/jquery/jquery-3.5.1.min.js}"></script>
<script type="text/javascript" charset="utf-8" th:src="@{static/layui/layui.js}"></script>
<script th:inline="javascript" type="text/javascript">
layui.use(['form','jquery','layedit', 'laydate'], function () {
var $ = layui.jquery,
form = layui.form,
layer = layui.layer;
});
function mylogin() {
var username = $("#username").val();
var password = $("#password").val();
console.log("username:" + username + "password:" + password);
var index = layer.load(1);
$.ajax({
type: "POST",
dataType: "json",
url: "user/login",
data: {
"username": username,
"password": password
//可加验证码参数等,后台登陆处理LoginSuccessHandler中会传入这些参数
 },
success: function (data) {
layer.close(index);
console.log("data===>:" + JSON.stringify(data));
if (data.code == 200) { //登录成功
 window.location.href = "index";
} else {
layer.msg(data.msg, {
icon: 2,
time: 3000 //2秒关闭(如果不配置,默认是3秒)
 });
}
},
error: function () {
layer.close(index);
layer.msg("数据请求异常!", {
icon: 2,
time: 2000 //2秒关闭(如果不配置,默认是3秒)
 });
}
});
}
</script>
</body>
</html>

4.3 演示图片

登录失败

 登录成功

 最后添加一个我写的一个小demo,里面也整合了security框架,使用springboot + ssm后端框架 + maven依赖包管理 + thmeleaf模板引擎 + pear-admin-layui前端框架等。

 demo演示地址:http://www.qnto.top/springfashionsys/login

 demo只对数据分析页面做了权限设置,只有admin才可访问。

转载需要加链接哦,整理不易。

总结:实践是检验真理的唯一标准,亲测可用。

 参考链接:

 https://blog.csdn.net/qq_40298902/article/details/106433192

 https://www.e-learn.cn/topic/3143567

 https://blog.csdn.net/qq_20108595/article/details/89647419

 http://www.spring4all.com/article/428

 https://blog.csdn.net/tanleijin/article/details/100698486

 https://blog.csdn.net/zhaoxichen_10/article/details/88713799

 https://blog.csdn.net/hanxiaotongtong/article/details/103095906

 https://www.jb51.net/article/140429.htm

 https://www.jianshu.com/p/29d10ad22531

 https://blog.csdn.net/weixin_39588542/article/details/110507502

 https://blog.csdn.net/sinat_33151213/article/details/89931819

版权声明
本文为[QianTLL]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/qiantao/p/14605154.html

  1. A love diary about http
  2. navicat连接win10 mysql8.0 报错2059
  3. [rocketmq source code analysis] in depth message storage (3)
  4. Implementation of service configuration center with spring cloud + Nacos (Hoxton version)
  5. SCIP: constructing data abstraction -- Explanation of queue and tree in data structure
  6. SCIP: abstraction of construction process -- object oriented explanation
  7. Using docker to build elasticsearch + kibana cluster
  8. What are the spring IOC features? I can't understand the source code!
  9. Spring cloud upgrade road - 2020.0. X - 3. Accesslog configuration of undertow
  10. 导致Oracle性能抖动的参数提醒
  11. 风险提醒之Oracle RAC高可用失效
  12. 小机上运行Oracle需要注意的进程调度bug
  13. Oracle内存过度消耗风险提醒
  14. Oracle SQL monitor
  15. 使用Bifrost实现Mysql的数据同步
  16. 揭秘Oracle数据库truncate原理
  17. 看了此文,Oracle SQL优化文章不必再看!
  18. Mybatis (3) map and fuzzy query expansion
  19. Kafka性能篇:为何这么“快”?
  20. 两个高频设计类面试题:如何设计HashMap和线程池
  21. [TTS] AIX - & gt; Linux -- Based on RMAN (real environment)
  22. 为什么学编程大部分人选Java编程语言?
  23. Redis 高可用篇:你管这叫 Sentinel 哨兵集群原理
  24. redis 为什么把简单的字符串设计成 SDS?
  25. [TTS] transfer table space AIX - & gt; Linux based on RMAN
  26. Linux 网卡数据收发过程分析
  27. Redis 高可用篇:你管这叫 Sentinel 哨兵集群原
  28. Redis 6.X Cluster 集群搭建
  29. [TTS] transfer table space AIX ASM - & gt; Linux ASM
  30. [TTS] transfer table space Linux ASM - & gt; AIX ASM
  31. 高性能通讯框架——Netty
  32. Brief introduction and test of orchestrator, a high availability management tool for MySQL
  33. [TTS] transfer table space Linux - & gt; AIX based on RMAN
  34. A love diary about http
  35. [rocketmq source code analysis] in depth message storage (3)
  36. Implementation of service configuration center with spring cloud + Nacos (Hoxton version)
  37. SiCp: abstraction of construction process -- object oriented explanation
  38. springboot网上点餐系统
  39. 【SPM】oracle如何固定执行计划
  40. 用好HugePage,告别Linux性能故障
  41. 3 W word long text, java basic interview questions! It's amazing!!!
  42. Spring cloud upgrade road - 2020.0. X - 3. Accesslog configuration of undertow
  43. Win10 uninstall mysql5.7
  44. CentOS下dotnet Core使用HttpWebRequest进行HTTP通讯,系统存在大量CLOSE_WAIT连接问题的分析,已解决。
  45. MySQL batch insert, how not to insert duplicate data?
  46. K8s cronjob application example
  47. Unconventional method, easy to deal with Oracle database critical exception
  48. How to use sqlplus - prelim in Oracle hang
  49. How to search Oracle official documents in full text
  50. Install mysql8.0 on win10
  51. Oracle OCR的备份与恢复
  52. Oracle kill session相关问题
  53. 《Oracle DBA工作笔记》第二章 常用工具和问题分析
  54. Oracle回收站及flashback drop
  55. Hand in hand to teach you to write a spring IOC container
  56. Exception in Java (1) - basic concept
  57. 3w 字长文爆肝 Java 基础面试题!太顶了!!!
  58. Error 2059 when Navicat connects to win10 mysql8.0
  59. Parameter reminder causing Oracle Performance jitter
  60. 「技术分享」Java线程状态间的互相转换看这个就行了