欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringSecurity JWT基于令牌的無(wú)狀態(tài)認(rèn)證實(shí)現(xiàn)

 更新時(shí)間:2025年04月13日 10:06:23   作者:程序媛學(xué)姐  
Spring Security中實(shí)現(xiàn)基于JWT的無(wú)狀態(tài)認(rèn)證是一種常見(jiàn)的做法,本文就來(lái)介紹一下SpringSecurity JWT基于令牌的無(wú)狀態(tài)認(rèn)證實(shí)現(xiàn),感興趣的可以了解一下

引言

在微服務(wù)架構(gòu)與分布式系統(tǒng)日益普及的今天,傳統(tǒng)的基于會(huì)話(Session)的認(rèn)證方式面臨著諸多挑戰(zhàn)。JSON Web Token(JWT)作為一種基于令牌的認(rèn)證機(jī)制,因其無(wú)狀態(tài)、自包含以及易于跨服務(wù)傳遞的特性,已成為現(xiàn)代應(yīng)用認(rèn)證的優(yōu)選方案。Spring Security作為Java生態(tài)系統(tǒng)中最流行的安全框架,提供了對(duì)JWT的全面支持。本文將深入探討如何在Spring Security中實(shí)現(xiàn)基于JWT的無(wú)狀態(tài)認(rèn)證,包括令牌生成、驗(yàn)證、續(xù)期等核心環(huán)節(jié),幫助開(kāi)發(fā)者構(gòu)建安全、高效的身份認(rèn)證系統(tǒng)。通過(guò)采用JWT認(rèn)證,系統(tǒng)可以更好地支持水平擴(kuò)展、減輕服務(wù)器存儲(chǔ)負(fù)擔(dān),并簡(jiǎn)化跨服務(wù)認(rèn)證的復(fù)雜性。

一、JWT基本原理與結(jié)構(gòu)

JWT(JSON Web Token)是一種開(kāi)放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊且自包含的方式,用于在各方之間安全地傳輸信息。每個(gè)JWT由三部分組成:頭部(Header)、載荷(Payload)和簽名(Signature)。頭部描述令牌類(lèi)型和使用的算法,載荷包含需要傳遞的數(shù)據(jù)(如用戶ID、角色等),簽名則確保令牌的完整性和真實(shí)性。JWT的設(shè)計(jì)理念是服務(wù)端不存儲(chǔ)令牌狀態(tài),而是通過(guò)驗(yàn)證簽名和檢查內(nèi)置的過(guò)期時(shí)間來(lái)判斷令牌有效性,這種方式特別適合分布式系統(tǒng)和微服務(wù)架構(gòu)。

// JWT結(jié)構(gòu)示例
public class JwtStructure {
    
    // 頭部示例(實(shí)際使用時(shí)會(huì)進(jìn)行Base64URL編碼)
    String header = "{\n" +
                    "  \"alg\": \"HS256\",\n" +  // 簽名算法
                    "  \"typ\": \"JWT\"\n" +     // 令牌類(lèi)型
                    "}";
    
    // 載荷示例(實(shí)際使用時(shí)會(huì)進(jìn)行Base64URL編碼)
    String payload = "{\n" +
                     "  \"sub\": \"1234567890\",\n" +  // 主題(通常是用戶ID)
                     "  \"name\": \"John Doe\",\n" +   // 用戶名
                     "  \"admin\": true,\n" +           // 自定義聲明
                     "  \"iat\": 1516239022,\n" +      // 令牌簽發(fā)時(shí)間
                     "  \"exp\": 1516242622\n" +       // 令牌過(guò)期時(shí)間
                     "}";
    
    // 簽名過(guò)程偽代碼
    String signatureAlgorithm = "HMACSHA256";
    String signature = HMACSHA256(
        base64UrlEncode(header) + "." + base64UrlEncode(payload),
        secret
    );
    
    // 最終的JWT形式:Header.Payload.Signature
    String jwt = base64UrlEncode(header) + "." +
                 base64UrlEncode(payload) + "." +
                 signature;
}

