Java中关于OAuth2.0的原理分析

 更新时间:2023年09月18日 09:08:05   作者:还没秃的小菜鸡  
这篇文章主要介绍了Java中关于OAuth2.0的原理分析,OAuth是一个关于授权的开放网络标准,允许用户授权第三 方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,需要的朋友可以参考下

授权服务器

@EnableAuthorizationServer解析

我们都知道 一个授权认证服务器最最核心的就是 @EnableAuthorizationServer , 那么 @EnableAuthorizationServer 主要做了什么呢?

我们看下 @EnableAuthorizationServer 源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {
}

我们可以看到其源码内部导入了 AuthorizationServerEndpointsConfigurationAuthorizationServerSecurityConfiguration 这2个配置类。 接下来我们分别看下这2个配置类具体做了什么。

AuthorizationServerEndpointsConfiguration

@Configuration
@Import(TokenKeyEndpointRegistrar.class)
public class AuthorizationServerEndpointsConfiguration {
    // 省略 其他相关配置代码
  ....
    // 1、 AuthorizationEndpoint 创建
    @Bean
    public AuthorizationEndpoint authorizationEndpoint() throws Exception {
        AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();
        FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
        authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
        authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator());
        authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
        authorizationEndpoint.setTokenGranter(tokenGranter());
        authorizationEndpoint.setClientDetailsService(clientDetailsService);
        authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());
        authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
        authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
        authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());
        authorizationEndpoint.setRedirectResolver(redirectResolver());
        return authorizationEndpoint;
    }
    // 2、 TokenEndpoint 创建
    @Bean
    public TokenEndpoint tokenEndpoint() throws Exception {
        TokenEndpoint tokenEndpoint = new TokenEndpoint();
        tokenEndpoint.setClientDetailsService(clientDetailsService);
        tokenEndpoint.setProviderExceptionHandler(exceptionTranslator());
        tokenEndpoint.setTokenGranter(tokenGranter());
        tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
        tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
        tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods());
        return tokenEndpoint;
    }
    // 省略 其他相关配置代码
   ....

通过源码我们可以很明确的知道:

  • AuthorizationEndpoint 用于服务授权请求。预设地址:/oauth/authorize。
  • TokenEndpoint 用于服务访问令牌的请求。预设地址:/oauth/token。

AuthorizationServerSecurityConfiguration

  • ClientDetailsService : 内部仅有 loadClientByClientId 方法。从方法名我们就可知其是通过 clientId 来获取 Client 信息, 官方提供 JdbcClientDetailsService、InMemoryClientDetailsService 2个实现类,我们也可以像UserDetailsService 一样编写自己的实现类。
  • UserDetailsService : 内部仅有 loadUserByUsername 方法。这个类不用我再介绍了吧。不清楚得同学可以看下我之前得文章。
  • ClientDetailsUserDetailsService : UserDetailsService子类,内部维护了 ClientDetailsService 。其 loadUserByUsername 方法重写后调用ClientDetailsService.loadClientByClientId()。
  • ClientCredentialsTokenEndpointFilter** 作用与 UserNamePasswordAuthenticationFilter 类似,通过拦截 /oauth/token 地址,获取到 clientId 和 clientSecret 信息并创建 UsernamePasswordAuthenticationToken 作为 AuthenticationManager.authenticate() 参数 调用认证过程。整个认证过程唯一最大得区别在于 DaoAuthenticationProvider.retrieveUser() 获取认证用户信息时调用的是 ClientDetailsUserDetailsService,根据前面讲述的其内部其实是调用ClientDetailsService 获取到客户端信息

@EnableResourceServer

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ResourceServerConfiguration.class})
public @interface EnableResourceServer {
}

从源码中我们可以看到其导入了 ResourceServerConfiguration 配置类,这个配置类最核心的配置是 应用了 ResourceServerSecurityConfigurer ,我这边贴出 ResourceServerSecurityConfigurer 源码 最核心的配置代码如下:

