从零搭建微服务项目Pro(第6-2章——微服务鉴权模块SpringSecurity+JWT)

前言:

        在上一章已经实现了SpringBoot单服务的鉴权,在导入SpringSecurity的相关依赖,以及使用JWT生成的accessToken和refreshToken能够实现不同Controller乃至同一Controller中不同接口的权限单独校验。上一章链接如下:

从零搭建微服务项目Pro(第6-1章——Spring Security+JWT实现用户鉴权访问与token刷新)_微服务springboot+security+jwt实现刷新token-CSDN博客https://blog.csdn.net/wlf2030/article/details/146316131?spm=1001.2014.3001.5501但在微服务架构中,如何实现各服务统一使用相同鉴权模块、如何权衡网关和鉴权模块的关系,如何确保Feign调用不被SpringSecurity拦截如何使用对无状态的JWT进行控制,这些问题仍需要解决。

本章针对这些问题给出了解答分析以及代码示例。完整代码链接如下:

(该链接为一个笔者正在开发的微服务商城项目,会逐渐整合本专栏所有功能,欢迎Star)

wlf728050719/BitGoPlushttps://github.com/wlf728050719/BitGoPlus

以及本专栏会持续更新微服务项目,每一章的项目都会基于前一章项目进行功能的完善,欢迎小伙伴们关注!同时如果只是对单章感兴趣也不用从头看,只需下载前一章项目即可,每一章都会有前置项目准备部分,跟着操作就能实现上一章的最终效果,当然如果是一直跟着做可以直接跳过这一部分。专栏目录链接如下,其中Base篇为基础微服务搭建,Pro篇为复杂模块实现。

从零搭建微服务项目(全)-CSDN博客https://blog.csdn.net/wlf2030/article/details/145799620?spm=1001.2014.3001.5501


核心依赖:

        <!-- security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- jwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId></dependency><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId></dependency>

权限实体:

package cn.bit.pojo.dto;import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;import java.util.Collection;@Getter
public class BitGoUser extends User {private final UserBaseInfo userBaseInfo;public BitGoUser(UserBaseInfo userBaseInfo, Collection<? extends GrantedAuthority> authorities) {super(userBaseInfo.getUsername(), userBaseInfo.getPassword(), authorities);this.userBaseInfo = userBaseInfo;}
}

先定义整个项目的鉴权实体类,当有请求访问时,通过鉴权后会将这个实体类存储在整个服务的鉴权上下文中,在加上注解进行aop操作即可实现单接口权限控制。这里是直接继承spring.secutity定义好的user,user定义如下:

当然也可以不选择继承定义好的User,但必须实现UserDetails接口中的所有方法,UserDetails可以看作整个spring.security的核心。

即主要实现用户用户名,密码,权限,以及是否过期,是否上锁,是否启用,是否权限超时。

继续深入查看权限接口是如何定义的,其实会发现spring.security鉴权的底层实际是鉴是否有字符串。


实体初始化:

在定义好权限实体类后,我们需要给一个service用来初始化权限。具体代码如下:

