Spring boot access to GitHub third party login

Osu rebyzmjz 2020-11-10 13:38:55
spring boot access github party


Click the blue character above to set the star


link :zyc.red/Spring/Security/OAuth2/OAuth2-Client/

Preface

OAuth( Open licensing ) It's an open standard , Allow users to authorize third-party websites to access information they store on other service providers , Instead of providing a user name and password to a third-party website or sharing all of their data . There's a lot about it online OAuth Explanation of the agreement , I'm not going to explain it in detail here OAuth Related concepts , Please refer to the relevant information by yourself , Otherwise, the rest of this article may be difficult to understand .

Spring-Security Yes OAuth2.0 Support for

As of the date of this article ,Spring Has been provided for OAuth Support provided (spring-security-oauth:https://github.com/spring-projects/spring-security-oauth), But the project has been abandoned , because Spring-Security The project provides the latest OAuth2.0 Support . If you use expired Spring-Security-OAuth, Please refer to 《OAuth 2.0 Migration guide :https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide》,

This article will OAuth2.0 Principle analysis of client mode in , combination Spring The official guide provides a simple one based on spring-boot And oauth2.0 Integration of third-party application login cases (spring-boot-oauth2:https://spring.io/guides/tutorials/spring-boot-oauth2/), Step by step analysis of the principle of its internal implementation .

The official account has also released nearly 100 articles Spring Boot Related practical articles , Pay attention to WeChat public number Java Back end , reply 666 Download this technical stack manual .

establish GitHub OAuth Apps

stay Github OAuth Apps Create a new application in

This app is equivalent to our own app ( client ), Registered in Github( Authorization server ) It's in , If our users have github If you have an account number , It can be based on oauth2 To log in to our system , Instead of the original user name and password . In the example of the official guide , Use spring-security and oauth2 Social login only needs to be done on your pom Add the following dependencies to the file :

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

Then fill in the configuration file with the clientId and clientSecret:

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            clientId: github-client-id
            clientSecret: github-client-secret

And then it's like normal spring-security The application is the same , Inherit WebSecurityConfigurerAdapter, Simple configuration is enough :

@SpringBootApplication
@RestController
public class SocialApplication extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      
        http
            .authorizeRequests(a -> a
                .antMatchers("/", "/error", "/webjars/**").permitAll()
                .anyRequest().authenticated()
            )
            .exceptionHandling(e -> e
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
            )
            .oauth2Login();
        
    }
}

That is to say, we just need to add maven Dependency and inheritance WebSecurityConfigurerAdapter Do some simple configuration , One oauth2 The client application is built . Next, follow the steps in the guide and click on the page github Log in and our page will jump to github Authorization landing page , Wait for the user's authorization to complete and the browser will be redirected to our callback URL The final request user The information endpoint can access the just logged in github User information , The whole application is so simple to build , What's the principle behind it ? Next we start to analyze .

Same as before , In the configuration file, we will security The log level of is set to debug

logging:
  level:
    org.springframework.security: debug

After restarting the application , From the output of the console, we can see that it is similar to the normal spring-security The difference in application is that the following filters are added to the whole filter chain :

OAuth2AuthorizationRequestRedirectFilter
OAuth2LoginAuthenticationFilter

lenovo oauth2 The authorization code pattern and the names of the two filters , be familiar with spring-security My classmates must have some ideas in their hearts . Yes, it is ,spring-security The support of client mode is based on these two filters . Now let's recall the execution process of authorization code mode

  1. The user clicks the three party application login button on the client page ( The client is what we just registered github application )

  2. The page will jump to the authorized party page of three party application registration ( The authorization server is github)

  3. After user login Authorization ,github Call the callback address of our application ( We just registered github The callback address filled in when applying )

  4. In the callback address of step 3 github Will code Put the parameters in url in , Then our client will take this internally code Call again github

    Of access_token Address acquisition token

That's the standard authorization_code Authorization mode ,OAuth2AuthorizationRequestRedirectFilter The function of the above step is 1.2 The combination of steps , When the user clicks on the page github to grant authorization url after ,OAuth2AuthorizationRequestRedirectFilter Match this request , And then it will put the clientId、scope And construct a state Parameters ( prevent csrf attack ) To join together into one url Redirect to github Authorization of url,OAuth2LoginAuthenticationFilter The role of the top 3.4 The combination of steps , When the user github After the authorization page is authorized github Call callback address ,OAuth2LoginAuthenticationFilter Match this callback address , The address of the callback is resolved code And state After the parameters are verified, take this inside code The remote invocation github Of access_token Address , Get access_token After through OAuth2UserService Get the corresponding user information ( Inside is to take access_token The remote invocation github User information endpoint for ) Finally, the user information is constructed into Authentication By SecurityContextPersistenceFilter Filter saved to HttpSession in .