二、Spring Security JWT依賴(lài)配置

實(shí)現(xiàn)JWT認(rèn)證首先需要引入相關(guān)依賴(lài)。主要包括Spring Security核心庫(kù)以及處理JWT的庫(kù),如jjwtjava-jwt。此外,還需要添加JSON處理庫(kù)以及Spring Boot相關(guān)依賴(lài)。在Spring Boot項(xiàng)目中,通過(guò)Maven或Gradle可以方便地管理這些依賴(lài)。配置好依賴(lài)后,可以進(jìn)一步設(shè)置JWT的參數(shù),如密鑰、令牌有效期等,這些通常在應(yīng)用的配置文件中定義。

// Maven依賴(lài)配置(pom.xml片段)
public class Dependencies {
    
    String mavenDependencies = 
        "<!-- Spring Boot安全依賴(lài) -->\n" +
        "<dependency>\n" +
        "    <groupId>org.springframework.boot</groupId>\n" +
        "    <artifactId>spring-boot-starter-security</artifactId>\n" +
        "</dependency>\n" +
        "\n" +
        "<!-- Spring Boot Web依賴(lài) -->\n" +
        "<dependency>\n" +
        "    <groupId>org.springframework.boot</groupId>\n" +
        "    <artifactId>spring-boot-starter-web</artifactId>\n" +
        "</dependency>\n" +
        "\n" +
        "<!-- JWT依賴(lài) - JJWT -->\n" +
        "<dependency>\n" +
        "    <groupId>io.jsonwebtoken</groupId>\n" +
        "    <artifactId>jjwt-api</artifactId>\n" +
        "    <version>0.11.5</version>\n" +
        "</dependency>\n" +
        "<dependency>\n" +
        "    <groupId>io.jsonwebtoken</groupId>\n" +
        "    <artifactId>jjwt-impl</artifactId>\n" +
        "    <version>0.11.5</version>\n" +
        "    <scope>runtime</scope>\n" +
        "</dependency>\n" +
        "<dependency>\n" +
        "    <groupId>io.jsonwebtoken</groupId>\n" +
        "    <artifactId>jjwt-jackson</artifactId>\n" +
        "    <version>0.11.5</version>\n" +
        "    <scope>runtime</scope>\n" +
        "</dependency>";
    
    // 應(yīng)用配置文件(application.yml片段)
    String applicationConfig = 
        "jwt:\n" +
        "  secret: mySecretKey123456789012345678901234567890\n" +
        "  expiration: 86400000  # 24小時(shí),單位毫秒\n" +
        "  header: Authorization\n" +
        "  prefix: Bearer ";
}

三、JWT令牌生成與處理

JWT令牌的生成是認(rèn)證流程的核心環(huán)節(jié)。在用戶成功通過(guò)身份驗(yàn)證后,系統(tǒng)需要?jiǎng)?chuàng)建包含用戶身份和權(quán)限信息的JWT,并將其發(fā)送給客戶端。令牌處理服務(wù)負(fù)責(zé)JWT的創(chuàng)建、簽名和驗(yàn)證等操作。通過(guò)合理封裝JWT操作,可以確保令牌的安全性和一致性。在實(shí)際項(xiàng)目中,通常將JWT相關(guān)操作封裝在專(zhuān)門(mén)的服務(wù)類(lèi)中,該類(lèi)負(fù)責(zé)令牌的生成、解析和驗(yàn)證。