public void configure(HttpSecurity http) throws Exception {
    AuthenticationManager oauthAuthenticationManager = this.oauthAuthenticationManager(http);
    this.resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
    this.resourcesServerFilter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
    this.resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
    if (this.eventPublisher != null) {
        this.resourcesServerFilter.setAuthenticationEventPublisher(this.eventPublisher);
    }
    if (this.tokenExtractor != null) {
        this.resourcesServerFilter.setTokenExtractor(this.tokenExtractor);
    }
    this.resourcesServerFilter = (OAuth2AuthenticationProcessingFilter)this.postProcess(this.resourcesServerFilter);
    this.resourcesServerFilter.setStateless(this.stateless);
    ((HttpSecurity)http.authorizeRequests().expressionHandler(this.expressionHandler).and()).addFilterBefore(this.resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class).exceptionHandling().accessDeniedHandler(this.accessDeniedHandler).authenticationEntryPoint(this.authenticationEntryPoint);
}
private AuthenticationManager oauthAuthenticationManager(HttpSecurity http) {
    OAuth2AuthenticationManager oauthAuthenticationManager = new OAuth2AuthenticationManager();
    if (this.authenticationManager != null) {
        if (!(this.authenticationManager instanceof OAuth2AuthenticationManager)) {
            return this.authenticationManager;
        }
        oauthAuthenticationManager = (OAuth2AuthenticationManager)this.authenticationManager;
    }
    oauthAuthenticationManager.setResourceId(this.resourceId);
    oauthAuthenticationManager.setTokenServices(this.resourceTokenServices(http));
    oauthAuthenticationManager.setClientDetailsService(this.clientDetails());
    return oauthAuthenticationManager;
}

源码中最核心的 就是 官方文档中介绍的 OAuth2AuthenticationProcessingFilter 过滤器, 其配置分3步:

1、 创建 OAuth2AuthenticationProcessingFilter 过滤器 对象

2、 创建 OAuth2AuthenticationManager 对象 对将其作为参数设置到 OAuth2AuthenticationProcessingFilter 中

3、 将 OAuth2AuthenticationProcessingFilter 过滤器添加到过滤器链上

AuthorizationEndpoint生成授权码

@RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
                              SessionStatus sessionStatus, Principal principal) {
    //  1、 通过 OAuth2RequestFactory 从 参数中获取信息创建 AuthorizationRequest 授权请求对象
    AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);
    Set<String> responseTypes = authorizationRequest.getResponseTypes();
    if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
        throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
    }
    if (authorizationRequest.getClientId() == null) {
        throw new InvalidClientException("A client id must be provided");
    }
    try {
        // 2、 判断  principal 是否 已授权 : /oauth/authorize 设置为无权限访问 ,所以要判断,如果 判断失败则抛出 InsufficientAuthenticationException (AuthenticationException 子类),其异常会被 ExceptionTranslationFilter 处理 ,最终跳转到 登录页面,这也是为什么我们第一次去请求获取 授权码时会跳转到登陆界面的原因
        if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
            throw new InsufficientAuthenticationException(
                    "User must be authenticated with Spring Security before authorization can be completed.");
        }
        // 3、 通过 ClientDetailsService.loadClientByClientId() 获取到 ClientDetails 客户端信息
        ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
        // 4、 获取参数中的回调地址并且与系统配置的回调地址对比
        String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
        String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
        if (!StringUtils.hasText(resolvedRedirect)) {
            throw new RedirectMismatchException(
                    "A redirectUri must be either supplied or preconfigured in the ClientDetails");
        }
        authorizationRequest.setRedirectUri(resolvedRedirect);
        //  5、 验证 scope 
        oauth2RequestValidator.validateScope(authorizationRequest, client);
        //  6、 检测该客户端是否设置自动 授权(即 我们配置客户端时配置的 autoApprove(true)  )
        authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
                (Authentication) principal);
        boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
        authorizationRequest.setApproved(approved);
        if (authorizationRequest.isApproved()) {
            if (responseTypes.contains("token")) {
                return getImplicitGrantResponse(authorizationRequest);
            }
            if (responseTypes.contains("code")) {
                // 7 调用 getAuthorizationCodeResponse() 方法生成code码并回调到设置的回调地址
                return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
                        (Authentication) principal));
            }
        }
        model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
        model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest));
        return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
    }
    catch (RuntimeException e) {
        sessionStatus.setComplete();
        throw e;
    }
}

1、 通过 OAuth2RequestFactory 从 参数中获取信息创建 AuthorizationRequest 授权请求对象