Let's take a look at the internal execution of these two filters :

public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilter {
    
    ...... Omitted code
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    try {
      OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
      if (authorizationRequest != null) {
        this.sendRedirectForAuthorization(request, response, authorizationRequest);
        return;
      }
    } catch (Exception failed) {
      this.unsuccessfulRedirectForAuthorization(request, response, failed);
      return;
    }
        ...... Omitted code
}

adopt authorizationRequestResolver The parser parses the request , The default implementation of the parser is DefaultOAuth2AuthorizationRequestResolver, The core parsing method is as follows :

@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
    
    String registrationId = this.resolveRegistrationId(request);
    String redirectUriAction = getAction(request, "login");
    return resolve(request, registrationId, redirectUriAction);
}
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) {
    if (registrationId == null) {
        return null;
    }
  
    ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
    if (clientRegistration == null) {
        throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
    }
    Map<String, Object> attributes = new HashMap<>();
    attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
    OAuth2AuthorizationRequest.Builder builder;
    
    if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
        builder = OAuth2AuthorizationRequest.authorizationCode();
        Map<String, Object> additionalParameters = new HashMap<>();
        if (!CollectionUtils.isEmpty(clientRegistration.getScopes()) &&
            clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
            
            
            
            addNonceParameters(attributes, additionalParameters);
        }
        if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
            addPkceParameters(attributes, additionalParameters);
        }
        builder.additionalParameters(additionalParameters);
    } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
        builder = OAuth2AuthorizationRequest.implicit();
    } else {
        throw new IllegalArgumentException("Invalid Authorization Grant Type ("  +
                                           clientRegistration.getAuthorizationGrantType().getValue() +
                                           ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
    }
    String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
    OAuth2AuthorizationRequest authorizationRequest = builder
        .clientId(clientRegistration.getClientId())
        .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
        .redirectUri(redirectUriStr)
        .scopes(clientRegistration.getScopes())
        
        .state(this.stateGenerator.generateKey())
        .attributes(attributes)
        .build();
    return authorizationRequest;
}

DefaultOAuth2AuthorizationRequestResolver Determine whether the request is an authorization request , Finally return to a OAuth2AuthorizationRequest The object is to OAuth2AuthorizationRequestRedirectFilter, If OAuth2AuthorizationRequest Not for null Words , State that the current request is an authorization request , Then it's time to redirect the request to the authorization endpoint of the authorization server , Let's take a look at OAuth2AuthorizationRequestRedirectFilter Send redirection logic :

private void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
                                          OAuth2AuthorizationRequest authorizationRequest) throws IOException {
    if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationRequest.getGrantType())) {
        this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
    }
    this.authorizationRedirectStrategy.sendRedirect(request, response, authorizationRequest.getAuthorizationRequestUri());
}

1. If the current authorization request is an authorization code type, then the request information needs to be saved , Because the authorization server calls back next, we need to use the parameters of the authorization request for verification and other operations ( comparison state), This is through authorizationRequestRepository Save the authorization request , The default way to save is through HttpSessionOAuth2AuthorizationRequestRepository Save in httpsession Medium , The specific preservation logic is very simple , I won't go into details here .

2. After saving, it is time to start redirecting to the authorized service endpoint , The default here is authorizationRedirectStrategy yes DefaultRedirectStrategy, The logic of redirection is simple , adopt response.sendRedirect Method to redirect the front page to the specified authorization

public void sendRedirect(HttpServletRequest request, HttpServletResponse response,
                         String url) throws IOException {
    String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
    redirectUrl = response.encodeRedirectURL(redirectUrl);
    if (logger.isDebugEnabled()) {
        logger.debug("Redirecting to '" + redirectUrl + "'");
    }
    response.sendRedirect(redirectUrl);
}

OAuth2AuthorizationRequestRedirectFilter The processing logic is over , Let's make a summary of its handling process

a. Through internal OAuth2AuthorizationRequestResolver Parse the current request , Return to one OAuth2AuthorizationRequest object , If the current request is an authorization endpoint request , Then a constructed object is returned , Including our client_id、state、redirect_uri Parameters , If the object is null Words , Then it means that the current request is not an authorization endpoint request .

Note that if OAuth2AuthorizationRequestResolver Not for null Words ,OAuth2AuthorizationRequestResolver It will be stored internally in httpsession In this way, when the authorization server calls our callback address, we can start from httpsession The request will be state Contrast in case csrf attack .