@Service
public class JwtTokenProvider {
    
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    @Value("${jwt.expiration}")
    private long jwtExpiration;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    // 生成令牌
    public String generateToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpiration);
        
        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                // 添加用戶角色信息
                .claim("roles", userDetails.getAuthorities().stream()
                        .map(GrantedAuthority::getAuthority)
                        .collect(Collectors.toList()))
                // 可添加額外的自定義聲明
                .claim("additional", "custom value")
                // 使用HS512算法和密鑰簽名JWT
                .signWith(Keys.hmacShaKeyFor(jwtSecret.getBytes()), SignatureAlgorithm.HS512)
                .compact();
    }
    
    // 從令牌中獲取用戶名
    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(Keys.hmacShaKeyFor(jwtSecret.getBytes()))
                .build()
                .parseClaimsJws(token)
                .getBody();
        
        return claims.getSubject();
    }
    
    // 獲取令牌中的所有聲明
    public Claims getAllClaimsFromToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(Keys.hmacShaKeyFor(jwtSecret.getBytes()))
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
    
    // 驗(yàn)證令牌
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                .setSigningKey(Keys.hmacShaKeyFor(jwtSecret.getBytes()))
                .build()
                .parseClaimsJws(token);
            return true;
        } catch (MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            // 捕獲各種JWT異常并記錄日志
            return false;
        }
    }
    
    // 從令牌解析認(rèn)證信息
    public Authentication getAuthentication(String token) {
        String username = getUsernameFromToken(token);
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }
}

四、Spring Security配置與過(guò)濾器實(shí)現(xiàn)

在Spring Security中集成JWT認(rèn)證需要自定義安全配置和過(guò)濾器。首先,需要?jiǎng)?chuàng)建JWT認(rèn)證過(guò)濾器,攔截請(qǐng)求并驗(yàn)證JWT的有效性。其次,配置安全規(guī)則,定義哪些URL需要認(rèn)證,哪些可以匿名訪問(wèn)。最后,禁用會(huì)話管理,因?yàn)镴WT是無(wú)狀態(tài)的,不需要在服務(wù)器端維護(hù)會(huì)話。通過(guò)這些配置,可以將JWT認(rèn)證機(jī)制無(wú)縫集成到Spring Security框架中。

// JWT認(rèn)證過(guò)濾器
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Value("${jwt.header}")
    private String tokenHeader;
    
    @Value("${jwt.prefix}")
    private String tokenPrefix;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        
        try {
            // 從請(qǐng)求中提取JWT
            String jwt = getJwtFromRequest(request);
            
            // 驗(yàn)證JWT是否存在且有效
            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                // 從JWT中獲取用戶認(rèn)證信息
                Authentication authentication = tokenProvider.getAuthentication(jwt);
                
                // 將認(rèn)證信息設(shè)置到Spring Security上下文
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            // 記錄解析JWT時(shí)的異常,但不中斷過(guò)濾器鏈
            logger.error("Could not set user authentication in security context", ex);
        }
        
        // 繼續(xù)執(zhí)行過(guò)濾器鏈
        filterChain.doFilter(request, response);
    }
    
    // 從請(qǐng)求頭中提取JWT
    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader(tokenHeader);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(tokenPrefix)) {
            return bearerToken.substring(tokenPrefix.length());
        }
        return null;
    }
}

// Spring Security配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 禁用CSRF保護(hù),因?yàn)镴WT是無(wú)狀態(tài)的
            .csrf().disable()
            
            // 配置異常處理
            .exceptionHandling()
            .authenticationEntryPoint((request, response, authException) -> {
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("{\"error\":\"Unauthorized\",\"message\":\"" + 
                                         authException.getMessage() + "\"}");
            })
            .and()
            
            // 禁用會(huì)話管理,使用JWT我們不需要會(huì)話
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            
            // 配置請(qǐng)求授權(quán)
            .authorizeRequests()
            // 允許所有人訪問(wèn)登錄和注冊(cè)接口
            .antMatchers("/api/auth/**").permitAll()
            // 允許所有人訪問(wèn)靜態(tài)資源
            .antMatchers("/static/**").permitAll()
            // 所有其他請(qǐng)求需要認(rèn)證
            .anyRequest().authenticated();
        
        // 添加JWT過(guò)濾器
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
    
    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

五、認(rèn)證控制器與登錄流程實(shí)現(xiàn)