2、 判断 principal 是否 已授权 : /oauth/authorize 设置为无权限访问 ,所以要判断,如果 判断失败则抛出 InsufficientAuthenticationException (AuthenticationException 子类),其异常会被 ExceptionTranslationFilter 处理 ,最终跳转到 登录页面,这也是为什么我们第一次去请求获取 授权码时会跳转到登陆界面的原因

3、 通过 ClientDetailsService.loadClientByClientId() 获取到 ClientDetails 客户端信息

4、 获取参数中的回调地址并且与系统配置的回调地址(步骤3获取到的client信息)对比

5、 与步骤4一样 验证 scope

6、 检测该客户端是否设置自动 授权(即 我们配置客户端时配置的 autoApprove(true))

7、 由于我们设置 autoApprove(true) 则 调用 getAuthorizationCodeResponse() 方法生成code码并回调到设置的回调地址

8、 真实生成Code 的方法时 generateCode(AuthorizationRequest authorizationRequest, Authentication authentication) 方法: 其内部是authorizationCodeServices.createAuthorizationCode()方法生成code的

TokenEndpoint 生成token

@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
        Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
    // 1、 验证 用户信息 (正常情况下会经过 ClientCredentialsTokenEndpointFilter 过滤器认证后获取到用户信息 )
    if (!(principal instanceof Authentication)) {
        throw new InsufficientAuthenticationException(
                "There is no client authentication. Try adding an appropriate authentication filter.");
    }
    // 2、 通过 ClientDetailsService().loadClientByClientId() 获取系统配置客户端信息
    String clientId = getClientId(principal);
    ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
    // 3、 通过客户端信息生成 TokenRequest 对象
    TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
 ......
    // 4、 调用 TokenGranter.grant()方法生成 OAuth2AccessToken 对象(即token)
    OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
    if (token == null) {
        throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
    }
    // 5、 返回token
    return getResponse(token);
}

1、 验证 用户信息 (正常情况下会经过 ClientCredentialsTokenEndpointFilter 过滤器认证后获取到用户信息 )

2、 通过 ClientDetailsService().loadClientByClientId() 获取系统配置的客户端信息

3、 通过客户端信息生成 TokenRequest 对象

4、 将步骤3获取到的 TokenRequest 作为TokenGranter.grant() 方法参照 生成 OAuth2AccessToken 对象(即token)

5、 返回 token

TokenGranter

TokenGranter的设计思路是使用CompositeTokenGranter管理一个List列表,每一种grantType对应一个具体的真正授权者,CompositeTokenGranter 内部就是在循环调用五种TokenGranter实现类的 grant方法,而granter内部则是通过grantType来区分是否是各自的授权类型。

五种类型分别是:

  • ResourceOwnerPasswordTokenGranter ==> password密码模式
  • AuthorizationCodeTokenGranter ==> authorization_code授权码模式
  • ClientCredentialsTokenGranter ==> client_credentials客户端模式
  • ImplicitTokenGranter ==> implicit简化模式
  • RefreshTokenGranter ==>refresh_token 刷新token专用

OAuth2AccessToken

@JsonSerialize(
    using = OAuth2AccessTokenJackson1Serializer.class
)
@JsonDeserialize(
    using = OAuth2AccessTokenJackson1Deserializer.class
)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(
    using = OAuth2AccessTokenJackson2Serializer.class
)
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(
    using = OAuth2AccessTokenJackson2Deserializer.class
)
public interface OAuth2AccessToken {
    String BEARER_TYPE = "Bearer";
    String OAUTH2_TYPE = "OAuth2";
    String ACCESS_TOKEN = "access_token";
    String TOKEN_TYPE = "token_type";
    String EXPIRES_IN = "expires_in";
    String REFRESH_TOKEN = "refresh_token";
    String SCOPE = "scope";
}

AuthorizationServerTokenServices

public interface AuthorizationServerTokenServices {
    //创建
    OAuth2AccessToken createAccessToken(OAuth2Authentication var1) throws AuthenticationException;
	//刷新
    OAuth2AccessToken refreshAccessToken(String var1, TokenRequest var2) throws AuthenticationException;
	//获取
    OAuth2AccessToken getAccessToken(OAuth2Authentication var1);
}

流程

在这里插入图片描述

资源服务器