b. If the first step returns OAuth2AuthorizationRequest Objects are not as null Words , And then it's going to go through response.sendRedirect Method will OAuth2AuthorizationRequest The request from the authorization endpoint in is sent to the response header of the front end, and then the browser will be redirected to the authorization page , Waiting for user authorization .

OAuth2LoginAuthenticationFilter

public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
      throws AuthenticationException {
    MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
        
    if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
      OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
      throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    
        
    OAuth2AuthorizationRequest authorizationRequest =
        this.authorizationRequestRepository.removeAuthorizationRequest(request, response);
    if (authorizationRequest == null) {
      OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
      throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    
        
    String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
    ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
    if (clientRegistration == null) {
      OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
          "Client Registration not found with Id: " + registrationId, null);
      throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
        .replaceQuery(null)
        .build()
        .toUriString();
    OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params, redirectUri);
    Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
    OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(
        clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
    authenticationRequest.setDetails(authenticationDetails);
    
    OAuth2LoginAuthenticationToken authenticationResult =
      (OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest);
    
    OAuth2AuthenticationToken oauth2Authentication = new OAuth2AuthenticationToken(
      authenticationResult.getPrincipal(),
      authenticationResult.getAuthorities(),
      authenticationResult.getClientRegistration().getRegistrationId());
    oauth2Authentication.setDetails(authenticationDetails);  
        
    OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
      authenticationResult.getClientRegistration(),
      oauth2Authentication.getName(),
      authenticationResult.getAccessToken(),
      authenticationResult.getRefreshToken());
    this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
    return oauth2Authentication;
  }
}

OAuth2LoginAuthenticationFilter It's very simple , The address of the authorization server is the response , The core is OAuth2LoginAuthenticationProvider Yes OAuth2LoginAuthenticationToken Certification of ,

OAuth2LoginAuthenticationToken

OAuth2LoginAuthenticationProvider

public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
    
     ... Omitted code
    
    @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    OAuth2LoginAuthenticationToken authorizationCodeAuthentication =
      (OAuth2LoginAuthenticationToken) authentication;
    
    
    
    if (authorizationCodeAuthentication.getAuthorizationExchange()
      .getAuthorizationRequest().getScopes().contains("openid")) {
      
      
      return null;
    }
    OAuth2AccessTokenResponse accessTokenResponse;
    try {
      OAuth2AuthorizationExchangeValidator.validate(
          authorizationCodeAuthentication.getAuthorizationExchange());
      
      accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
          new OAuth2AuthorizationCodeGrantRequest(
              authorizationCodeAuthentication.getClientRegistration(),
              authorizationCodeAuthentication.getAuthorizationExchange()));
    } catch (OAuth2AuthorizationException ex) {
      OAuth2Error oauth2Error = ex.getError();
      throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    
         
    OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
    Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters();
    
         
    OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
        authorizationCodeAuthentication.getClientRegistration(), accessToken, additionalParameters));
    Collection<? extends GrantedAuthority> mappedAuthorities =
      this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
    
         
    OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
      authorizationCodeAuthentication.getClientRegistration(),
      authorizationCodeAuthentication.getAuthorizationExchange(),
      oauth2User,
      mappedAuthorities,
      accessToken,
      accessTokenResponse.getRefreshToken());
    authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
    return authenticationResult;
  }
    ... Omitted code
}

OAuth2LoginAuthenticationProvider The execution logic of is very simple , First, through code obtain access_token, And then through access_token Get user information , This and the standard oauth2 Authorization code pattern is consistent .

Automatic configuration

stay spring In the example of the guide , We found that it was just a simple configuration oauth2Login() Method , A complete oauth2 The authorization process is established , In fact, it's all due to spring-boot Of autoconfigure, We find spring-boot-autoconfigure.jar In bag security.oauth2.client.servlet package , You can find spring-boot Several autoconfiguration classes are provided for us :

OAuth2ClientAutoConfiguration
OAuth2ClientRegistrationRepositoryConfiguration
OAuth2WebSecurityConfiguration

among OAuth2ClientAutoConfiguration Imported OAuth2ClientRegistrationRepositoryConfiguration and OAuth2WebSecurityConfiguration Configuration of

OAuth2ClientRegistrationRepositoryConfiguration:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@Conditional(ClientsConfiguredCondition.class)
class OAuth2ClientRegistrationRepositoryConfiguration {
    @Bean
    @ConditionalOnMissingBean(ClientRegistrationRepository.class)
    InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
        List<ClientRegistration> registrations = new ArrayList<>(
            OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());
        return new InMemoryClientRegistrationRepository(registrations);
    }
}

