首页 文章 精选 留言 我的

精选列表

搜索[网站开发],共10000篇文章
优秀的个人博客,低调大师

维基框架 (Wiki Framework) v1.1.2 | 企业级微服务开发框架

Release Notes 版本修复日志 【修复】修复HTTPS请求参数ContentType创建错误问题; 【修复】修复用户接口类 IUserDetailsService 被删除问题; 【修复】修复Spring Boot 全局响应处理增加对返回字符串兼容; 【修复】修复代码生成模板变量命名错误问题、模板引我错误问题; 【修复】修复框架Mybatis XML文件 mapper指引地址错误问题; 【修复】修复调整MinioUtils内部方法全部为静态; 版本新增日志 【新增】修改协议、删除MySQL Connector协议引用; 【新增】新增MySQL test使用; 【新增】新增Nacos及Nacos-Cloud配置中心的支持; 获取资源与了解更多: 官网:https://framewiki.com/wiki-framework.html Gitee:https://gitee.com/cdkjframework/wiki-framework Github:https://github.com/cdkjframework/wiki-framework 示例项目:https://gitee.com/cdkjframework/framewiki-example 使用许可:Wiki Framework 采用木兰宽松许可证 (MulanPSL-2.0)。

优秀的个人博客,低调大师

多位诺贝尔奖得主与开发者呼吁各国为 AI 设定

10位诺贝尔奖得主和两位前国家元首近日联合呼吁,各国应在2026年底前,对人工智能(AI)发展设定“红线”,以推动国际层面的风险管控。这份声明由超过200位签署者联合发布,涵盖OpenAI、Google DeepMind与Anthropic的高级员工。 https://red-lines.ai/?tl=chinese#call 声明强调,AI可能引发的工程性大流行、失业风险等问题已引起全球广泛关注,诸多专家警告未来几年或将难以对AI系统实施有效的人类控制。 本次呼吁恰逢联合国大会召开,在推动全球共识方面意义重大,但因美国反对,预计短期内难以推动具体的治理措施。签署者包括AI领域知名学者Hinton、Bengio、“经济学家”斯蒂格利茨、哥伦比亚前总统桑托斯、爱尔兰前总统玛丽·罗宾逊、意大利前总理莱塔,以及多位前政府部长、科学家与外交官。 值得一提的是,演员Stephen Fry也在名单之列。OpenAI联合创始人Wojciech Zaremba、DeepMind首席科学家Ian Goodfellow等科技公司高管亦参与签署,但未见三家公司的CEO加入。 声明指出,部分先进AI系统已经展现出欺骗性和有害行为,但这些系统却在获得更多自主权,能够在现实世界中采取行动并做出决策。因此,制定明确定义且可验证的“红线”国际协议,成为防范不可接受风险的必要措施。签名者希望这些红线能在2026年底前付诸实施,并配套执行机制。 虽然声明未具体列举AI治理红线内容,但去年另一份文件曾建议禁止AI自主复制、追求权力、自动发起网络攻击以及“沙包行为”。该提案同样得到中国多位科学家支持,如百度前总裁张亚勤、北京人工智能学会理事长黄铁军。 此外,联合国近期已宣布将设立国际AI科学咨询委员会(类似气候变化政府间专门委员会IPCC)及全球AI治理对话机制,为全球治理AI迈出重要一步。但由于美国政府态度消极,相关行动推进仍面临阻力。根据特朗普政府今年7月发布的《美国AI行动计划》,美方虽表态支持与志同道合国家合作发展AI,但明确反对“过度监管”、文化议程为主的模糊行为规则,及中国企业参与的治理方案。本月早些时候,美国参议员Ted Cruz表示,国会制定AI监管“支柱”之一就是“对抗过度的外国监管”。 尽管如此,声明显示全球对于AI潜力和风险的关注持续升温。联合国大会前主席科罗西在评论中指出:“人类历史上从未遇到比自身更高的智能,而在未来几年,人类即将迎来这样的挑战。”

优秀的个人博客,低调大师

维基框架 (Wiki FW) v1.1.1 | 企业级微服务开发框架

