# Spring Cloud下基于OAUTH2认证授权的实现示例
在`Spring Cloud`需要使用`OAUTH2`来实现多个微服务的统一认证授权,通过向`OAUTH服务`发送某个类型的`grant type`进行集中认证和授权,从而获得`token`,而这个token是受其他微服务信任的,我们在后续的访问可以通过`token`来进行,从而实现了微服务的统一认证授权。
**本示例提供了四大部分:**
1. `discovery-service`:服务注册和发现的基本模块
2. `auth-server`:OAUTH2认证授权中心
3. `order-service`:普通微服务,用来验证认证和授权
4. `api-gateway`:边界网关(所有微服务都在它之后)
**OAUTH2中的角色:**
1. `Resource Server`:被授权访问的资源
2. `Authotization Server`:OAUTH2认证授权中心
3. `Resource Owner`: 用户
4. `Client`:使用API的客户端(如Android 、IOS、web app)
Grant Type:
1. `Authorization Code`:用在服务端应用之间
2. `Implicit`:用在移动app或者web app(这些app是在用户的设备上的,如在手机上调起微信来进行认证授权)
3. `Resource Owner Password Credentials(password)`:应用直接都是受信任的(都是由一家公司开发的,本例子使用
4. `Client Credentials`:用在应用API访问。
5. 【扩展pwd】MobilePassword 手机号密码
6. 【扩展sms】MobileSms 手机号短信验证码
环境搭建:
1、基础环境
```
mysql oauth\_client\_details 表存储客户端信息
redis 存储短信验证码信息
```
2、auth-server搭建
*****
\*\*2.1 \*\*定义拦截器拦截登录的请求【在这个类种主要完成2部分工作:1、根据参数获取当前的是认证类型,2、根据不同的认证类型调用不同的IntegrationAuthenticator.prepar进行预处理】
~~~
package com.hjf.auth.filter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 功能说明:【】
* 作 者:lihaijun
* 创建日期:2020-11-20
* Spring Security对于获取TOKEN的请求(默认是"/oauth/token"),需要认证client_id和client_secret。
* 认证client_id和client_secret可以有2种方式,一种是通过本节讲的ClientCredentialsTokenEndpointFilter,
* 另一种是通过BasicAuthenticationFilter。
* ClientCredentialsTokenEndpointFilter首先比对请求URL是否是TOKEN请求路径以及请求参数中是否包含
* client_id,如果满足以上条件,再调用ProviderManager认证client_id和client_secret是否与配置的一致。
* 如果通过认证,会把身份认证信息保存打SecurityContext上下文中。
*/
public class CustomClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter {
private AuthorizationServerSecurityConfigurer configurer;
private AuthenticationEntryPoint authenticationEntryPoint;
public CustomClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer, String path) {
this.configurer = configurer;
setFilterProcessesUrl(path);
}
@Override
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
// 把父类的干掉
super.setAuthenticationEntryPoint(null);
this.authenticationEntryPoint = authenticationEntryPoint;
}
@Override
protected AuthenticationManager getAuthenticationManager() {
return configurer.and().getSharedObject(AuthenticationManager.class);
}
@Override
public void afterPropertiesSet() {
setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
authenticationEntryPoint.commence(httpServletRequest, httpServletResponse, e);
}
});
setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// 无操作-仅允许过滤器链继续到令牌端点
}
});
}
}
~~~
*****
**2.2 OAuth2服务配置 【将拦截器放入到拦截链条中】**
~~~
package com.hjf.auth.configurer;
/**
* 功能说明:【授权服务配置】
* 作 者:lihaijun
* 创建日期:2020-11-09
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
@Autowired private TokenService tokenService;
@Autowired private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired private CustomUserDetailsService userDetailsService;
@Autowired private AuthorizationServerEndpointsConfiguration configuration;
@Resource private DataSource dataSource;
@Resource private AuthenticationManager authenticationManager;
/**
* 配置客户端详情
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
/**
* 配置令牌端点得访问约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
String path = "/oauth/token";
try {
// 获取自定义映射路径,比如 ((AuthorizationServerEndpointsConfigurer) endpoints).pathMapping("/oauth/token", "/my/token");
path = configuration.oauth2EndpointHandlerMapping().getServletPath(path);
} catch (Exception e) {
}
CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security, path);
endpointFilter.afterPropertiesSet();
endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
security.authenticationEntryPoint(authenticationEntryPoint);
security.addTokenEndpointAuthenticationFilter(endpointFilter);
security.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}
/**
* 配置令牌访问端点、令牌服务
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenService.tokenStore());
List<TokenGranter> tokenGranters = getTokenGranters(endpoints.getAuthorizationCodeServices(), endpoints.getTokenStore(), endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
endpoints.tokenEnhancer(tokenService.tokenEnhancer());
}
/**
* 授权模式【用户名密码、Token刷新、授权码、手机号密码、手机号验证码】
* @param authorizationCodeServices
* @param tokenStore
* @param tokenServices
* @param clientDetailsService
* @param requestFactory
*/
private List<TokenGranter> getTokenGranters(AuthorizationCodeServices authorizationCodeServices, TokenStore tokenStore, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
return new ArrayList<>(Arrays.asList(
new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory),
new CustomRefreshTokenGranter(tokenStore, tokenServices, clientDetailsService, requestFactory),
new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory),
new MobilePasswordCustomTokenGranter(userDetailsService, tokenServices, clientDetailsService, requestFactory),
new MobileSmsCustomTokenGranter(userDetailsService, tokenServices, clientDetailsService, requestFactory)
));
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
}
~~~
*****
**2.3web安全配置**
~~~
package com.hjf.auth.configurer;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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;
/**
* 功能说明:【安全配置】
* 作 者:lihaijun
* 创建日期:2020-11-09
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.antMatchers("/oauth/**").permitAll()
.anyRequest().permitAll()
.and()
.csrf().disable();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
~~~
**2.4 自定义令牌授予者【手机号+验证码等】**
~~~
package com.hjf.auth.granter;
import com.hjf.auth.model.MyUserDetails;
import com.hjf.auth.service.CustomUserDetailsService;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.Map;
/**
* 功能说明:【自定义手机号+验证码令牌授予者】
* 作 者:lihaijun
* 创建日期:2020-11-09
*/
public class MobileSmsCustomTokenGranter extends AbstractCustomTokenGranter {
protected CustomUserDetailsService userDetailsService;
public MobileSmsCustomTokenGranter(CustomUserDetailsService userDetailsService, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
super(tokenServices, clientDetailsService, requestFactory, "sms");
this.userDetailsService = userDetailsService;
}
@Override
protected MyUserDetails getCustomUser(Map<String, String> parameters) {
String mobile = parameters.get("mobile");
String smscode = parameters.get("smscode");
return userDetailsService.loadUserByMobileAndSmsCode(mobile, smscode);
}
}
~~~
**2.5 用户细节服务实现 【具体实现根据认证类型进行用户数据查询操作】**
~~~
package com.hjf.auth.service;
import com.hjf.api.entity.Account;
import com.hjf.auth.constant.CacheKeys;
import com.hjf.auth.constant.MessageConstant;
import com.hjf.auth.mapper.AccountMapper;
import com.hjf.auth.model.MyUserDetails;
import com.hjf.core.cache.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.password.PasswordEncoder;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
/**
* 功能说明:【用户细节服务实现】
* 作 者:lihaijun
* 创建日期:2020-11-20
*/
@Slf4j
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Resource RedisUtil redisUtil;
@Resource AccountMapper accountMapper;
/**
* 手机号密码模式验证逻辑
*/
public MyUserDetails loadUserByMobileAndPassword(String mobile, String password) {
if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
throw new InvalidGrantException("您输入的手机号或密码不正确");
}
Account account=accountMapper.getByTelephone(mobile);
if (account==null){
throw new UsernameNotFoundException(MessageConstant.LOGIN_ERROR_ACCOUNT_NOTFOUND);
}
MyUserDetails userDetails=new MyUserDetails(account);
return userDetails;
}
/**
* 手机号和验证码验证逻辑
*/
public MyUserDetails loadUserByMobileAndSmsCode(String mobile, String smsCode) {
if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(smsCode)) {
log.error("【根据手机号和验证码查询用户信息】 缺失手机号或者验证码");
throw new InvalidGrantException(MessageConstant.LOGIN_ERROR_PHONE_OR_SMSCODE);
}
Object smsCodeCache=redisUtil.get(CacheKeys.SMS_CODE+mobile);
if (smsCodeCache==null){
log.error("【根据手机号和验证码查询用户信息】 没有查询到缓存数据");
throw new InvalidGrantException(MessageConstant.LOGIN_ERROR_PHONE_OR_SMSCODE);
}
if (!smsCode.equals(smsCodeCache.toString())){
log.error("【根据手机号和验证码查询用户信息】 缓存查询到的信息和输入信息不匹配");
throw new InvalidGrantException(MessageConstant.LOGIN_ERROR_PHONE_OR_SMSCODE);
}
Account account=accountMapper.getByTelephone(mobile);
if (account==null){
log.error("【根据手机号和验证码查询用户信息】 数据库没有查询到账户信息");
throw new UsernameNotFoundException(MessageConstant.LOGIN_ERROR_ACCOUNT_NOTFOUND);
}
MyUserDetails userDetails=new MyUserDetails(account);
return userDetails;
}
/**
* 用户名&密码验证逻辑【系统ResourceOwnerPasswordTokenGranter使用的默认功能 】
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String[] permissionArray = new String[]{};
Account account=accountMapper.getByTelephone(username);
if (account==null){
log.error("【用户名&密码认证】");
throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);
}
MyUserDetails securityUser = new MyUserDetails(account);
securityUser.setPassword(passwordEncoder.encode(account.getPassword()));
securityUser.setEnabled(true);
return securityUser;
}
}
~~~
**2.6 token服务工具【tokenEnhancer、tokenStore、jwtAccessTokenConverter】**
~~~
package com.hjf.auth.token;
import com.hjf.auth.model.MyUserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.security.KeyPair;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* 功能说明:【Token服务】
* 作 者:lihaijun
* 创建日期:2020-11-20
*/
@Component
public class TokenService {
@Autowired private RedisConnectionFactory redisFactory;
private TokenStoreType tokenStoreType = TokenStoreType.JWT;
private enum TokenStoreType {
IN_MEMORY, REDIS, JWT
}
@Bean
public TokenStore tokenStore() {
switch (tokenStoreType) {
case REDIS:
return new RedisTokenStore(redisFactory);
case JWT:
return new JwtTokenStore(jwtAccessTokenConverter());
}
return new InMemoryTokenStore();
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new TokenEnhancer() {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken;
MyUserDetails user = (MyUserDetails) oAuth2Authentication.getPrincipal();
Map<String, Object> map = new LinkedHashMap<>();
map.put("username", user.getUsername());
map.put("telephone", user.getTelephone());
map.put("accountId", user.getAccountId());
token.setAdditionalInformation(map);
if (tokenStoreType == TokenStoreType.JWT) {
token = (DefaultOAuth2AccessToken) jwtAccessTokenConverter().enhance(oAuth2AccessToken, oAuth2Authentication);
} else {
token.setValue(buildTokenValue());
if (token.getRefreshToken() != null) {
if (token.getRefreshToken() instanceof DefaultExpiringOAuth2RefreshToken) {
DefaultExpiringOAuth2RefreshToken refreshToken = (DefaultExpiringOAuth2RefreshToken) token.getRefreshToken();
token.setRefreshToken(new DefaultExpiringOAuth2RefreshToken(buildTokenValue(), refreshToken.getExpiration()));
} else {
token.setRefreshToken(new DefaultOAuth2RefreshToken(buildTokenValue()));
}
}
}
return token;
}
};
}
@Bean
public KeyPair keyPair() {
//从classpath下的证书中获取秘钥对
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
// accessTokenConverter.setSigningKey("demo-oauth2");
((DefaultAccessTokenConverter) accessTokenConverter.getAccessTokenConverter()).setUserTokenConverter(new DefaultUserAuthenticationConverter() {
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
MyUserDetails customUser = new MyUserDetails();
BeanMap.create(customUser).putAll(map);
Object authorities = map.get("authorities");
if (authorities instanceof String) {
customUser.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities));
} else if (authorities instanceof Collection) {
customUser.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection) authorities)));
}
return new PreAuthenticatedAuthenticationToken(customUser, null, customUser.getAuthorities());
}
});
accessTokenConverter.setKeyPair(keyPair());
return accessTokenConverter;
}
public String buildTokenValue() {
String tokenValue = UUID.randomUUID().toString() + UUID.randomUUID().toString();
return tokenValue;
}
}
~~~
**2.7 token生成入口**
~~~
package com.hjf.auth.controller;
import com.hjf.auth.token.Oauth2TokenDto;
import com.hjf.frame.base.BaseResp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.Map;
/**
* 功能说明:【认证服务入口】
* 作 者:lihaijun
* 创建日期:2020-11-20
*/
@RestController
@RequestMapping("/oauth")
public class OauthController {
@Autowired
private TokenEndpoint tokenEndpoint;
@GetMapping("/token")
public BaseResp getAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
return resp(tokenEndpoint.getAccessToken(principal, parameters).getBody());
}
@PostMapping("/token")
public BaseResp postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
return resp(tokenEndpoint.postAccessToken(principal, parameters).getBody());
}
/**
* 定制申请返回实体
*/
private BaseResp resp(OAuth2AccessToken accessToken) {
Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder()
.token(accessToken.getValue())
.refreshToken(accessToken.getRefreshToken().getValue())
.expiresIn(accessToken.getExpiresIn())
.tokenHead("Bearer ")
.additionalInformation(accessToken.getAdditionalInformation())
.build();
return BaseResp.success(oauth2TokenDto);
}
}
~~~
*****
测试 localhost:8000/oauth/token?client\_id=client-mp&client\_secret=123456&mobile=15010123237&password=123456&smscode=1111&grant\_type=sms
![](https://img.kancloud.cn/9c/45/9c45e725c73882e3149bdd6904f90e9a_735x523.png)
- 项目介绍
- 项目声明
- 项目简介
- 架构设计
- 项目亮点功能介绍
- 技术栈介绍
- 核心功能
- 运行环境
- 项目更新日志
- 文档更新日志
- F&Q
- 部署教程
- 环境准备
- JDK安装
- JDK1.8,17共存
- maven
- 分布式缓存Redis
- 单机版
- 集群
- 注册&配置中心alibaba/nacos
- 介绍
- Nacos安装
- Nacos配置中心
- Nacos注册发现
- Nacos生产部署方案
- 服务监控-BootAdmin
- 基本介绍
- 如何使用
- 整合Admin-Ui
- 客户端配置
- 链路追踪
- 基本介绍
- SkyWalking-1
- Skywalking-1
- 消息队列
- Kafka
- docker安装kafka
- Linux集群
- Maven私服
- nexus安装部署
- nexus使用介绍
- 全文搜索elasticsearch
- windows集群搭建
- docker安装es
- ElasticHD
- linux集群部署
- 统一日志解决方案
- 日志解决方案设计
- 介绍与相关资料
- ELK安装部署
- elasticsearch 7.5
- logstash-7.5
- kibana-7.5
- filebeat
- 服务监控-Prometheus
- Prometheus安装配置
- Prometheus介绍
- grafana
- 持续集成部署CICD
- 自动化部署Jenkins
- 安装部署win
- 打包发布远程执行
- 安装部署linux
- jenkins+gitlab+docker容器化工程自动化部署
- Git
- CICD说明
- 阿里云效
- CentOS_MYSQL安装
- docker
- 安装
- Docker安装Nginx
- Docker部署启动springboot
- dockerCompose
- harbor
- Docker私有镜像仓库
- Portainer
- Docker远程连接设置
- 打包工程
- 必要启动模块
- 核心模块
- 登录认证
- 缓存功能
- 日志模块
- 分布式锁
- 消息队列
- 异常处理
- 系统接口
- 参数验证
- es检索
- 数据导出
- 系统设计
- 系统总体架构
- 扩展模块(可选)
- 限流熔断alibaba/sentinel
- 使用Sentinel实现gateway网关及服务接口限流
- Sentinel使用Nacos存储规则及同步
- 服务调用Feign
- Feign基本介绍
- 如何使用
- 负载均衡
- 请求超时
- 请求拦截器
- 分布式任务调度
- XXL-JOB
- 分布式事务
- TX-LCN
- Seata
- Seata原理解析
- 数据库分库分表
- swagger文档
- 分布式ID生成器解决方案
- 服务网关CloudGateway
- 基本介绍
- 使用网关
- 路由配置
- 全局过滤器
- 服务认证授权架构设计
- 认证服务流程
- 授权服务流程
- 系统幂等性设计与实践
- 分布式日志链路跟踪
- 实时搜索系统设计
- 应用性能
- 压力测试工具
- Apache JMeter介绍和安装
- ApacheJMeter使用
- JVM
- JVM性能调优
- 常见JVM内存错误及解决方案
- JVM 分析工具详解
- Spring Cloud性能调优
- Linux运维
- Linux 常用命令
- Linux开启端口