微服务OAuth 2.1认证授权Demo方案(Spring Security 6)
文章目录
- 一、介绍
- 二、auth微服务代码
- 1. SecurityConfig
- 2. UserDetailsService
- 3. 总结
- 三、gateway微服务代码
- 1. 统一处理CORS问题
- 四、content微服务代码
- 1. controller
- 2. SecurityConfig
- 3. 解析JWT Utils
- 4. 总结
- 五、一些坑
书接上文
微服务OAuth 2.1认证授权可行性方案(Spring Security 6)
一、介绍
三个微服务
auth微服务作为认证服务器,用于颁发JWT。gateway微服务作为网关,用于拦截过滤。content微服务作为资源服务器,用于校验授权。
以下是授权相关数据库。
user表示用户表role表示角色表user_role关联了用户和角色,表示某个用户是是什么角色。一个用户可以有多个角色menu表示资源权限表。@PreAuthorize("hasAuthority('xxx')")时用的就是这里的code。permission关联了角色和资源权限,表示某个角色用于哪些资源访问权限,一个角色有多个资源访问权限。

当我们知道userId,我们就可以知道这个用户可以访问哪些资源,并把这些权限(也就是menu里的code字段)写成数组,写到JWT的负载部分的authorities字段中。当用户携带此JWT访问具有@PreAuthorize("hasAuthority('xxx')")修饰的资源时,我们解析出JWT中的authorities字段,判断是否包含hasAuthority指定的xxx权限,以此来完成所谓的的“授权”。
二、auth微服务代码
1. SecurityConfig
package com.xuecheng.auth.config;import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
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.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;/*** 身份验证服务器安全配置** @author mumu* @date 2024/02/13*/
//@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@Configuration
@EnableWebSecurity
public class AuthServerSecurityConfig {private static KeyPair generateRsaKey() {KeyPair keyPair;try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();} catch (Exception ex) {throw new IllegalStateException(ex);}return keyPair;}/*** 密码编码器* 用于加密认证服务器client密码和用户密码** @return {@link PasswordEncoder}*/@Beanpublic PasswordEncoder passwordEncoder() {// 密码为明文方式// return NoOpPasswordEncoder.getInstance();// 或使用 BCryptPasswordEncoderreturn new BCryptPasswordEncoder();}/*** 授权服务器安全筛选器链* <br/>* 来自Spring Authorization Server示例,用于暴露Oauth2.1端点,一般不影响常规的请求** @param http http* @return {@link SecurityFilterChain}* @throws Exception 例外*/@Bean@Order(1)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0http// Redirect to the login page when not authenticated from the// authorization endpoint.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(new LoginUrlAuthenticationEntryPoint("/login"),new MediaTypeRequestMatcher(MediaType.TEXT_HTML)))// Accept access tokens for User Info and/or Client Registration.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));return http.build();}/*** 默认筛选器链* <br/>* 这个才是我们需要关心的过滤链,可以指定哪些请求被放行,哪些请求需要JWT验证** @param http http* @return {@link SecurityFilterChain}* @throws Exception 例外*/@Bean@Order(2)public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) ->authorize.requestMatchers(new AntPathRequestMatcher("/actuator/**")).permitAll().requestMatchers(new AntPathRequestMatcher("/login")).permitAll().requestMatchers(new AntPathRequestMatcher("/logout")).permitAll().requestMatchers(new AntPathRequestMatcher("/wxLogin")).permitAll().requestMatchers(new AntPathRequestMatcher("/register")).permitAll().requestMatchers(new AntPathRequestMatcher("/oauth2/**")).permitAll().requestMatchers(new AntPathRequestMatcher("/**/*.html")).permitAll().requestMatchers(new AntPathRequestMatcher("/**/*.json")).permitAll().requestMatchers(new AntPathRequestMatcher("/auth/**")).permitAll().anyRequest().authenticated()).csrf(AbstractHttpConfigurer::disable)//指定logout端点,用于退出登陆,不然二次获取授权码时会自动登陆导致短时间内无法切换用户.logout(logout -> logout.logoutUrl("/logout").addLogoutHandler(new SecurityContextLogoutHandler()).logoutSuccessUrl("http://www.51xuecheng.cn")).formLogin(Customizer.withDefaults()).oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())
// .jwt(jwt -> jwt
// .jwtAuthenticationConverter(jwtAuthenticationConverter())
// ));return http.build();}private JwtAuthenticationConverter jwtAuthenticationConverter() {JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();return jwtConverter;}/*** 客户端管理实例* <br/>* 来自Spring Authorization Server示例** @return {@link RegisteredClientRepository}*/@Beanpublic RegisteredClientRepository registeredClientRepository() {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("XcWebApp").clientSecret(passwordEncoder().encode("XcWebApp")).clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).redirectUri("http://www.51xuecheng.cn").redirectUri("http://localhost:63070/auth/wxLogin").redirectUri("http://www.51xuecheng.cn/sign.html")
// .postLogoutRedirectUri("http://localhost:63070/login?logout").scope("all").scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).scope("message.read").scope("message.write").scope("read").scope("write").clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofHours(2)) // 设置访问令牌的有效期.refreshTokenTimeToLive(Duration.ofDays(3)) // 设置刷新令牌的有效期.reuseRefreshTokens(true) // 是否重用刷新令牌.build()).build();return new InMemoryRegisteredClientRepository(registeredClient);}/*** jwk源* <br/>* 对访问令牌进行签名的示例,里面包含公私钥信息。** @return {@link JWKSource}<{@link SecurityContext}>*/@Beanpublic JWKSource<SecurityContext> jwkSource() {KeyPair keyPair = generateRsaKey();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();JWKSet jwkSet = new JWKSet(rsaKey);return new ImmutableJWKSet<>(jwkSet);}/*** jwt解码器* <br/>* JWT解码器,主要就是基于公钥信息来解码** @param jwkSource jwk源* @return {@link JwtDecoder}*/@Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}@Beanpublic AuthorizationServerSettings authorizationServerSettings() {return AuthorizationServerSettings.builder().build();}/*** JWT定制器* <BR/>* 可以往JWT从加入额外信息,这里是加入authorities字段,是一个权限数组。** @return {@link OAuth2TokenCustomizer}<{@link JwtEncodingContext}>*/@Beanpublic OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() {return context -> {Authentication authentication = context.getPrincipal();if (authentication.getPrincipal() instanceof UserDetails userDetails) {List<String> authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());context.getClaims().claim("authorities", authorities);}};}
}
这里需要注意几点
- 使用
BCryptPasswordEncoder密码加密,在设置clientSecret时需要手动使用密码编码器。 jwtTokenCustomizer解析UserDetails然后往JWT中添加authorities字段,为了后面的授权。
2. UserDetailsService
package com.xuecheng.ucenter.service.impl;import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.ucenter.mapper.XcMenuMapper;
import com.xuecheng.ucenter.mapper.XcUserMapper;
import com.xuecheng.ucenter.model.dto.AuthParamsDto;
import com.xuecheng.ucenter.model.dto.XcUserExt;
import com.xuecheng.ucenter.model.po.XcMenu;
import com.xuecheng.ucenter.model.po.XcUser;
import com.xuecheng.ucenter.service.AuthService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;@Component
@Slf4j
public class UserServiceImpl implements UserDetailsService {@Autowiredprivate MyAuthService myAuthService;@AutowiredXcMenuMapper xcMenuMapper;/*** 用户统一认证** @param s 用户信息Json字符串* @return {@link UserDetails}* @throws UsernameNotFoundException 找不到用户名异常*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {XcUserExt xcUserExt = myAuthService.execute(username);return getUserPrincipal(xcUserExt);}public UserDetails getUserPrincipal(XcUserExt user){//用户权限,如果不加报Cannot pass a null GrantedAuthority collectionList<XcMenu> xcMenus = xcMenuMapper.selectPermissionByUserId(user.getId());String[] permissions = {"read"};if (ObjectUtils.isNotEmpty(xcMenus)){permissions = xcMenus.stream().map(XcMenu::getCode).toList().toArray(String[]::new);log.info("权限如下:{}", Arrays.toString(permissions));}//为了安全在令牌中不放密码String password = user.getPassword();user.setPassword(null);//将user对象转jsonString userString = JSON.toJSONString(user);//创建UserDetails对象return User.withUsername(userString).password(password).authorities(permissions).build();}
}
这里需要注意几点
username就是前端/auth/login的时候输入的账户名。myAuthService.execute(username)不抛异常,就默认表示账户存在,此时将password加入UserDetails并返回,Spring Authorization Server对比校验两个密码。myAuthService.execute(username)根据username获取用户信息返回,将用户信息存入withUsername中,Spring Authorization Server默认会将其加入到JWT中。- 现在
Spring Authorization Server默认不会把authorities(permissions)写入JWT,需要配合OAuth2TokenCustomizer手动写入。
3. 总结
这样,auth微服务颁发的JWT,现在就会包含authorities字段。示例如下
{"active": true,"sub": "{\"cellphone\":\"17266666637\",\"createTime\":\"2024-02-13 10:33:13\",\"email\":\"1138882663@qq.com\",\"id\":\"012f3a90-2bc9-4a2c-82a3-f9777c9ac10a\",\"name\":\"xiamu\",\"nickname\":\"xiamu\",\"permissions\":[],\"status\":\"1\",\"updateTime\":\"2024-02-13 10:33:13\",\"username\":\"xiamu\",\"utype\":\"101001\",\"wxUnionid\":\"test\"}","aud": ["XcWebApp"],"nbf": 1707830437,"scope": "all","iss": "http://localhost:63070/auth","exp": 1707837637,"iat": 1707830437,"jti": "8a657c60-968f-4d98-8a4c-22a7b4ecd333","authorities": ["xc_sysmanager","xc_sysmanager_company","xc_sysmanager_doc","xc_sysmanager_log","xc_teachmanager_course_list"],"client_id": "XcWebApp","token_type": "Bearer"
}
三、gateway微服务代码
1. 统一处理CORS问题
@EnableWebFluxSecurity
@Configuration
public class SecurityConfig {//安全拦截配置@Beanpublic SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception {return http.cors(cors -> cors.configurationSource(corsConfigurationSource())).authorizeExchange(exchanges ->exchanges.pathMatchers("/**").permitAll().anyExchange().authenticated()).oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())).csrf(ServerHttpSecurity.CsrfSpec::disable).build();}@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration corsConfig = new CorsConfiguration();corsConfig.addAllowedOriginPattern("*"); // 允许任何源corsConfig.addAllowedMethod("*"); // 允许任何HTTP方法corsConfig.addAllowedHeader("*"); // 允许任何HTTP头corsConfig.setAllowCredentials(true); // 允许证书(cookies)corsConfig.setMaxAge(3600L); // 预检请求的缓存时间(秒)UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", corsConfig); // 对所有路径应用这个配置return source;}
}
这里需要注意几点
- 书接上文,这里虽然用了
oauth2.jwt(Customizer.withDefaults()),但实际上基于远程auth微服务开放的jwkSetEndpoint配置的JwtDecoder。 .cors(cors -> cors.configurationSource(corsConfigurationSource()))一次性处理CORS问题。
四、content微服务代码
1. controller
@PreAuthorize("hasAuthority('xc_teachmanager_course_list')")@ApiResponse(responseCode = "200", description = "Successfully retrieved user")@Operation(summary = "查询课程信息列表")@PostMapping("/course/list")public PageResult<CourseBase> list(PageParams pageParams,@Parameter(description = "请求具体内容") @RequestBody(required = false) QueryCourseParamsDto dto){SecurityUtil.XcUser xcUser = SecurityUtil.getUser();if (xcUser != null){System.out.println(xcUser.getUsername());System.out.println(xcUser.toString());}return courseBaseInfoService.queryCourseBaseList(pageParams, dto);}
使用了@PreAuthorize("hasAuthority('xc_teachmanager_course_list')")修饰的controller资源。
2. SecurityConfig
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {//安全拦截配置@Beanpublic SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) ->authorize.requestMatchers("/**").permitAll().anyRequest().authenticated()).csrf(AbstractHttpConfigurer::disable).oauth2ResourceServer(oauth -> oauth.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));return http.build();}private JwtAuthenticationConverter jwtAuthenticationConverter() {JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();jwtConverter.setJwtGrantedAuthoritiesConverter(jwt -> {// 从JWT的claims中提取权限信息List<String> authorities = jwt.getClaimAsStringList("authorities");if (authorities == null) {return Collections.emptyList();}return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());});return jwtConverter;}
}
需要注意几点
- 使用
@EnableMethodSecurity让@PreAuthorize生效 - 和
gateway一样,需要基于远程auth微服务开放的jwkSetEndpoint配置JwtDecoder。 - 指定
JwtAuthenticationConverter,让anyRequest().authenticated()需要验证的请求,除了完成默认的JWT验证外,还需要完成JwtAuthenticationConverter指定逻辑。 JwtAuthenticationConverter中将JWT的authorities部分形成数组后写入GrantedAuthorities,这正是spring security6用于校验@PreAuthorize的字段。
3. 解析JWT Utils
@Slf4j
public class SecurityUtil {public static XcUser getUser(){Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication == null){return null;}if (authentication instanceof JwtAuthenticationToken) {JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) authentication;System.out.println(jwtAuth);Map<String, Object> tokenAttributes = jwtAuth.getTokenAttributes();System.out.println(tokenAttributes);Object sub = tokenAttributes.get("sub");return JSON.parseObject(sub.toString(), XcUser.class);}return null;}@Datapublic static class XcUser implements Serializable {private static final long serialVersionUID = 1L;private String id;private String username;private String password;private String salt;private String name;private String nickname;private String wxUnionid;private String companyId;/*** 头像*/private String userpic;private String utype;private LocalDateTime birthday;private String sex;private String email;private String cellphone;private String qq;/*** 用户状态*/private String status;private LocalDateTime createTime;private LocalDateTime updateTime;}
}
把JWT的信息解析回XcUser ,相当于用户携带JWT访问后端,后端可以根据JWT获取此用户的信息。当然,你可以尽情的自定义,扩展。
4. 总结
当用户携带JWT访问需要权限的资源时,现在可以正常的校验权限了。
五、一些坑
- 写
RegisteredClient时注册那么多redirectUri是因为debug了很久,才发现获取授权码和获取JWT时,redirect_uri参数需要一致。 cors问题,spring secuity6似乎会一开始直接默认拒绝cors,导致跨域请求刚到gateway就寄了,到不了content微服务,即使content微服务配置了CORS的处理方案,也无济于事。