package cn.bit.service.impl;import cn.bit.constant.SecurityConstant;
import cn.bit.pojo.dto.BitGoAuthorization;
import cn.bit.pojo.dto.UserBaseInfo;
import cn.bit.service.BitGoUserService;
import cn.bit.exception.BizException;
import cn.bit.exception.SysException;
import cn.bit.client.UserClient;
import cn.bit.pojo.dto.BitGoUser;
import cn.bit.pojo.vo.R;
import lombok.AllArgsConstructor;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;import java.util.Collection;
import java.util.Set;@Service("BitGoUserService")
@AllArgsConstructor
public class BitGoUserServiceFeignImpl implements BitGoUserService {private final UserClient userClient;@Overridepublic UserDetails loadUserByUsername(String username) {return getBitGoUserFromRPC(username);}public BitGoUser getBitGoUserFromRPC(String username) {// 获取用户基本信息R<UserBaseInfo> userResponse = userClient.getInfoByUsername(username);if (userResponse == null) {throw new SysException("get response from user-service failed");}if (userResponse.getData() == null) {throw new BizException("用户名不存在");}UserBaseInfo user = userResponse.getData();// 获取用户角色信息R<Set<BitGoAuthorization>> roleResponse = userClient.getBitGoAuthorizationByUserId(user.getUserId());if (roleResponse == null) {throw new SysException("get response from user-service failed");}// 构建BitGoUser对象return new BitGoUser(user, roleResponse.getData());}@Overridepublic boolean checkUser(BitGoUser user, Long userId) {if (user == null) {return false;}return user.getUserBaseInfo().getUserId().equals(userId);}@Overridepublic boolean checkAdmin(BitGoUser user) {return checkRoleAndTenantId(user, null, SecurityConstant.ROLE_ADMIN);}@Overridepublic boolean checkShopKeeper(BitGoUser user, Long tenantId) {return checkRoleAndTenantId(user, tenantId, SecurityConstant.ROLE_SHOPKEEPER);}@Overridepublic boolean checkClerk(BitGoUser user, Long tenantId) {return checkRoleAndTenantId(user, tenantId, SecurityConstant.ROLE_CLERK);}private boolean checkRoleAndTenantId(BitGoUser user, Long tenantId, String roleCode) {if (user == null) {return false;}// 获取用户的所有授权信息Collection<? extends GrantedAuthority> authorities = user.getAuthorities();// 检查是否有匹配的角色return authorities.stream().filter(auth -> auth instanceof BitGoAuthorization).map(auth -> (BitGoAuthorization) auth).anyMatch(auth -> {// 1. 先检查角色是否匹配boolean roleMatches = auth.getRoleCode().equals(roleCode);// 2. 如果角色不匹配,直接返回 falseif (!roleMatches) {return false;}// 3. 如果角色匹配,且不需要检查租户(tenantId == null),则直接返回 trueif (tenantId == null) {return true;}// 4. 如果需要检查租户,则检查该角色的租户是否匹配return tenantId.equals(auth.getTenantId());});}}

这里先是定义了一个服务接口方便后续拓展,接口如下:

主要功能是加载用户,以及判断用户的身份是否符合要求。加载用户时远程调用user-service从数据库提取用户权限并装载实体类。主要核心是实现UserDetailService的方法。


实体类注入:

我们需要将实体类注入上下文中,方便每个接口进行鉴权。这里是在每次请求的过滤器进行注入:

package cn.bit.filter;import cn.bit.constant.RedisKey;
import cn.bit.pojo.dto.BitGoUser;
import cn.bit.pojo.dto.InternalServiceAuthentication;
import cn.bit.constant.SecurityConstant;
import cn.bit.util.JwtUtil;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@AllArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {private JwtUtil jwtUtil;private UserDetailsService userDetailsService;private RedisTemplate<String, Object> redisTemplate;@SuppressWarnings("checkstyle:ReturnCount")@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {final String authorizationHeader = request.getHeader(SecurityConstant.HEADER_AUTHORIZATION);final String sourceHeader = request.getHeader(SecurityConstant.HEADER_SOURCE);// 处理内部服务Tokenif (authorizationHeader != null && authorizationHeader.startsWith(SecurityConstant.TAG_INTERNAL)&& sourceHeader != null && sourceHeader.startsWith(SecurityConstant.TAG_SERVICE)) {String source = sourceHeader.substring(SecurityConstant.TAG_SERVICE.length());String token = authorizationHeader.substring(SecurityConstant.TAG_INTERNAL.length());if (jwtUtil.validateInternalToken(token, source)) {Authentication auth = new InternalServiceAuthentication(source);SecurityContextHolder.getContext().setAuthentication(auth);chain.doFilter(request, response);return;}}// 处理外部请求TokenString username = null;String jwt = null;if (authorizationHeader != null && authorizationHeader.startsWith(SecurityConstant.TAG_BEARER)) {jwt = authorizationHeader.substring(7);username = jwtUtil.extractData(jwt);}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);BitGoUser bitGoUser = (BitGoUser) userDetails;String key = String.format(RedisKey.TOKEN_KEY_FORMAT, bitGoUser.getUsername());String value = (String) redisTemplate.opsForValue().get(key);// 与缓存中jwt不一致禁止访问if (value == null || !value.equals(jwt)) {chain.doFilter(request, response);return;}// 用户被删除或冻结时禁止访问if (bitGoUser.getUserBaseInfo().getLockFlag() != 0 || bitGoUser.getUserBaseInfo().getDelFlag() != 0) {chain.doFilter(request, response);return;}if (jwtUtil.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}}chain.doFilter(request, response);}
}

即每次请求,从请求头中取出jwt并进行解密,然后将解密出的用户名通过之前定义的service完成权限实体的初始化并注入上下文中。


全局访问规则配置:

在对接口进行精细化权限控制前,可对每个服务做全局规则配置。

package cn.bit.config;import cn.bit.constant.SecurityConstant;
import cn.bit.filter.JwtAuthenticationFilter;
import cn.bit.util.JwtUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MicroserviceSecurityConfig extends WebSecurityConfigurerAdapter {private final JwtUtil jwtUtil;private final UserDetailsService userDetailsService;private final RedisTemplate<String, Object> redisTemplate;// 推荐使用构造函数注入public MicroserviceSecurityConfig(JwtUtil jwtUtil, UserDetailsService userDetailsService,RedisTemplate<String, Object> redisTemplate) {this.jwtUtil = jwtUtil;this.userDetailsService = userDetailsService;this.redisTemplate = redisTemplate;}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话.and()// 将JWT过滤器添加到UsernamePasswordAuthenticationFilter之前.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).authorizeRequests().antMatchers("/auth/**").permitAll().antMatchers("/user/open/**").permitAll().antMatchers("/api/**").hasRole(SecurityConstant.ROLE_INTERNAL_SERVICE)// 允许认证端点公开访问.anyRequest().authenticated(); // 其他所有请求需要认证}@Beanpublic JwtAuthenticationFilter jwtAuthenticationFilter() {return new JwtAuthenticationFilter(jwtUtil, userDetailsService, redisTemplate);}
}

这里的config中即配置允许所有用户访问/auth路径下接口和/user/open接口,同时只允许有内部服务权限的用户访问/api接口。


单接口注解配置:

package cn.bit.annotation;import org.springframework.security.access.prepost.PreAuthorize;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("@BitGoUserService.checkAdmin(authentication.principal)")
public @interface Admin {
}

定义注解Admin,当调用接口前会执行名称为BitGoService的checkAdmin方法,方法实参为上下文中的实体,这样当上下文中权限实体被判定为true时才允许通过。

同时每个接口还能随时从上下文中取出权限实体类进行操作。下面接口为只有管理员用户能够访问,且输出访问实体类的用户id的示例。

使用工具类如下:

package cn.bit.util;import lombok.experimental.UtilityClass;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;/*** 安全工具类** @author L.cm*/
@UtilityClass
public class SecurityUtils {/*** 获取Authentication*/public Authentication getAuthentication() {return SecurityContextHolder.getContext().getAuthentication();}/*** 获取用户*/public User getUser(Authentication authentication) {if (authentication == null || authentication.getPrincipal() == null) {return null;}Object principal = authentication.getPrincipal();if (principal instanceof User) {return (User) principal;}return null;}/*** 获取用户*/public User getUser() {Authentication authentication = getAuthentication();return getUser(authentication);}
}

内部服务鉴权:

在添加鉴权后,会发现原有的feign调用也一并被拦截。这里可以同样为feign做配置。实现思路有三种,一种是使用固定的key,在jwt过滤器时提取到key后单独设置,但key需要单独配置,且泄露后会造成较大危害。一种是服务内部的api允许任何人访问,内部服务通过自定义注解,当请求头中不含有某个自定义header时视为非法访问,网关对所有原始请求清洗对应header,同时feign调用时添加上对应请求头。但当请求头header内容泄露以及服务端口泄露,可直接不经过网关访问从而造成攻击。一种是同样为feign调用添加jwt识别header,但设置jwt过期时间很短,这样即使jwt意外泄漏也不会造成过大危害,只是稍微影响服务间调用的性能。很明细第三种最为安全,只有当jwt密钥泄露,或持续抓取服务间调用请求内容并攻击(这两种情况无论哪种防护方法都无解)才会出现问题。具体配置如下:

package cn.bit.config;import cn.bit.constant.SecurityConstant;
import cn.bit.util.JwtUtil;
import feign.RequestInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FeignSecurityConfiguration {@Bean@ConditionalOnMissingBeanpublic RequestInterceptor requestInterceptor(JwtUtil jwtUtil) {return template -> {// 从配置获取服务名,而不是硬编码String serviceName = template.feignTarget().name();String token = jwtUtil.generateInternalToken(serviceName);template.header(SecurityConstant.HEADER_AUTHORIZATION, SecurityConstant.TAG_INTERNAL + token);template.header(SecurityConstant.HEADER_SOURCE, SecurityConstant.TAG_SERVICE + serviceName);};}
}

对应前面filter内容

以及全局配置


JWT密钥状态管理:

尽管jwt无状态管理能够极大减小服务器存储压力,但试想下面案例,当用户密码泄露后,他人使用密码获取token,用户修改密码试图减小损失,他人使用原有token仍然能够访问对应接口,即同一时间能有多个token对应同一用户并同时操作,这显然是存在问题的,因此需要引入缓存存储每次颁发的token,当用户的token与缓存中token不一致时,拒绝访问。

对应过滤器内容

对应登录内容:


各服务启用鉴权:

上述所有内容定义在common-security模块中

并将所有需要的bean导出给每个服务使用

各服务只需要在对应pom导入即可使用。


最后:

        spring.security其实的整体思路很清晰,但一定要搞清楚其和网关的关系,一开始我误以为jwt过滤器设置在网关,对于每一个请求网关均提取token并调用auth服务获取user后存取在上下文中,之后各服务直接从网关给的上下文拿取实体类,但实际上我犯了一个很严重的错误,spring cloud的每个服务都是独立的上下文,存在网关中的上下文是不能传递给其他服务的,正确思路应该如下,网关只负载基本的header清洗以及路由转发,jwt过滤器设置在每个服务上,有n个服务,则项目总共有n个jwt过滤器,同时有n个全局访问规则配置在生效,但由于所有服务都装填同一个配置类导致看起来像是在给网关配置访问规则,各服务使用UserDetailService完成实体类初始化后注入自己的上下文,并通过注解完成权限控制。不知道上面解释能否解答你的问题,如果仍不明白,强烈建议git上面代码链接,并查看spring.security的源码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/76368.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

win安装软件

win安装软件 jdk安装 jdk安装 首先去官网下载适合系统版本的JDK&#xff0c;下载地址&#xff1a; http://www.oracle.com/technetwork/java/javase/downloads/index.html进入下载页面&#xff0c;如下图&#xff1a; 首先选择&#xff1a;Accept License Agreement单选按钮&…

Prompt-Tuning 提示词微调

1. Hard Prompt 定义&#xff1a; Hard prompt 是一种更为具体和明确的提示&#xff0c;要求模型按照给定的信息生成精确的结果&#xff0c;通常用于需要模型提供准确答案的任务. 原理&#xff1a; Prompt Tuning原理如下图所示&#xff1a;冻结主模型全部参数&#xff0c;在…

【Vue生命周期的演变:从Vue 2到Vue 3的深度剖析】

Vue生命周期的演变&#xff1a;从Vue 2到Vue 3的深度剖析 1. 生命周期钩子的概念与意义 Vue框架通过生命周期钩子函数使开发者可以在组件不同阶段执行自定义逻辑。这些钩子函数是Vue组件生命周期中的关键切入点&#xff0c;对于控制组件行为至关重要。 2. Vue 2中的生命周期…

java ai 图像处理

Java AI 图像处理 图像处理是人工智能&#xff08;AI&#xff09;领域中非常重要的一个应用方向。通过使用Java编程语言和相应的库&#xff0c;我们可以实现各种图像处理任务&#xff0c;如图像识别、图像分类、图像分割等。本文将介绍一些常见的图像处理算法&#xff0c;并通过…

从 0~1 保姆级 详细版 PostgreSQL 数据库安装教程

PostgreSQL数据库安装 PostgreSQL官网 【PostgreSQL官网】 | 【PostgreSQL安装官网_Windows】 安装步骤 step1&#xff1a; 选择与电脑相对应的PostgreSQL版本进行下载。 step2&#xff1a; 双击打开刚才下载好的文件。 step3&#xff1a; 在弹出的setup窗口中点击 …

Keil MDK中禁用半主机(No Semihosting)

在 ARM 编译器&#xff08;如 Keil MDK&#xff09; 中禁用半主机&#xff08;Semihosting&#xff09;并实现标准库的基本功能&#xff0c;需要以下步骤&#xff1a; 1. 禁用半主机 #pragma import(__use_no_semihosting) // 禁用半主机模式作用&#xff1a;防止标准库函数&…

github | 仓库权限管理 | 开权限

省流版总结&#xff1a; github 给别人开权限&#xff1a;仓库 -> Setting -> Cllaborate -> Add people GitHub中 将公开仓库改为私有&#xff1a;仓库 -> Setting -> Danger Zone&#xff08;危险区&#xff09; ->Change repository visibility( 更改仓…

快速部署大模型 Openwebui + Ollama + deepSeek-R1模型

背景 本文主要快速部署一个带有web可交互界面的大模型的应用&#xff0c;主要用于开发测试节点&#xff0c;其中涉及到的三个组件为 open-webui Ollama deepSeek开放平台 首先 Ollama 是一个开源的本地化大模型部署工具,提供与OpenAI兼容的Api接口&#xff0c;可以快速的运…

极狐GitLab 项目导入导出设置介绍?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 导入导出设置 (BASIC SELF) 导入和导出相关功能的设置。 配置允许的导入源 在从其他系统导入项目之前&#xff0c;必须为该…

信奥还能考吗?未来三年科技特长生政策变化

近年来&#xff0c;科技特长生已成为名校录取的“黄金敲门砖”。 从CSP-J/S到NOI&#xff0c;编程竞赛成绩直接关联升学优势。 未来三年&#xff0c;政策将如何调整&#xff1f;家长该如何提前布局&#xff1f; 一、科技特长生政策趋势&#xff1a;2025-2027关键变化 1. 竞…

AI测试用例生成平台

AI测试用例生成平台 项目背景技术栈业务描述项目展示项目重难点 项目背景 针对传统接口测试用例设计高度依赖人工经验、重复工作量大、覆盖场景有限等行业痛点&#xff0c;基于大语言模型技术实现接口测试用例智能生成系统。 技术栈 LangChain框架GLM-4模型Prompt Engineeri…

操作系统-PV

&#x1f9e0; 背景&#xff1a;为什么会有 PV&#xff1f; 类比&#xff1a;内存&#xff08;生产者&#xff09; 和 CPU&#xff08;消费者&#xff09; 内存 / IO / 磁盘 / 网络下载 → 不断“生产数据” 例如&#xff1a;读取文件、下载视频、从数据库加载信息 CPU → 负…

工厂方法模式详解及在自动驾驶场景代码示例(c++代码实现)

模式定义 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;通过定义抽象工厂接口将对象创建过程延迟到子类实现&#xff0c;实现对象创建与使用的解耦。该模式特别适合需要动态扩展产品类型的场景。 自动驾驶感知场景分析 自动驾…

基于 S2SH 架构的企业车辆管理系统:设计、实现与应用

在企业运营中&#xff0c;车辆管理是一项重要工作。随着企业规模的扩大&#xff0c;车辆数量增多&#xff0c;传统管理方式效率低下&#xff0c;难以满足企业需求。本文介绍的基于 S2SH 的企业车辆管理系统&#xff0c;借助现代化计算机技术&#xff0c;实现车辆、驾驶员和出车…

IntelliJ IDEA download JDK

IntelliJ IDEA download JDK 自动下载各个版本JDK&#xff0c;步骤 File - Project Structure &#xff08;快捷键 Ctrl Shift Alt S&#xff09; 如果下载失败&#xff0c;换个下载站点吧。一般选择Oracle版本&#xff0c;因为java被Oracle收购了 好了。 花里胡哨&#…

MCP协议在纳米材料领域的深度应用:从跨尺度协同到智能研发范式重构

MCP协议在纳米材料领域的深度应用&#xff1a;从跨尺度协同到智能研发范式重构 文章目录 MCP协议在纳米材料领域的深度应用&#xff1a;从跨尺度协同到智能研发范式重构一、MCP协议的技术演进与纳米材料研究的适配性分析1.1 MCP协议的核心架构升级1.2 纳米材料研发的核心挑战与…

OpenAI发布GPT-4.1:开发者专属模型的深度解析 [特殊字符]

最近OpenAI发布了GPT-4.1模型&#xff0c;却让不少人感到困惑。今天我们就来深入剖析这个新模型的关键信息&#xff01; 重要前提&#xff1a;API专属模型 &#x1f4bb; 首先需要明确的是&#xff0c;GPT-4.1仅通过API提供&#xff0c;不会出现在聊天界面中。这是因为该模型主…

DemoGen:用于数据高效视觉运动策略学习的合成演示生成

25年2月来自清华、上海姚期智研究院和上海AI实验室的论文“DemoGen: Synthetic Demonstration Generation for Data-Efficient Visuomotor Policy Learning”。 视觉运动策略在机器人操控中展现出巨大潜力&#xff0c;但通常需要大量人工采集的数据才能有效执行。驱动高数据需…

界面控件DevExpress WPF v25.1新功能预览 - 文档处理类功能升级

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

Muduo网络库实现 [十六] - HttpServer模块

目录 设计思路 类的设计 模块的实现 公有接口 私有接口 疑问点 设计思路 本模块就是设计一个HttpServer模块&#xff0c;提供便携的搭建http协议的服务器的方法。那么这个模块需要如何设计呢&#xff1f; 这还需要从Http请求说起。 首先从http请求的请求行开始分析&…