@EnableResourceServer

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ResourceServerConfiguration.class})
public @interface EnableResourceServer {
}

ResourceServerConfiguration

protected void configure(HttpSecurity http) throws Exception {
    ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
    ResourceServerTokenServices services = this.resolveTokenServices();
    if (services != null) {
        resources.tokenServices(services);
    } else if (this.tokenStore != null) {
        resources.tokenStore(this.tokenStore);
    } else if (this.endpoints != null) {
        resources.tokenStore(this.endpoints.getEndpointsConfigurer().getTokenStore());
    }
    if (this.eventPublisher != null) {
        resources.eventPublisher(this.eventPublisher);
    }
    Iterator var4 = this.configurers.iterator();
    ResourceServerConfigurer configurer;
    while(var4.hasNext()) {
        configurer = (ResourceServerConfigurer)var4.next();
        configurer.configure(resources);
    }
    ((HttpSecurity)((HttpSecurity)http.authenticationProvider(new AnonymousAuthenticationProvider("default")).exceptionHandling().accessDeniedHandler(resources.getAccessDeniedHandler()).and()).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()).csrf().disable();
    http.apply(resources);
    if (this.endpoints != null) {
        http.requestMatcher(new ResourceServerConfiguration.NotOAuthRequestMatcher(this.endpoints.oauth2EndpointHandlerMapping()));
    }
    var4 = this.configurers.iterator();
    while(var4.hasNext()) {
        configurer = (ResourceServerConfigurer)var4.next();
        configurer.configure(http);
    }
    if (this.configurers.isEmpty()) {
        ((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated();
    }
}

ResourceServerSecurityConfigurer

public void configure(HttpSecurity http) throws Exception {
    AuthenticationManager oauthAuthenticationManager = this.oauthAuthenticationManager(http);
    //创建OAuth2核心过滤器
    this.resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
    this.resourcesServerFilter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
    //设置OAuth2的身份认证处理器,没有交给spring管理(避免影响非普通的认证流程)
    this.resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
    if (this.eventPublisher != null) {
        this.resourcesServerFilter.setAuthenticationEventPublisher(this.eventPublisher);
    }
    if (this.tokenExtractor != null) {
        //设置TokenExtractor默认的实现BearerTokenExtractor
        this.resourcesServerFilter.setTokenExtractor(this.tokenExtractor);
    }
    this.resourcesServerFilter = (OAuth2AuthenticationProcessingFilter)this.postProcess(this.resourcesServerFilter);
    this.resourcesServerFilter.setStateless(this.stateless);
    // @formatter:off
    ((HttpSecurity)http.authorizeRequests().expressionHandler(this.expressionHandler).and()).addFilterBefore(this.resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class).exceptionHandling().accessDeniedHandler(this.accessDeniedHandler).authenticationEntryPoint(this.authenticationEntryPoint);
}

OAuth2AuthenticationProcessingFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    boolean debug = logger.isDebugEnabled();
    HttpServletRequest request = (HttpServletRequest)req;
    HttpServletResponse response = (HttpServletResponse)res;
    try {
        //从请求中取出身份信息,即access_token,封装到 PreAuthenticatedAuthenticationToken
        Authentication authentication = this.tokenExtractor.extract(request);
        if (authentication == null) {
            .....
        } else {
            request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
            if (authentication instanceof AbstractAuthenticationToken) {
                AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;
                needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));
            }
			//认证身份
            Authentication authResult = this.authenticationManager.authenticate(authentication);
            if (debug) {
                logger.debug("Authentication success: " + authResult);
            }
            this.eventPublisher.publishAuthenticationSuccess(authResult);
            //将身份信息绑定到SecurityContextHolder中
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }
    } catch (OAuth2Exception var9) {
        SecurityContextHolder.clearContext();
        if (debug) {
            logger.debug("Authentication request failed: " + var9);
        }
        this.eventPublisher.publishAuthenticationFailure(new BadCredentialsException(var9.getMessage(), var9), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
        this.authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(var9.getMessage(), var9));
        return;
    }
    chain.doFilter(request, response);
}

整个filter步骤最核心的是下面2个:

1、 调用 tokenExtractor.extract() 方法从请求中解析出token信息并存放到 authentication 的 principal 字段 中