Release Notes 版本修复日志 【修复】修复wiki-all-jpa包命名空间错误问题 【修复】修改日志输出目录至项目根目录 【修复】wiki-all,wiki-all-jpa 启动类型注解配置 新增 wiki-oauth2 组件,支持OAuth2授权 1.【安全】OAuth2权限体系强化 新增标准OAuth2授权码(authorization_code)获取流程,支持/oauth2/authorize端点动态生成权限code 扩展令牌端点/oauth2/access_token,支持通过授权码交换访问令牌(access_token) 新增令牌刷新机制,支持grant_type=refresh_token动态刷新访问凭证 集成RBAC权限模型,实现细粒度Scope权限控制 2.【优化】Spring Security深度整合 重构认证过滤器链,支持OAuth2与原生表单登录无缝切换 新增@OAuth2ResourceServer注解,一键开启资源服务器配置 优化JWT令牌校验性能,支持HS256/RS256双签名算法 3.【安全】令牌管理增强 令牌存储支持数据库模式,持久化访问凭证 新增令牌自动回收机制,闲置令牌15分钟强制失效 令牌绑定客户端IP,防止凭证劫持(CVE-2025-8821修复) 4.【优化】配置简化 spring: custom: oauth2: # 刷新令牌有效期,单位秒,默认30天(默认值) refresh-token-time-to-live: 2592000 # 访问令牌有效期,单位秒,默认7天(默认值) access-token-time-to-live: 604800 # 需要授权接口 api-path-patterns: - /api/** # 需要放行接口 allow-list: - /oauth2/** OAuth2 接入示例 1. 引入依赖 <dependencies> <groupId>com.framewiki</groupId> <artifactId>wiki-oauth2</artifactId> <version>1.1.1version> </dependencies> 2. 请求示例 1. 获取授权码 GET /oauth2/authorize?response_type=code &client_id=client123 &scope=read 2. 兑换访问令牌 GET /oauth2/access_token grant_type=authorization_code &code=MmUuMrt &client_id=client123 ×tamp=1756625626 &signature=817a2bcb5720fec967ba936fdfc64fc5 3. 刷新访问令牌 GET /oauth2/access_token grant_type=refresh_token &refresh_token=eyJhbGciOiJIUzUxMiJ9.eyJkaXNwbGF5TmFtZSI6ImNsaWVudDEyMyIsImxvZ2luTmFtZSI6ImNsaWVudDEyMyIsInRpbWUiOjE3NTY3MTIyNTAsInVzZXJUeXBlIjoiY2xpZW50MTIzIiwiZXhwIjoxNzU2NzEyMjUwLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsInVzZXJuYW1lIjoiY2xpZW50MTIzIiwidG9rZW4iOiIzNjc3YjIzYmFhMDhmNzRjMjhhYmEwN2YwY2I2NTU0ZSJ9.GGeatgfoGdG56HV9fvgaONY4kJKso5M4crF9KHOV_zYq0U5aM7BT6tyfgnni7t0FG6o8wSLpkmTFcwLJc2g9Fw 3. 实现代码示例 1. 常量 package com.cdkjframework.oauth2.constant; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.constant * @ClassName: OAuth2Constant * @Description: 常量 * @Author: xiaLin * @Date: 2025/7/31 16:51 * @Version: 1.0 */ public interface OAuth2Constant { /** * 授权端点 * 注意:不以双斜杠结尾,避免路径拼接出现 */ String OAUTH2 = "/oauth2/"; /** * 授权类型 - 刷新令牌 */ String REFRESH_TOKEN = "refresh_token"; /** * 授权端点 */ String AUTHORIZE = OAUTH2 + "authorize"; /** * 访问令牌端点 */ String OAUTH2_ACCESS_TOKEN = OAUTH2 + "token"; /** * 撤销令牌端点 */ String OAUTH2_REVOKE = OAUTH2 + "revoke"; /** * 密钥 */ String SECRET_KEY = "cdkj-framework-jwt"; /** * 权限 */ String AUTHORIZATION = "Authorization"; /** * 权限值 */ String BEARER = "Bearer "; /** * 授权类型 */ String CLIENT_ID = "client_id"; /** * 空 */ String EMPTY = ""; /** * 授权码 */ String ACCESS_TOKEN = "access_token"; /** * 过期时间 */ String EXPIRES_IN = "expires_in"; /** * 授权类型 */ String TOKEN_TYPE = "token_type"; /** * 令牌时间 */ String ACCESS_TOKEN_TIME_TO_LIVE = "accessTokenTimeToLive"; /** * 刷新令牌时间 */ String REFRESH_TOKEN_TIME_TO_LIVE = "refreshTokenTimeToLive"; /** * 授权编码错误 */ Integer CODE_ERROR = 10000; /** * 授权编码过期 */ Integer CODE_EXPIRED = 10001; /** * client_id 错误 */ Integer CLIENT_ERROR = 10002; /** * 密钥错误 */ Integer SECRET_ERROR = 10003; /** * 刷新令牌错误 */ Integer REFRESH_TOKEN_ERROR = 10004; /** * 刷新令牌过期 */ Integer REFRESH_TOKEN_EXPIRED = 10005; /** * 授权类型未找到 */ Integer GRANT_TYPE = 10006; /** * 授权类型错误 */ Integer GRANT_TYPE_ERROR = 10007; /** * 时间戳错误 */ Integer TIMESTAMP_ERROR = 10008; /** * 签名错误 */ Integer SIGNATURE_ERROR = 10009; /** * 系统错误 */ Integer ERROR = 999; } 2. 授权服务器配置 package com.cdkjframework.oauth2.config; import com.cdkjframework.oauth2.filter.JwtTokenFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import java.util.ArrayList; import java.util.List; import static com.cdkjframework.oauth2.constant.OAuth2Constant.*; /** * @ProjectName: wiki-oauth2 * @Package:com.cdkjframework.oauth2.config * @ClassName: AuthorizationServerConfig * @Description: 授权服务器配置 * @Author: xiaLin * @Date: 2025/7/31 13:32 * @Version: 1.0 */ @Configuration @EnableWebSecurity @RequiredArgsConstructor public class AuthorizationServerConfig { /** * 凭证过滤 */ private final JwtTokenFilter jwtTokenFilter; /** * OAuth2 配置 */ private final Oauth2Config oauth2Config; /** * 定义安全策略 * * @param http http安全 * @return 安全过滤链 * @throws Exception 异常信息 */ @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { List<String> publicEndpoints = new ArrayList<>() { { add(AUTHORIZE); add(OAUTH2_ACCESS_TOKEN); } }; publicEndpoints.addAll(oauth2Config.getAllowList()); List<String> finalPathArray = oauth2Config.getApiPathPatterns(); http // 配置授权规则 .authorizeHttpRequests(authorizeRequests -> authorizeRequests // 公开的 OAuth2 端点 .requestMatchers(publicEndpoints.toArray(String[]::new)).permitAll() // 权限页面 .requestMatchers(finalPathArray.toArray(String[]::new)).authenticated() // 其他请求需要认证 .anyRequest().authenticated()) // 在 UsernamePasswordAuthenticationFilter 前添加 JWT 过滤器 .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class) // 禁用 Session .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 可选:禁用 CSRF 防护(针对无状态认证,如 JWT) .csrf(csrf -> csrf.disable()); return http.build(); } /** * 客户端详情存储库 */ @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() .authorizationEndpoint(AUTHORIZE) .tokenEndpoint(OAUTH2_ACCESS_TOKEN) .tokenRevocationEndpoint(OAUTH2_REVOKE) .build(); } } 3. JWT 令牌过滤器 package com.cdkjframework.oauth2.filter; import com.cdkjframework.oauth2.constant.OAuth2Constant; import com.cdkjframework.oauth2.entity.ClientDetails; import com.cdkjframework.oauth2.provider.JwtTokenProvider; import com.cdkjframework.oauth2.repository.OAuth2ClientRepository; import com.cdkjframework.util.tool.StringUtils; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.filter * @ClassName: JwtTokenFilter * @Description: JWT 令牌过滤器 * @Author: xiaLin * @Date: 2025/7/31 16:48 * @Version: 1.0 */ @Component @RequiredArgsConstructor public class JwtTokenFilter extends OncePerRequestFilter { /** * 使用自定义的 RegisteredClientRepository 进行客户端与权限信息的加载 */ private final RegisteredClientRepository registeredClientRepository; /** * 是否进行内部筛选 * * @param request 请求 * @param response 响应 * @param filterChain 过滤器链 * @throws ServletException Servlet异常 * @throws IOException IO异常 */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader(OAuth2Constant.AUTHORIZATION); if (StringUtils.isNotNullAndEmpty(token) && token.startsWith(OAuth2Constant.BEARER)) { try { String jwt = token.replace(OAuth2Constant.BEARER, OAuth2Constant.EMPTY); // Validate and parse the JWT token JwtTokenProvider.validateToken(jwt); String clientId = JwtTokenProvider.getClientIdFromToken(jwt); // 通过自定义仓库加载 RegisteredClient RegisteredClient registeredClient = registeredClientRepository.findByClientId(clientId); if (registeredClient == null) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json"); response.getWriter().write("{\\\\"error\\\\":\\\\"Client Not Found\\\\",\\\\"clientId\\\\":\\\\"" + clientId + "\\\\"}"); return; } // 从 RegisteredClient 构造权限集合(Scopes -> SCOPE_xxx,GrantTypes -> GRANT_xxx) List<GrantedAuthority> authorities = parseAuthorities(registeredClient); // 基于 HTTP 方法的简单权限校验:GET/HEAD/OPTIONS 需要 SCOPE_read,其它需要 SCOPE_write if (!checkHttpMethodPermission(request.getMethod(), authorities)) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType("application/json"); response.getWriter().write("{\\\\"error\\\\":\\\\"Forbidden\\\\",\\\\"message\\\\":\\\\"insufficient_scope\\\\"}"); return; } request.setAttribute(OAuth2Constant.CLIENT_ID, clientId); // 设置认证信息到 SecurityContext SecurityContextHolder.getContext().setAuthentication( new UsernamePasswordAuthenticationToken(clientId, registeredClient.getClientSecret(), authorities) ); } catch (Exception e) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json"); response.getWriter().write("{\\\\"error\\\\": \\\\"Invalid Token\\\\", \\\\"message\\\\": \\\\"" + e.getMessage() + "\\\\"}"); return; } } // Proceed with the filter chain filterChain.doFilter(request, response); } /** * 将 RegisteredClient 的 scopes 与授权类型转换为权限集合 * - Scopes: 生成形如 SCOPE_xxx 的权限 * - GrantTypes: 生成形如 GRANT_xxx 的权限 */ private List<GrantedAuthority> parseAuthorities(RegisteredClient client) { List<GrantedAuthority> list = new ArrayList<>(); // scopes -> SCOPE_* client.getScopes().forEach(scope -> list.add(new SimpleGrantedAuthority("SCOPE_" + scope))); // grant types -> GRANT_* client.getAuthorizationGrantTypes().stream() .map(AuthorizationGrantType::getValue) .forEach(gt -> list.add(new SimpleGrantedAuthority("GRANT_" + gt))); return list; } /** * 基于 HTTP 方法的通用权限校验 */ private boolean checkHttpMethodPermission(String method, List<GrantedAuthority> authorities) { String m = method == null ? "GET" : method.toUpperCase(Locale.ROOT); boolean isRead = m.equals("GET") || m.equals("HEAD") || m.equals("OPTIONS"); String required = isRead ? "SCOPE_read" : "SCOPE_write"; return authorities.stream().anyMatch(a -> a.getAuthority().equals(required)); } } 4. JWT 令牌提供者 package com.cdkjframework.oauth2.provider; import com.cdkjframework.constant.BusinessConsts; import com.cdkjframework.constant.IntegerConsts; import com.cdkjframework.oauth2.constant.OAuth2Constant; import com.cdkjframework.util.encrypts.JwtUtils; import com.cdkjframework.util.encrypts.Md5Utils; import com.cdkjframework.util.tool.number.ConvertUtils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import static com.cdkjframework.oauth2.constant.OAuth2Constant.TOKEN_TYPE; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.service * @ClassName: JwtTokenProvider * @Description: JWT 令牌提供者 * @Author: xiaLin * @Date: 2025/7/31 13:30 * @Version: 1.0 */ @Component public class JwtTokenProvider { /** * 生成 JWT Token * * @param clientId 客户端ID * @param accessTokenTimeToLive 访问令牌存活时间(秒) * @return 返回 JWT Token */ public static String generateToken(String clientId, Long accessTokenTimeToLive) { LocalDateTime now = LocalDateTime.now(); // 1 天有效期 LocalDateTime expiryDate = now.plusSeconds(accessTokenTimeToLive); Instant time = expiryDate.atZone(ZoneId.systemDefault()).toInstant(); Long seconds = time.getEpochSecond() * IntegerConsts.ONE_THOUSAND; return JwtUtils.createJwt(parseToken(clientId, time.getEpochSecond()), OAuth2Constant.SECRET_KEY, seconds); } /** * 生成 Refresh Token * * @param clientId 客户端ID * @param refreshTokenTimeToLive 刷新令牌存活时间(秒) * @return 返回 Refresh Token */ public static String generateRefreshToken(String clientId, Long refreshTokenTimeToLive) { Instant time = Instant.now().plus(refreshTokenTimeToLive, ChronoUnit.SECONDS); // 构建包含客户端标识和唯一性的JWT return Jwts.builder() // JWT唯一标识 .setId(UUID.randomUUID().toString()) // 绑定客户端ID[1,8](@ref) .setSubject(clientId) // 签发时间 .setIssuedAt(Date.from(Instant.now())) .setClaims(parseToken(clientId, time.getEpochSecond())) // 30 天有效期[4](@ref) .setExpiration(Date.from(time)) // 明确令牌类型 .claim(TOKEN_TYPE, "refresh") // 强加密签名[2](@ref) .signWith(SignatureAlgorithm.HS512, OAuth2Constant.SECRET_KEY) .compact(); } /** * 解析 Token * * @param clientId 客户端ID * @param time 时间 * @return 返回 解析后的 Token 信息 */ private static Map<String, Object> parseToken(String clientId, Long time) { // 生成 JWT token Map<String, Object> map = new HashMap<>(IntegerConsts.FOUR); map.put(BusinessConsts.LOGIN_NAME, clientId); map.put(BusinessConsts.TIME, time); map.put(BusinessConsts.USER_NAME, clientId); map.put(BusinessConsts.USER_TYPE, clientId); map.put(BusinessConsts.DISPLAY_NAME, clientId); String token = Md5Utils.getMd5(clientId); map.put(BusinessConsts.HEADER_TOKEN, token); return map; } /** * 解析 Token 获取用户名(clientId) * * @param token 令牌 * @return 返回 用户名(clientId) */ public static String getClientIdFromToken(String token) { Claims claims = JwtUtils.parseJwt(token, OAuth2Constant.SECRET_KEY); if (claims == null) { return null; } return ConvertUtils.convertString(claims.get(BusinessConsts.LOGIN_NAME)); } /** * 验证 Token 是否有效 * * @param token 令牌 * @return 返回是否有效 */ public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(OAuth2Constant.SECRET_KEY).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } /** * 生成 MD5 签名 * * @param clientSecret 客户端密钥 * @param clientId 客户端ID * @param timestamp 时间戳 * @return 返回 MD5 签名 */ public static String md5Signature(String clientSecret, String clientId, String timestamp) { String input = "client_id=" + clientId + "&client_secret=" + clientSecret + "×tamp=" + timestamp; return Md5Utils.getMd5(input); } } 5. OAuth2授权服务接口 package com.cdkjframework.oauth2.service; import com.cdkjframework.builder.String; import com.cdkjframework.oauth2.entity.TokenResponse; /** * OAuth2授权服务接口 * * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.service * @ClassName: Oauth2AuthorizationService * @Description: OAuth2授权服务接口 * @Author: xiaLin * @Date: 2025/7/31 21:15 * @Version: 1.0 */ public interface Oauth2AuthorizationService { /** * 授权端点 * * @param clientId 客户端ID * @param responseType 响应类型 * @param scope 授权范围 * @return 授权页面或信息 */ ResponseBuilder authorizationCode(String clientId, String responseType, String scope); /** * 获取访问令牌 * * @param grantType 授权类型 * @param clientId 客户端ID * @param code 授权码 * @param timestamp 时间戳 * @param refreshToken 刷新令牌 * @param signature 签名 * @return */ TokenResponse token(String grantType, String clientId, String code, String timestamp, String refreshToken, String signature); } 6. OAuth2授权服务实现类 package com.cdkjframework.oauth2.service.impl; import com.cdkjframework.builder.ResponseBuilder; import com.cdkjframework.constant.IntegerConsts; import com.cdkjframework.exceptions.GlobalRuntimeException; import com.cdkjframework.oauth2.config.Oauth2Config; import com.cdkjframework.oauth2.entity.AuthorizationCode; import com.cdkjframework.oauth2.entity.OAuth2Token; import com.cdkjframework.oauth2.entity.TokenResponse; import com.cdkjframework.oauth2.provider.JwtTokenProvider; import com.cdkjframework.oauth2.repository.AuthorizationCodeRepository; import com.cdkjframework.oauth2.repository.CustomRegisteredClientRepository; import com.cdkjframework.oauth2.repository.OAuth2TokenRepository; import com.cdkjframework.oauth2.service.Oauth2AuthorizationService; import com.cdkjframework.oauth2.service.Oauth2TokenService; import com.cdkjframework.util.network.http.HttpServletUtils; import com.cdkjframework.util.tool.CollectUtils; import com.cdkjframework.util.tool.StringUtils; import com.cdkjframework.util.tool.number.ConvertUtils; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; import java.security.SecureRandom; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.HashMap; import java.util.Map; import static com.cdkjframework.oauth2.constant.OAuth2Constant.*; /** * OAuth2授权服务实现类 * * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.service.impl * @ClassName: Oauth2AuthorizationServiceImpl * @Description: OAuth2授权服务实现类 * @Author: xiaLin * @Date: 2025/7/31 21:16 * @Version: 1.0 */ @Service @RequiredArgsConstructor public class Oauth2AuthorizationServiceImpl implements Oauth2AuthorizationService { /** * 自定义注册的客户端存储库 */ private final CustomRegisteredClientRepository registeredClientRepository; /** * 授权码存储库 */ private final AuthorizationCodeRepository authorizationCodeRepository; /** * OAuth2客户端存储库 */ private final OAuth2TokenRepository auth2TokenRepository; /** * oauth2令牌服务 */ private final Oauth2TokenService oauth2TokenService; /** * Oauth2配置 */ private final Oauth2Config oauth2Config; /** * 安全字符集(排除易混淆字符) */ private static final String SAFE_CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnopqrstuvwxyz23456789"; /** * 授权端点 * * @param clientId 客户端ID * @param responseType 响应类型 * @param scope 授权范围 * @return code */ @Override public ResponseBuilder authorizationCode(String clientId, String responseType, String scope) { HttpServletResponse response = HttpServletUtils.getResponse(); // 验证客户端 RegisteredClient client = registeredClientRepository.findByClientId(clientId); if (client == null) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(CODE_ERROR, "Invalid client ID"); } // 生成授权编码 String authCode = generate(IntegerConsts.SEVEN); AuthorizationCode code = new AuthorizationCode(); code.setCode(authCode); code.setClientId(clientId); code.setRedirectUri(client.getRedirectUris().stream().findFirst().orElse(null)); code.setIssuedAt(LocalDateTime.now()); // 设置过期时间为10分钟后 code.setExpiryAt(code.getIssuedAt().plusMinutes(IntegerConsts.TEN)); // 检查授权码是否已存在 authorizationCodeRepository.save(code); // 返回授权码 return ResponseBuilder.successBuilder(authCode); } /** * 获取访问令牌 * * @param grantType 授权类型 * @param clientId 客户端ID * @param code 授权码 * @param timestamp 时间戳 * @param refreshToken 刷新令牌 * @param signature 签名 * @return 访问令牌 */ @Override public TokenResponse token(String grantType, String clientId, String code, String timestamp, String refreshToken, String signature) { HttpServletResponse response = HttpServletUtils.getResponse(); // 2. 校验客户端 ID 和客户端密钥 if (StringUtils.isNullAndSpaceOrEmpty(grantType)) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(GRANT_TYPE, "grant_type not found"); } // 3. 根据授权类型处理请求 AuthorizationGrantType authorizationGrantType = new AuthorizationGrantType(grantType.toLowerCase()); if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) { // 处理授权码模式 return handleAuthorizationCode(response, authorizationGrantType, clientId, code, timestamp, signature); } else if (AuthorizationGrantType.REFRESH_TOKEN.equals(authorizationGrantType)) { // 处理刷新令牌模式 return handleRefreshToken(response, authorizationGrantType, refreshToken); } else { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(GRANT_TYPE, "Unsupported grant_type"); } } /** * 处理授权码模式 * * @param response HTTP响应对象 * @param grantType 授权类型 * @param clientId 客户端ID * @param code 授权码 * @param timestamp 时间戳 * @param signature 签名 * @return 响应构建器 */ private TokenResponse handleAuthorizationCode(HttpServletResponse response, AuthorizationGrantType grantType, String clientId, String code, String timestamp, String signature) { // 验证时间戳 try { verifyTimestamp(timestamp); } catch (IllegalArgumentException e) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(TIMESTAMP_ERROR, e.getMessage()); } // 1. 验证授权码是否有效 AuthorizationCode authorizationCode = authorizationCodeRepository.findByCode(code); if (ObjectUtils.isEmpty(authorizationCode)) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(CODE_ERROR, "Invalid authorization code"); } if (authorizationCode.isExpired()) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(CODE_EXPIRED, "expired authorization code"); } // 2. 根据 clientId 查找注册的客户端 RegisteredClient client = registeredClientRepository.findByClientId(clientId); if (ObjectUtils.isEmpty(client)) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(CLIENT_ERROR, "client_id not found"); } // 3. 验证签名 try { verifySignature(client, signature, clientId, timestamp); } catch (IllegalArgumentException e) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(SIGNATURE_ERROR, e.getMessage()); } // 4. 验证授权类型是否被允许 if (!client.getAuthorizationGrantTypes().contains(grantType)) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(GRANT_TYPE_ERROR, "Invalid grant_type"); } // 7. 将生成的令牌存储到数据库中 OAuth2Token oauth2Token = new OAuth2Token(); oauth2Token.setUserId(code); // 8. 构建授权 return buildToken(client, oauth2Token); } /** * 刷新访问令牌 * * @param refreshToken 刷新令牌 * @return 刷新后的访问令牌 */ public TokenResponse handleRefreshToken(HttpServletResponse response, AuthorizationGrantType grantType, String refreshToken) { if (StringUtils.isNullAndSpaceOrEmpty(refreshToken)) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(REFRESH_TOKEN_ERROR, "Invalid refresh token"); } // 1. 验证刷新令牌是否有效 OAuth2Token oauth2Token = auth2TokenRepository.findByRefreshToken(refreshToken); if (ObjectUtils.isEmpty(oauth2Token) || StringUtils.isNullAndSpaceOrEmpty(oauth2Token.getRefreshToken())) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(REFRESH_TOKEN_ERROR, "Invalid refresh token"); } int status = ConvertUtils.convertInt(oauth2Token.getStatus()); if (!IntegerConsts.ONE.equals(status)) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(REFRESH_TOKEN_ERROR, "Invalid refresh token"); } // 2. 检查刷新令牌是否过期 if (oauth2Token.isExpired()) { response.setStatus(HttpStatus.BAD_REQUEST.value()); throw new GlobalRuntimeException(REFRESH_TOKEN_EXPIRED, "Refresh token has expired"); } // 2. 根据 clientId 查找注册的客户端 RegisteredClient client = registeredClientRepository.findByClientId(oauth2Token.getClientId()); assert client != null; // 3. 构建授权 return buildToken(client, oauth2Token); } /** * 构建并返回新的访问令牌和刷新令牌 * * @param client 注册的客户端 * @param oauth2Token 当前的 OAuth2 令牌实体 * @return 响应构建器,包含新的访问令牌和刷新令牌 */ private TokenResponse buildToken(RegisteredClient client, OAuth2Token oauth2Token) { // 1. 获取令牌设置 TokenSettings settings = client.getTokenSettings(); Long accessTokenTimeToLive, refreshTokenTimeToLive; if (settings != null && CollectUtils.isNotEmpty(settings.getSettings())) { Map<String, Object> map = settings.getSettings(); accessTokenTimeToLive = ConvertUtils.convertLong(map.get(ACCESS_TOKEN_TIME_TO_LIVE)); refreshTokenTimeToLive = ConvertUtils.convertLong(map.get(REFRESH_TOKEN_TIME_TO_LIVE)); } else { accessTokenTimeToLive = oauth2Config.getAccessTokenTimeToLive(); refreshTokenTimeToLive = oauth2Config.getRefreshTokenTimeToLive(); } // 2. 成新的访问令牌 String newAccessToken = JwtTokenProvider.generateToken(client.getClientId(), accessTokenTimeToLive); oauth2TokenService.validateToken(newAccessToken); // 3. 生成新的刷新令牌(可选) String newRefreshToken = JwtTokenProvider.generateRefreshToken(client.getClientId(), refreshTokenTimeToLive); LocalDateTime localDateTime = LocalDateTime.now(); // 4. 更新数据库中的刷新令牌(这里我们选择创建一个新刷新令牌) oauth2Token.setAccessToken(newAccessToken); oauth2Token.setRefreshToken(newRefreshToken); oauth2Token.setIssuedAt(localDateTime); oauth2Token.setClientId(client.getClientId()); // 5. 设置访问令牌过期时间为 7 天 oauth2Token.setExpiration(localDateTime.plusDays(IntegerConsts.SEVEN)); auth2TokenRepository.save(oauth2Token); // 6. 返回令牌信息 TokenResponse tokenResponse = new TokenResponse(); tokenResponse.setAccessToken(newAccessToken); tokenResponse.setRefreshToken(newRefreshToken); tokenResponse.setExpiresIn(accessTokenTimeToLive); // 返回新的访问令牌 return tokenResponse; } /** * 验证时间戳 * * @param timestamp 时间戳 */ private void verifyTimestamp(String timestamp) { LocalDateTime currentTime = LocalDateTime.now(); // 这里假设 timestamp 是一个表示毫秒的字符串 long requestTimeMillis; try { requestTimeMillis = Long.parseLong(timestamp); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid timestamp format"); } // LocalDateTime requestTime = LocalDateTime.ofEpochSecond(requestTimeMillis, IntegerConsts.ZERO, java.time.ZoneOffset.UTC); LocalDateTime beijingTime = requestTime.atZone(ZoneId.of("UTC")).withZoneSameInstant(ZoneId.of("Asia/Shanghai")) .toLocalDateTime(); // 允许的时间窗口(例如5分钟) long allowedWindowMillis = IntegerConsts.FIVE * IntegerConsts.SIXTY * IntegerConsts.ONE_THOUSAND; long timeDifference = Math.abs(java.time.Duration.between(currentTime, beijingTime).toMillis()); if (timeDifference > allowedWindowMillis) { throw new IllegalArgumentException("Timestamp is out of the allowed range"); } } /** * 验证签名 md5 签名 * * @param client 注册的客户端 * @param signature 签名 * @param clientId 客户端ID * @param timestamp 时间戳 */ private void verifySignature(RegisteredClient client, String signature, String clientId, String timestamp) { String sign = JwtTokenProvider.md5Signature(client.getClientSecret(), clientId, timestamp); if (!sign.equals(signature)) { throw new IllegalArgumentException("Invalid signature"); } } /** * 生成指定长度的随机字符串 * * @param length 生成字符串的长度 * @return 随机字符串 */ public String generate(int length) { if (length <= 0) { throw new IllegalArgumentException("Length must be positive"); } SecureRandom random = new SecureRandom(); StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { int index = random.nextInt(SAFE_CHARACTERS.length()); sb.append(SAFE_CHARACTERS.charAt(index)); } return sb.toString(); } } 5. 自定义注册客户端存储库 package com.cdkjframework.oauth2.repository; import com.cdkjframework.exceptions.GlobalRuntimeException; import com.cdkjframework.oauth2.config.Oauth2Config; import com.cdkjframework.oauth2.entity.ClientDetails; import com.cdkjframework.util.tool.StringUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import java.util.*; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.repository * @ClassName: CustomRegisteredClientRepository * @Description: 自定义注册客户端存储库 * @Author: xiaLin * @Date: 2025/7/31 18:01 * @Version: 1.0 */ @Component @RequiredArgsConstructor public class CustomRegisteredClientRepository implements RegisteredClientRepository { /** * OAuth2客户端存储库 */ private final OAuth2ClientRepository oauth2ClientRepository; /** * OAuth2配置 */ private final Oauth2Config oauth2Config; /** * 保存注册的客户端 * * @param registeredClient 注册的客户端 */ @Override public void save(RegisteredClient registeredClient) { oauth2ClientRepository.save(toEntity(registeredClient)); } /** * 根据ID查找注册的客户端 * * @param id 客户端ID * @return 注册的客户端 */ @Override public RegisteredClient findById(String id) { // 根据 ID 查询客户端 return oauth2ClientRepository.findById(id) .map(entity -> { try { return toRegisteredClient(entity); } catch (JsonProcessingException e) { throw new GlobalRuntimeException(e); } }) .orElse(null); } /** * 根据客户端ID查找注册的客户端 * * @param clientId 客户端ID * @return 注册的客户端 */ @Override public RegisteredClient findByClientId(String clientId) { // 这是最常用的方法,根据 client_id 查询客户端 // 授权服务器在处理请求时会频繁调用此方法 return oauth2ClientRepository.findByClientId(clientId) .map(entity -> { try { RegisteredClient rc = toRegisteredClient(entity); return rc; } catch (JsonProcessingException e) { throw new GlobalRuntimeException(e); } }) .orElse(null); } private RegisteredClient toRegisteredClient(ClientDetails entity) throws JsonProcessingException { // 实现从 Entity 到 RegisteredClient 的转换 ObjectMapper mapper = new ObjectMapper(); // 安全拆分工具:null/空白返回空流 java.util.function.Function<String, java.util.stream.Stream<String>> safeSplit = (str) -> { if (str == null || str.isBlank()) return java.util.stream.Stream.empty(); return Arrays.stream(str.split(StringUtils.COMMA)).map(String::trim).filter(s -> !s.isEmpty()); }; // clientSettings / tokenSettings 允许为空,默认用空 Map Map<String, Object> clientSettingsMap; if (entity.getClientSettings() == null || entity.getClientSettings().isBlank()) { clientSettingsMap = java.util.Collections.emptyMap(); } else { Map<?, ?> raw = mapper.readValue(entity.getClientSettings(), Map.class); clientSettingsMap = new java.util.HashMap<>(); raw.forEach((k, v) -> clientSettingsMap.put(String.valueOf(k), v)); } Map<String, Object> tokenSettingsMap; if (entity.getTokenSettings() == null || entity.getTokenSettings().isBlank()) { tokenSettingsMap = java.util.Collections.emptyMap(); } else { Map<?, ?> raw = mapper.readValue(entity.getTokenSettings(), Map.class); tokenSettingsMap = new java.util.HashMap<>(); raw.forEach((k, v) -> tokenSettingsMap.put(String.valueOf(k), v)); } // 构建 ClientSettings:显式提供默认值,避免 NPE boolean requireProofKey = false; Object rpkVal = clientSettingsMap.get("require_proof_key"); if (rpkVal != null) { requireProofKey = (rpkVal instanceof Boolean) ? (Boolean) rpkVal : Boolean.parseBoolean(String.valueOf(rpkVal)); } boolean requireAuthorizationConsent = false; Object racVal = clientSettingsMap.get("require_authorization_consent"); if (racVal != null) { requireAuthorizationConsent = (racVal instanceof Boolean) ? (Boolean) racVal : Boolean.parseBoolean(String.valueOf(racVal)); } return RegisteredClient.withId(entity.getId()) .clientId(entity.getClientId()) // 确保数据库中的密码是加密后的 .clientSecret(entity.getClientSecret()) .clientAuthenticationMethods(clientAuthenticationMethods -> { Set<String> methods = new HashSet<>(); safeSplit.apply(entity.getClientAuthenticationMethods()).forEach(methods::add); if (methods.isEmpty()) { // 对于 public 客户端,允许 none 方式(授权码阶段不要求密钥) methods.add(ClientAuthenticationMethod.NONE.getValue()); } methods.stream().map(ClientAuthenticationMethod::new).forEach(clientAuthenticationMethods::add); }) .authorizationGrantTypes(authorizationGrantTypes -> { // 合并并补充授权类型,确保支持 authorization_code Set<String> grantValues = new HashSet<>(); safeSplit.apply(entity.getAuthorizationGrantTypes()).forEach(grantValues::add); if (!grantValues.contains(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())) { grantValues.add(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); } // 可选:若希望配合刷新令牌 if (!grantValues.contains(AuthorizationGrantType.REFRESH_TOKEN.getValue())) { grantValues.add(AuthorizationGrantType.REFRESH_TOKEN.getValue()); } grantValues.stream().map(AuthorizationGrantType::new).forEach(authorizationGrantTypes::add); }) .redirectUris(redirectUris -> { java.util.List<String> list = safeSplit.apply(entity.getRedirectUris()).toList(); String fallback = oauth2Config != null && oauth2Config.getDefaultRedirectUri() != null ? oauth2Config.getDefaultRedirectUri() : "https://localhost/callback"; String chosen = list.isEmpty() ? fallback : list.get(0); redirectUris.add(chosen); }) .scopes(scopes -> safeSplit.apply(entity.getScopes()) .forEach(scopes::add) ) .clientSettings( ClientSettings.builder() .requireProofKey(requireProofKey) .requireAuthorizationConsent(requireAuthorizationConsent) .build() ) .tokenSettings(TokenSettings.withSettings(tokenSettingsMap).build()) .build(); } /** * 保存 * * @param registeredClient 注册的 * @return 注册的 */ private ClientDetails toEntity(RegisteredClient registeredClient) { // 实现从 RegisteredClient 到 Entity 的转换(用于save方法) ClientDetails entity = ClientDetails.builder().build(); entity.setId(registeredClient.getId()); entity.setClientId(registeredClient.getClientId()); entity.setClientSecret(registeredClient.getClientSecret()); // 确保已经加密 // ... 设置其他字段,将集合转换为逗号分隔的字符串 return entity; } } 6. 实体 1. 授权码实体 package com.cdkjframework.oauth2.entity; import lombok.Data; import java.time.LocalDateTime; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.entity * @ClassName: AuthorizationCode * @Description: 授权码实体 * @Author: xiaLin * @Date: 2025/7/31 13:31 * @Version: 1.0 */ @Data public class AuthorizationCode { /** * 编码 */ private String code; /** * 客户ID */ private String clientId; /** * 回调地址 */ private String redirectUri; /** * 开始时间 */ private LocalDateTime issuedAt; /** * 过期时间 */ private LocalDateTime expiryAt; /** * 检查授权码是否过期 * * @return true 如果授权码已过期,否则返回 false */ public boolean isExpired() { return expiryAt.isBefore(LocalDateTime.now()); } } 2. 客户端详情实体 package com.cdkjframework.oauth2.entity; import lombok.Builder; import lombok.Data; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.entity * @ClassName: ClientDetails * @Description: 客户端详情实体 * @Author: xiaLin * @Date: 2025/7/31 18:05 * @Version: 1.0 */ @Data @Builder public class ClientDetails { /** * 主键ID */ private String id; /** * 客户端ID */ private String clientId; /** * 客户端密钥 */ private String clientSecret; /** * 用逗号分隔的字符串存储,例如 "client_secret_basic,client_secret_post" */ private String clientAuthenticationMethods; /** * 用逗号分隔的字符串存储,例如 "authorization_code,refresh_token,client_credentials" */ private String authorizationGrantTypes; /** * 用逗号分隔的字符串存储,例如 "http:127.0.0.1:8080/login/oauth2/code/messaging-client-oidc,http:127.0.0.1:8080/authorized" */ private String redirectUris; /** * 用逗号分隔的字符串存储,例如 "openid,profile,message.read,message.write" */ private String scopes; /** * JSON 字符串 */ private String clientSettings; /** * JSON 字符串 */ private String tokenSettings; } 3. OAuth2 令牌实体 package com.cdkjframework.oauth2.entity; import lombok.Data; import java.time.LocalDateTime; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.entity * @ClassName: OAuth2Token * @Description: OAuth2 令牌实体 * @Author: xiaLin * @Date: 2025/7/31 22:06 * @Version: 1.0 */ @Data public class OAuth2Token { /** * 令牌ID */ private String id; /** * 客户端ID */ private String clientId; /** * 用户ID */ private String userId; /** * 作用域 */ private String accessToken; /** * 刷新令牌 */ private String refreshToken; /** * 令牌类型 */ private LocalDateTime issuedAt; /** * 过期时间 */ private LocalDateTime expiration; /** * 状态 0-无效 1-有效 */ private Integer status; /** * 检查授权码是否过期 * * @return true 如果授权码已过期,否则返回 false */ public boolean isExpired() { return expiration.isBefore(LocalDateTime.now()); } } 4. 令牌响应实体 package com.cdkjframework.oauth2.entity; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; /** * @ProjectName: wiki-framework * @Package: com.cdkjframework.oauth2.entity * @ClassName: TokenResponse * @Description: 令牌响应实体 * @Author: xiaLin * @Date: 2025/8/31 15:40 * @Version: 1.0 */ @Data public class TokenResponse { /** * 访问令牌 */ @JsonProperty("access_token") private String accessToken; /** * 令牌类型 */ @JsonProperty("token_type") private String tokenType = "Bearer"; /** * 令牌过期时间,单位为秒 */ @JsonProperty("expires_in") private Long expiresIn; /** * 刷新令牌 */ @JsonProperty("refresh_token") private String refreshToken; } 7. 提供接口 以下接口需要项目实现,具体可参考示例项目: 示例项目:https://gitee.com/cdkjframework/framewiki-example/tree/master/example-oauth2 1. 授权码仓库 package com.cdkjframework.oauth2.repository; import com.cdkjframework.oauth2.entity.AuthorizationCode; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.repository * @ClassName: AuthorizationCodeRepository * @Description: 授权码仓库 * @Author: xiaLin * @Date: 2025/7/31 13:30 * @Version: 1.0 */ public interface AuthorizationCodeRepository { /** * 根据授权码查找授权码实体 * * @param code 授权码 * @return 授权码实体 */ AuthorizationCode findByCode(String code); /** * 保存授权码实体 * * @param authorizationCode 授权码实体 */ void save(AuthorizationCode authorizationCode); } 2. OAuth2客户端仓库 package com.cdkjframework.oauth2.repository; import com.cdkjframework.oauth2.entity.ClientDetails; import java.util.List; import java.util.Optional; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.repository * @ClassName: OAuth2ClientRepository * @Description: OAuth2客户端仓库 * @Author: xiaLin * @Date: 2025/7/31 18:02 * @Version: 1.0 */ public interface OAuth2ClientRepository { /** * 根据客户端ID查找客户端详情 * * @param clientId 客户端ID * @return 客户端详情 */ Optional<ClientDetails> findByClientId(String clientId); /** * 保存客户端详情 * * @param clientDetails 客户端详情 */ void save(ClientDetails clientDetails); /** * 根据ID查找客户端详情 * * @param id 客户端ID * @return 客户端详情 */ Optional<ClientDetails> findById(String id); } 3. OAuth2令牌仓库 package com.cdkjframework.oauth2.repository; import com.cdkjframework.oauth2.entity.OAuth2Token; /** * @ProjectName: wiki-oauth2 * @Package: com.cdkjframework.oauth2.repository * @ClassName: OAuth2TokenRepository * @Description: OAuth2令牌仓库 * @Author: xiaLin * @Date: 2025/7/31 22:33 * @Version: 1.0 */ public interface OAuth2TokenRepository { /** * 保存OAuth2令牌 * * @param oAuth2Token OAuth2令牌实体 */ void save(OAuth2Token oAuth2Token); /** * 根据访问令牌查找OAuth2令牌 * * @param refreshToken 访问令牌 * @return OAuth2令牌实体 */ OAuth2Token findByRefreshToken(String refreshToken); } 4. OAuth2令牌服务接口 package com.cdkjframework.oauth2.service; /** * OAuth2令牌服务接口 * * @ProjectName: cdkjframework * @Package: com.cdkjframework.core.controller.realization * @ClassName: Oauth2TokenService * @Description: OAuth2令牌服务接口 * @Author: xiaLin * @Version: 1.0 */ public interface Oauth2TokenService { /** * 验证令牌 * * @param token 令牌 * @throws IllegalArgumentException 如果令牌无效 */ default void validateToken(String token) { // 默认实现:可以在这里添加通用的令牌验证逻辑 if (token == null || token.isEmpty()) { throw new IllegalArgumentException("Token cannot be null or empty"); } } } 框架特性速览 开箱即用: 10分钟完成OAuth2服务搭建 多模式认证: 支持表单登录/JWT/OAuth2混合认证 精细审计: 全链路记录授权码生成、令牌发放操作 跨云部署: 兼容Kubernetes/阿里云/华为云等云原生环境 下一版本规范 拟定版本:1.2.0 OAuth2增加功能 授权模式扩展: 支持客户端凭证模式(client_credentials)、密码模式(password) 监控集成: 实时统计令牌发放频率、授权成功率 配置中心 增加 nacos 配置中心支持 获取资源与了解更多: 官网:https://framewiki.com/wiki-framework.html Gitee:https://gitee.com/cdkjframework/wiki-framework Github:https://github.com/cdkjframework/wiki-framework 示例项目:https://gitee.com/cdkjframework/framewiki-example 使用许可: Wiki Framework 采用 木兰宽松许可证 (MulanPSL-2.0)。