為實(shí)現(xiàn)完整的JWT認(rèn)證流程,需要?jiǎng)?chuàng)建認(rèn)證控制器處理登錄請(qǐng)求??刂破鹘邮沼脩魬{據(jù),驗(yàn)證身份后生成JWT令牌并返回給客戶端。此外,還可以實(shí)現(xiàn)刷新令牌、注銷(xiāo)等功能。在前后端分離的架構(gòu)中,控制器通常返回JSON格式的響應(yīng),包含令牌和基本用戶信息。

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @PostMapping("/login")
    public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
        try {
            // 驗(yàn)證用戶憑據(jù)
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(),
                    loginRequest.getPassword()
                )
            );
            
            // 設(shè)置認(rèn)證信息到安全上下文
            SecurityContextHolder.getContext().setAuthentication(authentication);
            
            // 生成JWT令牌
            String jwt = tokenProvider.generateToken(authentication);
            
            // 獲取用戶詳情
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            List<String> roles = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
            
            // 構(gòu)建并返回響應(yīng)
            JwtAuthResponse response = new JwtAuthResponse();
            response.setToken(jwt);
            response.setUsername(userDetails.getUsername());
            response.setRoles(roles);
            
            return ResponseEntity.ok(response);
        } catch (BadCredentialsException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new ErrorResponse("Invalid username or password"));
        }
    }
    
    // 用于刷新令牌的端點(diǎn)
    @PostMapping("/refresh")
    public ResponseEntity<?> refreshToken(@RequestBody TokenRefreshRequest request) {
        // 驗(yàn)證刷新令牌(實(shí)際項(xiàng)目中應(yīng)使用專(zhuān)門(mén)的刷新令牌)
        String requestRefreshToken = request.getRefreshToken();
        
        try {
            // 驗(yàn)證刷新令牌有效性(簡(jiǎn)化示例)
            if (!tokenProvider.validateToken(requestRefreshToken)) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                    .body(new ErrorResponse("Invalid refresh token"));
            }
            
            // 從刷新令牌中獲取用戶信息
            String username = tokenProvider.getUsernameFromToken(requestRefreshToken);
            
            // 創(chuàng)建新的認(rèn)證對(duì)象
            UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities());
            
            // 生成新的訪問(wèn)令牌
            String newAccessToken = tokenProvider.generateToken(authentication);
            
            return ResponseEntity.ok(new JwtAuthResponse(newAccessToken, username, null));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse("Could not refresh token"));
        }
    }
}

// 登錄請(qǐng)求DTO
class LoginRequest {
    private String username;
    private String password;
    
    // getters and setters
}

// JWT認(rèn)證響應(yīng)DTO
class JwtAuthResponse {
    private String token;
    private String type = "Bearer";
    private String username;
    private List<String> roles;
    
    // constructors, getters and setters
}

總結(jié)

基于JWT的無(wú)狀態(tài)認(rèn)證為現(xiàn)代應(yīng)用提供了高效、靈活的安全機(jī)制。通過(guò)Spring Security與JWT的結(jié)合,可以實(shí)現(xiàn)既符合標(biāo)準(zhǔn)又易于維護(hù)的認(rèn)證系統(tǒng)。JWT的無(wú)狀態(tài)特性使其特別適合微服務(wù)和分布式環(huán)境,無(wú)需在服務(wù)器端存儲(chǔ)會(huì)話狀態(tài),大大減輕了服務(wù)器負(fù)擔(dān),同時(shí)支持系統(tǒng)的水平擴(kuò)展。在實(shí)現(xiàn)過(guò)程中,關(guān)鍵環(huán)節(jié)包括JWT令牌的生成與驗(yàn)證、安全過(guò)濾器的配置、認(rèn)證流程的設(shè)計(jì)等。通過(guò)本文介紹的實(shí)現(xiàn)方法,開(kāi)發(fā)者可以構(gòu)建安全可靠的JWT認(rèn)證系統(tǒng),滿足現(xiàn)代應(yīng)用的認(rèn)證需求。需要注意的是,雖然JWT提供了許多優(yōu)勢(shì),但也存在一些局限,如令牌撤銷(xiāo)困難、令牌大小限制等。在實(shí)際項(xiàng)目中,應(yīng)根據(jù)具體需求選擇適合的認(rèn)證機(jī)制,并遵循安全最佳實(shí)踐,確保系統(tǒng)的安全性和可靠性。隨著應(yīng)用架構(gòu)的不斷演進(jìn),基于令牌的無(wú)狀態(tài)認(rèn)證將繼續(xù)發(fā)揮重要作用,成為構(gòu)建安全分布式系統(tǒng)的基礎(chǔ)。