2、 调用 authenticationManager.authenticate() 认证过程: 注意此时的 authenticationManager 是 OAuth2AuthenticationManager

在解析@EnableResourceServer 时我们讲过 OAuth2AuthenticationManager 与 OAuth2AuthenticationProcessingFilter 的关系,这里不再重述,我们直接看下 OAuth2AuthenticationManager 的 authenticate() 方法实现:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    if (authentication == null) {
        throw new InvalidTokenException("Invalid token (token not found)");
    }
    // 1、 从 authentication 中获取 token
    String token = (String) authentication.getPrincipal();
    // 2、 调用 tokenServices.loadAuthentication() 方法  通过 token 参数获取到 OAuth2Authentication 对象 ,这里的tokenServices 就是我们资源服务器配置的。
    OAuth2Authentication auth = tokenServices.loadAuthentication(token);
    if (auth == null) {
        throw new InvalidTokenException("Invalid token: " + token);
    }
    Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
    if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
        throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
    }
    // 3、 检测客户端信息,由于我们采用授权服务器和资源服务器分离的设计,所以这个检测方法实际没有检测
    checkClientDetails(auth);
    if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
        // Guard against a cached copy of the same details
        if (!details.equals(auth.getDetails())) {
            // Preserve the authentication details from the one loaded by token services
            details.setDecodedDetails(auth.getDetails());
        }
    }
    // 4、 设置认证成功标识并返回
    auth.setDetails(authentication.getDetails());
    auth.setAuthenticated(true);
    return auth;
}

整个 认证逻辑分4步:

1、 从 authentication 中获取 token

2、 调用 tokenServices.loadAuthentication() 方法 通过 token 参数获取到 OAuth2Authentication 对象 ,这里的tokenServices 就是我们资源服务器配置的。

3、 检测客户端信息,由于我们采用授权服务器和资源服务器分离的设计,所以这个检测方法实际没有检测

4、 设置认证成功标识并返回 ,注意返回的是 OAuth2Authentication (Authentication 子类)。

到此这篇关于Java中关于OAuth2.0的原理分析的文章就介绍到这了,更多相关OAuth2.0原理 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • javaweb servlet中使用请求转发乱码的实现

    javaweb servlet中使用请求转发乱码的实现

    下面小编就为大家带来一篇javaweb servlet中使用请求转发乱码的实现。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-08-08
  • Spring Security加密和匹配及原理解析

    Spring Security加密和匹配及原理解析

    我们开发时进行密码加密,可用的加密手段有很多,比如对称加密、非对称加密、信息摘要等,本篇文章给大家介绍Spring Security加密和匹配及原理解析,感兴趣的朋友一起看看吧
    2023-10-10
  • java实现学生教师管理系统

    java实现学生教师管理系统

    这篇文章主要为大家详细介绍了java实现学生教师管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-10-10
  • Spring Boot 2.7.6整合redis与低版本的区别

    Spring Boot 2.7.6整合redis与低版本的区别

    这篇文章主要介绍了Spring Boot 2.7.6整合redis与低版本的区别,文中补充介绍了SpringBoot各个版本使用Redis之间的区别实例讲解,需要的朋友可以参考下
    2023-02-02
  • Jackson库中objectMapper的用法

    Jackson库中objectMapper的用法

    这篇文章主要介绍了Jackson库中objectMapper的用法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Java中BufferedReader与Scanner读入的区别详解

    Java中BufferedReader与Scanner读入的区别详解

    这篇文章主要介绍了Java中BufferedReader与Scanner读入的区别详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • IDEA提示:Boolean method ‘xxx‘ is always inverted问题

    IDEA提示:Boolean method ‘xxx‘ is always&nb

    这篇文章主要介绍了IDEA提示:Boolean method ‘xxx‘ is always inverted问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • springBoot下实现java自动创建数据库表

    springBoot下实现java自动创建数据库表

    这篇文章主要介绍了springBoot下实现java自动创建数据库表的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • spring cloud gateway限流常见算法实现

    spring cloud gateway限流常见算法实现

    本文主要介绍了spring cloud gateway限流常见算法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-02-02
  • elasticsearch源码分析index action实现方式

    elasticsearch源码分析index action实现方式

    这篇文章主要为大家介绍了elasticsearch源码分析index action实现方式,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-04-04

最新评论