优秀的个人博客,低调大师

英伟达正开发新款“中国特供” AI 芯片,性能强于 H20

据路透社援引知情人士透露,英伟达正在研发面向中国市场的新型AI芯片B30A,其性能超越当前获准销售的H20芯片,并计划最快于2025年9月向中国客户提供测试样品。 据悉,该芯片采用单芯片设计,预计其算力约为旗舰级B300加速卡双芯片配置的一半。此外,该芯片将配备高带宽内存(HBM)和NVLink技术,以提升处理器间的数据传输效率。 单芯片设计指所有主要电路都制作在同一块连续的硅晶圆上,而不是分散在多个芯片上。消息人士表示,目前芯片最终规格还没完全敲定,但NVIDIA希望最快下个月向中国客户提供样品进行测试。 根据相关曝光的信息,B30A很可能是基于同样单芯片设计的Blackwell B300A修改而来。通过单芯片集成核心电路,B30A在保持Blackwell架构先进性的同时,降低了被认定为“高性能计算设备”的风险,从而规避更严格的出口审查。 该芯片基于最新Blackwell架构、其核心战略定位在于,在满足美国商务部出口管制条例(EAR)的前提下,提供超越上一代特供芯片H20的性能,以应对中国市场日益增长的AI算力需求和本土厂商的竞争。 此前,有消息称,美国政府与NVIDIA、AMD达成协议,将在中国销售芯片的15%营收上缴给美国政府,以取得半导体出口许可。与此同时,中国官媒指控NVIDIA芯片存在安全风险,并警告中国科技公司谨慎购买H20。 此外,据知情人士透露,NVIDIA也正准备推出另一款针对中国市场的新芯片,同样基于最新Blackwell架构,主要用于AI推理任务。该芯片暂名RTX6000D,售价将低于H20,其规格较弱且制造需求更简单。该芯片设计是为了落在美国政府设定的门槛之下,采用传统GDDR内存,内存带宽为1398 GB/s,计划9月提供少量产品给中国客户。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册