相关文章:
微服务OAuth 2.1认证授权Demo方案(Spring Security 6)
文章目录 一、介绍二、auth微服务代码1. SecurityConfig2. UserDetailsService3. 总结 三、gateway微服务代码1. 统一处理CORS问题 四、content微服务代码1. controller2. SecurityConfig3. 解析JWT Utils4. 总结 五、一些坑 书接上文 微服务OAuth 2.1认证授权可行性方案(Sprin…...
WSL使用Centos7发行版(rootfs)
参考 导入要与 WSL 一起使用的任何 Linux 发行版 microsoftWSL2 的 2.0 更新彻底解决网络问题install daemon and client binaries on linuxInstall Compose standalone WSL配置 在HOST中,编辑用户目录下的.wslconfig文件 我需要使用docker,测试发现a…...
ClickHouse--04--数据库引擎、Log 系列表引擎、 Special 系列表引擎
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1.数据库引擎1.1 Ordinary 默认数据库引擎1.2 MySQL 数据库引擎MySQL 引擎语法字段类型的映射 2.ClickHouse 表引擎3.Log 系列表引擎几种 Log 表引擎的共性是&#…...
docker的底层原理
概述:Docker的底层原理基于容器化技术,通过使用命名空间和控制组等技术实现资源的隔离与管理。 底层原理: 客户端-服务器架构:Docker采用的是Client-Server架构,其中Docker守护进程(daemon)运…...
有关光猫、路由器、交换机、网关的理解
前提 在了解计算机网络的过程中,出现了这四个名词:光猫、路由器、交换机、网络。有点模糊,查阅互联网相关资料,进行整理。如有错误,欢迎大家批评指正。 光猫 首先光猫是物理存在的,大家在家里应该都可以…...
图像旋转翻转变换
题目描述 给定m行n列的图像各像素点灰度值,对其依次进行一系列操作后,求最终图像。 其中,可能的操作及对应字符有如下四种: A:顺时针旋转90度; B:逆时针旋转90度; C:…...
网站常见的反爬手段及反反爬思路
摘要:介绍常见的反爬手段和反反爬思路,内容详细具体,明晰解释每一步,非常适合小白和初学者学习!!! 目录 一、明确几个概念 二、常见的反爬手段及反反爬思路 1、检测user-agent 2、ip 访问频率的限制 …...
GUI—— 从的可执行exe文件中提取jar包并反编译成Java
从exe4j生成的可执行文件中提取嵌入的jar包并反编译成Java代码,可以按照以下步骤操作: 步骤1:提取jar包 1.运行exe程序:首先启动exe4j生成的.exe可执行文件。当它运行时,通常会将内部包含的jar文件解压到临时目录下。…...
阿里云服务器镜像是什么?如何选择镜像?
阿里云服务器镜像怎么选择?云服务器操作系统镜像分为Linux和Windows两大类,Linux可以选择Alibaba Cloud Linux,Windows可以选择Windows Server 2022数据中心版64位中文版,阿里云服务器网aliyunfuwuqi.com来详细说下阿里云服务器操…...
C语言------一种思路解决实际问题
1.比赛名次问题 ABCDE参加比赛,那么每个人的名次都有5种可能,即1,2,3,4,5; int main() {int a 0;int b 0;int c 0;int d 0;int e 0;for (a 1; a < 5; a){for (b 1; b < 5; b){for…...
前端判断对象为空
一.使用JSON.stringify()方法: JSON.stringify() 是将一个JavaScript对象或值转换为JSON格式字符串,如果最终只得到一个{},就说明他是一个空对象 let obj1 {}; console.log(JSON.stringify(obj1) "{}"); //true 表示为空对象l…...
DS:栈和队列的相互实现
创作不易,感谢友友们三连!! 一、前言 栈和队列的相互实现是用两个栈去实现队列或者是用两个队列去实现栈,这样其实是把问题复杂化的,实际中没有什么应用价值,但是通过他们的相互实现可以让我们更加深入地理…...
Hack The Box-Office
端口扫描&信息收集 使用nmap对靶机进行扫描 nmap -sC -sV 10.10.11.3开放了80端口,并且注意到该ip对应的域名为office.htb,将其加入到hosts文件中访问之 注意到扫描出来的还有robots文件,经过尝试后只有administrator界面是可以访问的 …...
android aidl进程间通信封装通用实现
接上一篇的分析,今天继续 aidl复杂流程封装-CSDN博客 今天的任务就是将代码梳理下放进来 1 项目gradle配置: 需要将对应的代码放到各自的目录下,这里仅贴下关键内容,细节可以下载代码慢慢看 sourceSets { main { manifest.srcFile src/main/And…...
FL Studio 21.2.3.4004 All Plugins Edition Win/Mac音乐软件
FL Studio 21.2.3.4004 All Plugins Edition 是一款功能强大的音乐制作软件,提供了丰富的音频处理工具和插件,适用于专业音乐制作人和爱好者。该软件具有直观的用户界面,支持多轨道录音、混音和编辑,以及各种音频效果和虚拟乐器。…...
vivado RAM HDL Coding Guidelines
从编码示例下载编码示例文件。 块RAM读/写同步模式 您可以配置块RAM资源,为提供以下同步模式给定的读/写端口: •先读取:在加载新内容之前先读取旧内容。 •先写:新内容立即可供阅读先写也是众所周知的如通读。 •无变化&…...
springboot/ssm甘肃旅游服务平台Java在线旅游规划管理系统
springboot/ssm甘肃旅游服务平台Java在线旅游规划管理系统 开发语言:Java 框架:springboot(可改ssm) vue JDK版本:JDK1.8(或11) 服务器:tomcat 数据库:mysql 5.7&am…...
第三百五十四回
文章目录 1. 概念介绍2. 使用方法2.1 获取所有时区2.2 转换时区时间 3. 示例代码4. 内容总结 我们在上一章回中介绍了"分享一些好的Flutter站点"相关的内容,本章回中将介绍timezone包.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在…...
【Funny Game】 吃豆人
目录 【Funny Game】 吃豆人 吃豆人 文章所属专区 Funny Game 吃豆人 吃豆人,这款经典游戏如今依旧魅力四射。玩家需操控小精灵,在迷宫内吞噬所有豆子,同时避开狡猾的鬼怪。当吃完所有豆子后,便可消灭鬼怪,赢得胜利。…...
PyCharm - Run Debug 程序安全执行步骤
PyCharm - Run & Debug 程序安全执行步骤 1. Run2. DebugReferences 1. Run right click -> Run ‘simulation_data_gene…’ or Ctrl Shift F10 2. Debug right click -> Debug ‘simulation_data_gene…’ 在一个 PyCharm 工程下,存在多个 Pytho…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
DBLP数据库是什么?
DBLP(Digital Bibliography & Library Project)Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高,数据库文献更新速度很快,很好地反映了国际计算机科学学术研…...
【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