到此這篇關(guān)于SpringSecurity JWT基于令牌的無(wú)狀態(tài)認(rèn)證實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity JWT令牌無(wú)狀態(tài)認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • spring cloud 使用Eureka 進(jìn)行服務(wù)治理方法

    spring cloud 使用Eureka 進(jìn)行服務(wù)治理方法

    這篇文章主要介紹了spring cloud 使用Eureka 進(jìn)行服務(wù)治理方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • JSP頁(yè)面無(wú)法識(shí)別EL表達(dá)式問(wèn)題解決方案

    JSP頁(yè)面無(wú)法識(shí)別EL表達(dá)式問(wèn)題解決方案

    這篇文章主要介紹了JSP頁(yè)面無(wú)法識(shí)別EL表達(dá)式問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • springboot中引入日志文件生成的配置詳解

    springboot中引入日志文件生成的配置詳解

    本文主要介紹了springboot中引入日志文件生成的配置詳解,包括日志級(jí)別的設(shè)置、日志格式的配置以及日志輸出的位置等,從而幫助開(kāi)發(fā)者更好地進(jìn)行開(kāi)發(fā)與調(diào)試
    2023-10-10
  • Java中Lambda表達(dá)式之Lambda語(yǔ)法與作用域解析

    Java中Lambda表達(dá)式之Lambda語(yǔ)法與作用域解析

    這篇文章主要介紹了Java中Lambda表達(dá)式之Lambda語(yǔ)法與作用域解析重點(diǎn)介紹Lambda表達(dá)式基礎(chǔ)知識(shí),需要的朋友可以參考下
    2017-02-02
  • Java中常見(jiàn)的5種WEB服務(wù)器介紹

    Java中常見(jiàn)的5種WEB服務(wù)器介紹

    這篇文章主要介紹了Java中常見(jiàn)的5種WEB服務(wù)器介紹,它們分別是Tomcat、Resin、JBoss、WebSphere、WebLogic,需要的朋友可以參考下
    2014-07-07
  • 解讀Mapper與Mapper.xml文件之間匹配的問(wèn)題

    解讀Mapper與Mapper.xml文件之間匹配的問(wèn)題

    這篇文章主要介紹了解讀Mapper與Mapper.xml文件之間匹配的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • SpringBoot定義Bean的幾種實(shí)現(xiàn)方式

    SpringBoot定義Bean的幾種實(shí)現(xiàn)方式

    本文主要介紹了SpringBoot定義Bean的幾種實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • 如何在springboot中配置和使用mybatis-plus

    如何在springboot中配置和使用mybatis-plus

    這篇文章主要給大家介紹了關(guān)于如何在springboot中配置和使用mybatis-plus的相關(guān)資料,MyBatis?Plus是MyBatis的增強(qiáng)版,旨在提供更多便捷的特性,減少開(kāi)發(fā)工作,同時(shí)保留了MyBatis的靈活性和強(qiáng)大性能,需要的朋友可以參考下
    2023-11-11
  • Spark Streaming編程初級(jí)實(shí)踐詳解

    Spark Streaming編程初級(jí)實(shí)踐詳解

    這篇文章主要為大家介紹了Spark Streaming編程初級(jí)實(shí)踐詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • mall整合SpringSecurity及JWT實(shí)現(xiàn)認(rèn)證授權(quán)實(shí)戰(zhàn)

    mall整合SpringSecurity及JWT實(shí)現(xiàn)認(rèn)證授權(quán)實(shí)戰(zhàn)

    這篇文章主要為大家介紹了mall整合SpringSecurity及JWT實(shí)現(xiàn)認(rèn)證授權(quán)實(shí)戰(zhàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06

最新評(píng)論