OAuth2ClientRegistrationRepositoryConfiguration The client Constructed as ClientRegistration And save it in memory . There's a hidden CommonOAuth2Provider class , This is an enumeration class , There are several commonly used parameters of three-party login authorization server, such as GOOGLE、GITHUB、FACEBOO、OKTA

CommonOAuth2Provider

public enum CommonOAuth2Provider {
  GOOGLE {
    @Override
    public Builder getBuilder(String registrationId) {
      ClientRegistration.Builder builder = getBuilder(registrationId,
          ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
      builder.scope("openid", "profile", "email");
      builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");
      builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");
      builder.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs");
      builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");
      builder.userNameAttributeName(IdTokenClaimNames.SUB);
      builder.clientName("Google");
      return builder;
    }
  },
  GITHUB {
    @Override
    public Builder getBuilder(String registrationId) {
      ClientRegistration.Builder builder = getBuilder(registrationId,
          ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
      builder.scope("read:user");
      builder.authorizationUri("https://github.com/login/oauth/authorize");
      builder.tokenUri("https://github.com/login/oauth/access_token");
      builder.userInfoUri("https://api.github.com/user");
      builder.userNameAttributeName("id");
      builder.clientName("GitHub");
      return builder;
    }
  },
  FACEBOOK {
    @Override
    public Builder getBuilder(String registrationId) {
      ClientRegistration.Builder builder = getBuilder(registrationId,
          ClientAuthenticationMethod.POST, DEFAULT_REDIRECT_URL);
      builder.scope("public_profile", "email");
      builder.authorizationUri("https://www.facebook.com/v2.8/dialog/oauth");
      builder.tokenUri("https://graph.facebook.com/v2.8/oauth/access_token");
      builder.userInfoUri("https://graph.facebook.com/me?fields=id,name,email");
      builder.userNameAttributeName("id");
      builder.clientName("Facebook");
      return builder;
    }
  },
  OKTA {
    @Override
    public Builder getBuilder(String registrationId) {
      ClientRegistration.Builder builder = getBuilder(registrationId,
          ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
      builder.scope("openid", "profile", "email");
      builder.userNameAttributeName(IdTokenClaimNames.SUB);
      builder.clientName("Okta");
      return builder;
    }
  };
  private static final String DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}";
  protected final ClientRegistration.Builder getBuilder(String registrationId,
                              ClientAuthenticationMethod method, String redirectUri) {
    ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(registrationId);
    builder.clientAuthenticationMethod(method);
    builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
    builder.redirectUriTemplate(redirectUri);
    return builder;
  }
    
  public abstract ClientRegistration.Builder getBuilder(String registrationId);
}

That's why we don't configure github The reason why the authorization endpoint can jump to the authorization page .

OAuth2WebSecurityConfiguration

OAuth2WebSecurityConfiguration Configure some web Related to the class , Like how to save and get authorized clients , And default oauth2 Client related configuration

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ClientRegistrationRepository.class)
class OAuth2WebSecurityConfiguration {
  @Bean
  @ConditionalOnMissingBean
  OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
    return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
  }
  @Bean
  @ConditionalOnMissingBean
  OAuth2AuthorizedClientRepository authorizedClientRepository(OAuth2AuthorizedClientService authorizedClientService) {
    return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
  }
  
    
  @Configuration(proxyBeanMethods = false)
  @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
  static class OAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
      http.oauth2Login(Customizer.withDefaults());
      http.oauth2Client();
    }
  }
}

Reference resources :

Integrate GitHub and QQ Social login

https://github.com/Allurx/spring-security-oauth2-demo/tree/master/spring-security-oauth2-client

spring-security-oauth Update the route

https://spring.io/blog/2019/11/14/spring-security-oauth-2-0-roadmap-update

spring-security Yes oauth2.0 Authorization server support

https://github.com/spring-projects/spring-security/issues/6320

Use spring-boot and oauth2.0 Building social logins

https://spring.io/guides/tutorials/spring-boot-oauth2/

 Recommended reading 
 Code comparison tool , I'll use this 6 individual 
 Share my favorite 5 A free online  SQL Database environment , It's so convenient !
Spring Boot A combination of three moves , Hand in hand to teach you to play elegant back-end interface 
MySQL 5.7 vs 8.0, You choose the one ? Net friend : I'm going to stay where I am ~

Last , I recommend you an interesting and interesting official account : The guy who wrote the code ,7 Old programmers teach you to write bug, reply interview | resources Send you a complete set of Development Notes There's a surprise
版权声明
本文为[Osu rebyzmjz]所创,转载请带上原文链接,感谢

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云