多维系统下单点登录之生产实践(2种方案3种实践)

image-20240921224910180

1、基于 Cookie 跨域与分布式 Session 的技术实践

1、XXL-SSO 整体价格

image-20240903211009366

2、实现原理剖析

  • 首次请求

    image-20240903211055497

  • 第二次请求

    image-20240903211122243

  • 跨域请求

    image-20240903211142333

  • 注销流程

    image-20240903211156398

3、案例演示

  • 首次登陆跳转至统一认证中心

访问:http://xxlssoclient1.com:8081/

image-20240921223004275

  • 登陆成功,写入 Cookie,保存 sessionId 信息

    image-20240921223046985

  • 跨域访问

    访问另外一个域名:http://xxlssoclient2.com:8081/

    自动登陆,并且写入 SessionId 至 Cookie 当中

    image-20240921223113529

4、代码实现剖析

采用 Debug 方式跟踪解析。关键断点:

  • 统一认证服务, 登陆入口:WebController 的 login 方法

  • 应用服务 Web 过滤器:XxlSsoWebFilter 的 doFilter 方法

  • 应用服务登陆注销:XxlSsoWebFilter 的 doFilter 方法,Line: 64

2、基于 Token 增强的微服务技术实践

1、整体流程

采用密码模式,基于 Token 增强的微服务应用实现方案

image-20240921223435827

2、代码实现

1、认证服务

认证服务配置AuthorizationServerConfig

/*** 自定义Client查询,可以修改表名, 字段等* @param clients*/
@
Override@ SneakyThrows
public void configure(ClientDetailsServiceConfigurer clients) {AuthClientDetailService clientDetailsService = newAuthClientDetailService(dataSource);clientDetailsService.setSelectClientDetailsSql(DEFAULT_SELECT_STATEMEN T);clientDetailsService.setFindClientDetailsSql(DEFAULT_FIND_STATEMENT);clients.withClientDetails(clientDetailsService);}/*** 防止申请token时出现401错误* @param oauthServer*/@
Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()").allowFormAuthenticationForClients();}/*** 认证服务配置* @param endpoints*/@
Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST).tokenStore(tokenStore()).tokenEnhancer(tokenEnhancer()).userDetailsService(authStockUserDetailService).authenticationManager(authenticationManager).reuseRefreshTokens(false);}/*** TokenStore实现方式, 采用Redis缓存* @return*/@
Bean
public TokenStore tokenStore() {RedisTokenStore tokenStore = newRedisTokenStore(redisConnectionFactory);tokenStore.setPrefix(GlobalConstants.OAUTH_PREFIX_KEY);tokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() {@Overridepublic String extractKey(OAuth2Authentication authentication) {return super.extractKey(authentication);}});return tokenStore;}/*** token增强处理, 支持扩展信息* @return TokenEnhancer*/@
Bean
public TokenEnhancer tokenEnhancer() {return (accessToken, authentication) - > {try {if (GlobalConstants.OAUTH_CLIENT_CREDENTIALS.equals(authentication.getOAuth2Request().getGrantType())) {return accessToken;}// 通过MAP 存储附加的信息final Map < String, Object > additionalInfo = newHashMap < > (16);OAuthTradeUser authTradeUser = (OAuthTradeUser)authentication.getUserAuthentication().getPrincipal();if (null != authTradeUser) {TradeUser tradeUser = authTradeUser.getTradeUser();// 需要扩充增加的用户附带信息additionalInfo.put(GlobalConstants.OAUTH_DETAILS_USER_ID,tradeUser.getId());additionalInfo.put(GlobalConstants.OAUTH_DETAILS_USERNAME,tradeUser.getName());additionalInfo.put(GlobalConstants.OAUTH_DETAILS_LOGIN_INFO,tradeUser.getEmail() + "|" + tradeUser.getAddress());additionalInfo.put("active", true);}// 将附加的信息记录保存, 形成增强的TOKEN((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);} catch (Exception e) {log.error(e.getMessage(), e);}return accessToken;};
}

用户信息服务接口AuthStockUserDetailServiceImpl

import com.itcast.bulls.stock.trade.oauth.repository.TradeUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
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.Service;@Service("authStockUserDetailService")
public class AuthStockUserDetailServiceImpl implements UserDetailsService {/*** 用户的数据层接口*/@Autowiredprivate TradeUserRepository tradeUserRepository;/*** 缓存管理接口*/@Autowiredprivate CacheManager cacheManager;/*** 根据用户账号获取用户对象接口* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String userNo) throws UsernameNotFoundException {// 1. 从缓存中查找用户对象Cache cache = cacheManager.getCache(GlobalConstants.OAUTH_KEY_STOCK_USER_DETAILS);if(null != cache && null != cache.get(userNo)) {return (UserDetails)cache.get(userNo).get();}// 2. 如果缓存未找到, 查询数据库TradeUser tradeUser = tradeUserRepository.findByUserNo(userNo);if(null == tradeUser) {throw new UsernameNotFoundException(userNo + " not valid! ");}// 3. 对用户信息做封装处理UserDetails userDetails = new OAuthTradeUser(tradeUser);// 4. 将封装的用户信息放入到缓存当中cache.put(userNo, userDetails);return userDetails;}
}

这是 Spring Security 提供的用户信息接口, 采用 OAUTH 的密码模式, 需要实现该接口的 loadUserByUsername 方法,为提升性能, 这里我们加入了 Spring Cache 缓存处理。

自定义用户信息: OAuthTradeUser

public class OAuthTradeUser extends User {private static final long serialVersionUUID = -1L;/*** 业务用户信息*/private TradeUser tradeUser;public OAuthTradeUser(TradeUser tradeUser) {// OAUTH2认证用户信息构造处理super(tradeUser.getUserNo(), tradeUser.getUserPwd(), (tradeUser.getStatus() == 0 ? true : false),true, true, (tradeUser.getStatus() == 0 ? true : false), Collections.emptyList());this.tradeUser = tradeUser;}public TradeUser getTradeUser() {return tradeUser;}
}

客户端信息服务接口AuthClientDetailService

public class AuthClientDetailService extends JdbcClientDetailsService {public AuthClientDetailService(DataSource dataSource) {super(dataSource);}/*** 重写原生方法支持redis缓存** @param clientId* @return* @throws InvalidClientException*/@Override@Cacheable(value = GlobalConstants.OAUTH_KEY_CLIENT_DETAILS, key = "#clientId", unless = "#result == null")public ClientDetails loadClientByClientId(String clientId) {return super.loadClientByClientId(clientId);}
}

这是 OAUTH 内置的客户端信息, 重新它是为了实现缓存, 减少数据库查询。

2、用户服务

认证配置ResourceSecurityConfigurer

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;import java.io.IOException;@Primary
@Order(90)
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceSecurityConfigurer implements ResourceServerConfigurer {@Autowiredprivate RemoteTokenServices remoteTokenServices;@Autowiredprivate RestTemplate restTemplate;/*** 远程调用, 采用RestTemplate方式* @param resources* @throws Exception*/@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {remoteTokenServices.setRestTemplate(restTemplate);resources.tokenServices(remoteTokenServices);}@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(accessTokenConverter());}@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey("test123");return converter;}/*** 资源服务的安全配置* @param http* @throws Exception*/@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/user/**").authenticated().and().formLogin().loginPage("/login").failureUrl("/login?error").defaultSuccessUrl("/home");}/*** RestTemplate配置* @return*/@Bean@Primary@LoadBalancedpublic RestTemplate lbRestTemplate() {RestTemplate restTemplate = new RestTemplate();restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {@Overridepublic void handleError(ClientHttpResponse response) throws IOException {if (response.getRawStatusCode() != HttpStatus.BAD_REQUEST.value()) {super.handleError(response);}}});return restTemplate;}}

用户服务为资源服务, 认证采用 RestTemplate 调用方式。 资源服务一定要开启@EnableResourceServer注解, @EnableGlobalMethodSecurity为方法级别安全控制。

提供获取用户增强信息接口StockUserController

import com.itcast.bulls.stock.common.exception.ComponentException;
import com.itcast.bulls.stock.entity.user.TradeUser;
import com.itcast.stock.common.web.vo.ApiRespResult;
import com.itcast.trade.bulls.stock.user.service.IStockUserService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;@RestController()
@RequestMapping("/user")
@Log4j2
public class StockUserController extends BaseController{@Autowiredprivate IStockUserService stockUserService;/*** 用户登陆接口* @param userNo* @param userPwd* @return*/@RequestMapping("/userLogin")public ApiRespResult userLogin(@RequestParam("userNo")String userNo, @RequestParam("userPwd") String userPwd) {ApiRespResult  result = null;try {// 用户登陆逻辑处理TradeUser tradeUser = stockUserService.userLogin(userNo, userPwd);result = ApiRespResult.success(tradeUser);}catch(ComponentException e) {log.error(e.getMessage(), e);result = ApiRespResult.error(e.geterrorCodeEnum());}catch(Exception e) {log.error(e.getMessage(), e);result = ApiRespResult.sysError(e.getMessage());}return result;}/*** 获取用户JWT扩展信息* @return*/@RequestMapping("/getJwtInfo")public ApiRespResult getUserEnhancer() {ApiRespResult  result = null;try {// 获取用户JWT扩展信息Map<String, Object> userAdditionalInfos = getUserAdditionalInfos();result = ApiRespResult.success(userAdditionalInfos);}catch(ComponentException e) {log.error(e.getMessage(), e);result = ApiRespResult.error(e.geterrorCodeEnum());}catch(Exception e) {log.error(e.getMessage(), e);result = ApiRespResult.sysError(e.getMessage());}return result;}
}
异常组件
import com.itcast.bulls.stock.common.exception.constants.IErrorCodeEnum;/*** 自定义组件异常*/
public class ComponentException extends AbstractException {/****/private static final long serialVersionUID = 2333790764399190094L;/*** 错误码枚举信息*/private IErrorCodeEnum errorCodeEnum;/*** 扩展的错误信息*/private String extendErrorMessage;public ComponentException(IErrorCodeEnum errorCodeEnum) {super(errorCodeEnum.getCode() + ":" + errorCodeEnum.getMessage());this.errorCodeEnum = errorCodeEnum;}public ComponentException(IErrorCodeEnum errorCodeEnum, String extendErrorMessage) {super(errorCodeEnum.getCode() + ":" + errorCodeEnum.getMessage() + "["+ extendErrorMessage + "]");this.errorCodeEnum = errorCodeEnum;this.extendErrorMessage = extendErrorMessage;}public IErrorCodeEnum geterrorCodeEnum() {return errorCodeEnum;}public void seterrorCodeEnum(IErrorCodeEnum errorCodeEnum) {this.errorCodeEnum = errorCodeEnum;}public String getExtendErrorMessage() {return extendErrorMessage;}public void setExtendErrorMessage(String extendErrorMessage) {this.extendErrorMessage = extendErrorMessage;}}
3、网关服务

全局过滤器StockRequestGlobalFilter

import io.netty.util.internal.StringUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@Component
@Log4j2
public class StockRequestGlobalFilter implements GlobalFilter, Ordered {/*** 通过filter来自定义配置转发信息* @param exchange* @param chain* @return*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String authentication = exchange.getRequest().getHeaders().getFirst("Authorization");if(!StringUtil.isNullOrEmpty(authentication)){log.info("enter stockRequestGlobalFilter filter method: " + authentication);exchange.getRequest().mutate().header("Authorization",authentication);}return chain.filter(exchange.mutate().build());}@Overridepublic int getOrder() {return -1000;}
}

这是自定义全局过滤器的实现, 防止 header 中的 Authorization 没有转发的问题

3、测试

申请 Toke

POST http://127.0.0.1:10680/oauth/token?
grant_type=password&username=admin&password=admin&scope=server
Accept: */*
Cache-Control: no-cache
Authorization: Basic YXBwOmFwcA==

返回 Token 信息:

{
"access_token": "cc5c4c1d-b519-458f-b338-ad4bd1ec06b0",
"token_type": "bearer",
"refresh_token": "86fec4ff-6c24-4171-a257-bf2d4e6bc30c",
"expires_in": 29749,
"scope": "server",
"login_info": "hekun1@itcast.cn|null",
"user_id": 1,
"user_name": "admin",
"active": true
}
1234567891011

获取增强用户信息

GET 127.0.0.1:10680/user/getUserEnhancer
Accept: */*
Cache-Control: no-cache
Authorization: Bearer cc5c4c1d-b519-458f-b338-ad4bd1ec06b0
1234

返回增强的用户信息:

{
"code": "SYS_200",
"msg": "成功",
"extendData": null,
"data": {
"login_info": "hekun1@itcast.cn|null",
"user_id": 1,
"user_name": "admin",
"active": true
},
"success": true
}
123456789101112

3、基于 JWT 扩展信息的微服务技术实践

1、整体流程

整体实现流程:采用密码模式,基于 JWT 扩展信息的微服务应用实践方案:

image-20240921224537421

2、代码实现

1、认证服务

认证服务系统配置AuthorizationServerConfig

 /*** 认证服务配置* @param endpoints*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {// JWT信息增强配置,采用链式配置, 包含JWT签名配置与JWT扩展信息配置。TokenEnhancerChain enhancerChain = new TokenEnhancerChain();List<TokenEnhancer> delegates = new ArrayList<>();delegates.add(jwtTokenEnhancer());delegates.add(accessTokenConverter());enhancerChain.setTokenEnhancers(delegates);endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).tokenStore(tokenStore()).userDetailsService(authStockUserDetailService).authenticationManager(authenticationManager).reuseRefreshTokens(false).tokenEnhancer(enhancerChain);}@Beanpublic JwtTokenEnhancer jwtTokenEnhancer() {return new JwtTokenEnhancer();}/*** TokenStore实现方式, 采用Redis缓存* @return*/@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(accessTokenConverter());}@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey("test123");return converter;}12345678910111213141516171819202122232425262728293031323334353637383940414243

认证服务采用 JWT 方式配置,JWT 配置,采用链式配置, 包含 JWT 签名配置与 JWT 扩展信息,JWT 签名设为 test123。这里采用自定义的增强 JWT 作实现。

JWT 增强实现类:JwtTokenEnhancer
public class JwtTokenEnhancer implements TokenEnhancer {/*** JWT扩展存储用户信息* @param accessToken* @param authentication* @return*/@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {Map<String, Object> additionalInfo = new HashMap<>();OAuthTradeUser authTradeUser = (OAuthTradeUser) authentication.getUserAuthentication().getPrincipal();if(null != authTradeUser) {TradeUser tradeUser = authTradeUser.getTradeUser();// 存储用户扩展信息additionalInfo.put(GlobalConstants.OAUTH_DETAILS_USER_ID, tradeUser.getId());additionalInfo.put(GlobalConstants.OAUTH_DETAILS_USERNAME, tradeUser.getName());additionalInfo.put(GlobalConstants.OAUTH_DETAILS_LOGIN_INFO, tradeUser.getEmail() + "|" + tradeUser.getAddress());((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);}return accessToken;}
}123456789101112131415161718192021222324

在 JWT 存储扩展用户信息,可以根据需要扩展不同的信息,但长度要有限制。

2、用户服务

认证配置ResourceSecurityConfigurer

@Primary
@Order(90)
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceSecurityConfigurer implements ResourceServerConfigurer {@Autowiredprivate RemoteTokenServices remoteTokenServices;@Autowiredprivate RestTemplate restTemplate;/*** 远程调用, 采用RestTemplate方式* @param resources* @throws Exception*/@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {remoteTokenServices.setRestTemplate(restTemplate);resources.tokenServices(remoteTokenServices);}@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(accessTokenConverter());}@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey("test123");return converter;}/*** 资源服务的安全配置* @param http* @throws Exception*/@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/user/**").authenticated().and().formLogin().loginPage("/login").failureUrl("/login?error").defaultSuccessUrl("/home");}/*** RestTemplate配置* @return*/@Bean@Primary@LoadBalancedpublic RestTemplate lbRestTemplate() {RestTemplate restTemplate = new RestTemplate();restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {@Overridepublic void handleError(ClientHttpResponse response) throws IOException {if (response.getRawStatusCode() != HttpStatus.BAD_REQUEST.value()) {super.handleError(response);}}});return restTemplate;}}

修改认证配置,采用 JWT 方式,设置签名为 test123,这里要和认证服务里面的签名保持一致,否则不能正常解析 JWT 信息。

增加获取 JWT 扩展信息的接口StockUserController
    /*** 获取用户JWT扩展信息* @return*/@RequestMapping("/getJwtInfo")public ApiRespResult getUserEnhancer() {ApiRespResult  result = null;try {// 获取用户JWT扩展信息Map<String, Object> userAdditionalInfos = getUserAdditionalInfos();result = ApiRespResult.success(userAdditionalInfos);}catch(ComponentException e) {log.error(e.getMessage(), e);result = ApiRespResult.error(e.geterrorCodeEnum());}catch(Exception e) {log.error(e.getMessage(), e);result = ApiRespResult.sysError(e.getMessage());}return result;}

自定义解析 JWT 数据增加依赖:

 <!-- JWT TOKEN 组件 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency>protected String getJwtToken() {// 1. 获取Request对象HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 2. 获取token信息String token = request.getHeader("Authorization");if(null != token) {token = token.replace(OAuth2AccessToken.BEARER_TYPE, "").trim();}return Jwts.parser().setSigningKey("test123".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token).getBody().toString();}

3、测试验证

申请 Token

POST http://127.0.0.1:10680/oauth/token?
grant_type=password&username=admin&password=admin&scope=server
Accept: */*
Cache-Control: no-cache
Authorization: Basic YXBwOmFwcA==

返回 Token 信息:

{
"access_token":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbl9pbmZvIjoiaGVrdW4xQGl0Y
2FzdC5jbnxudWxsIiwidXNlcl9pZCI6MSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6
WyJzZXJ2ZXIiXSwiZXhwIjoxNTk0ODQ5NTg1LCJqdGkiOiI2OWI4MWQzMi05MTk2LTQ5YmI
tOTU3ZC05YmRlZDM2OTY3ZTAiLCJjbGllbnRfaWQiOiJhcHAifQ.bFBKhPf0IYnJ9dpZG4a
PIlmpLECYwK-jTYTPHd2fc_M",
"token_type": "bearer",
"refresh_token":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbl9pbmZvIjoiaGVrdW4xQGl0Y
2FzdC5jbnxudWxsIiwidXNlcl9pZCI6MSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6
WyJzZXJ2ZXIiXSwiYXRpIjoiNjliODFkMzItOTE5Ni00OWJiLTk1N2QtOWJkZWQzNjk2N2U
wIiwiZXhwIjoxNTk3Mzk4Mzg1LCJqdGkiOiIyMjhkMmIyZS02YmRkLTQ1NzktYTljNy03ZG
I0NmZmMjA3ZjkiLCJjbGllbnRfaWQiOiJhcHAifQ.yHD0U1WtOH_SAGev3mPwD1L1_XucWv
tRpTT-upHNqTM",
"expires_in": 43199,
"scope": "server",
"login_info": "hekun1@itcast.cn|null",
"user_id": 1,
"user_name": "admin",
"jti": "69b81d32-9196-49bb-957d-9bded36967e0"
}

获取 JWT 扩展用户信息

GET 127.0.0.1:10680/user/getJwtInfo
Accept: */*
Cache-Control: no-cache
Authorization: Bearer
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbl9pbmZvIjoiaGVrdW4xQGl0Y2
FzdC5jbnxudWxsIiwidXNlcl9pZCI6MSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6W
yJzZXJ2ZXIiXSwiZXhwIjoxNTk0ODQ5NTg1LCJqdGkiOiI2OWI4MWQzMi05MTk2LTQ5YmIt
OTU3ZC05YmRlZDM2OTY3ZTAiLCJjbGllbnRfaWQiOiJhcHAifQ.bFBKhPf0IYnJ9dpZG4aP
IlmpLECYwK-jTYTPHd2fc_M

返回 JWT 扩展用户信息:

{
"code": "SYS_200",
"msg": "成功",
"extendData": null,
"data": {
"login_info": "hekun1@itcast.cn|null",
"user_id": 1,
"user_name": "admin",
"jti": "69b81d32-9196-49bb-957d-9bded36967e0"
},
"success": true
}

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL record 06 part

事务、存储过程 事务&#xff1a; MySQL的同步&#xff0c;同步是指 together done&#xff0c;要么一起前进&#xff0c;要么一起后退的意思。 注意&#xff0c;回滚 rollback 对已经提交 commit 的数据是无效的&#xff0c;也就是说&#xff0c;只能对没有被提交 commit …

【iOS】KVC的学习

【iOS】KVC的学习 文章目录 【iOS】KVC的学习前言KVC定义KVC设值KVC取值KVC使用keyPathKVC处理异常处理nil异常 KVC的一些应用修改动态的设置值实现高阶的消息传递 小结 前言 笔者简单学习了有关与KVC的相关内容&#xff0c;这里写一篇博客简单介绍一下相关内容。 KVC 定义 KV…

saas收银系统源码

1. 线下门店多样化收银 ①门店有社区小店、也会有大店&#xff0c;甚至还会有夫妻店&#xff0c;同时还要有Windows版和安卓版&#xff0c;需满足不同门店的收银需求。 ②支持Windows收银、安卓收银、无人自助收银、聚合码收银等&#xff0c;支持ai智能称重、收银称重一体机等…

『功能项目』QFrameWorkBug拖拽功能【66】

我们打开上一篇65QFrameWork道具栏物品生成的项目&#xff0c; 本章要做的事情是实现物品的拖拽功能 修改脚本&#xff1a;UISlot.cs 实现接口后编写脚本&#xff1a; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace QFramework {publi…

Netty+HTML5+Canvas 网络画画板实时在线画画

采用Html5的canvas做前端画画板&#xff0c;发送数据到后端Netty服务&#xff0c;实时转发笔迹数据&#xff0c;在线实时同步画笔轨迹&#xff0c;单击绿色小方块&#xff0c;保存画板的图片 页面&#xff1a; <!-- index.html --><!DOCTYPE html> <html> …

[Linux]:信号(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 1. 信号的阻塞 1.1 基本概念 信号被操作系统发送给进程之后&#xff0c;进程…

机器学习05-聚类算法(python)SC(轮廓系数)详解

# 导入必要的库 from sklearn.cluster import KMeans # 导入 KMeans 聚类算法 import matplotlib.pyplot as plt # 导入 matplotlib 用于绘图 from sklearn.datasets import make_blobs # 导入 make_blobs 用于生成模拟数据 from sklearn.metrics import silhouette_score …

react:组件通信

组件通信 父组件向子组件通信 function App() {return (<div><div>这是父组件</div><Child name"这是子组件" /></div>); }// 子组件 function Child(props) {return <div>{props.name}</div>; }props说明 props可以传…

浅谈计算机视觉的学习路径1

计算机视觉&#xff08;Computer Vision, CV&#xff09;是人工智能领域的一个重要分支&#xff0c;它的目标是使计算机能够像人类一样理解和处理图像和视频数据。 面向想要从事该方向的大学生&#xff0c;笔者这里给出以下是关于计算机视觉的学习路径建议&#xff1a; 简要了解…

Linux开发工具(git、gdb/cgdb)--详解

目录 一、Linux 开发工具分布式版本控制软件 git1、背景2、使用 git&#xff08;1&#xff09;预备工作——安装 git&#xff1a;&#xff08;2&#xff09;克隆远程仓库到本地&#xff08;3&#xff09;把需要提交的代码拷贝到本地仓库&#xff08;4&#xff09;提交本地仓库文…

一种新的电子邮件攻击方式:AiTM

新的攻击组利用合作伙伴组织之间的信任关系来绕过多重身份验证。 一种新的攻击方式开始出现&#xff0c;它利用合作伙伴组织之间的信任关系绕过多重身份验证。在一个利用不同组织之间关系的攻击中&#xff0c;攻击者成功地对四家或更多组织进行了商业电子邮件欺诈(BEC)攻击&…

VM-Ubantu中使用vscode头文件报错——解决办法

问题 系统中头文件明明存在但是却报错 解决方法 在报错的文件中点击&#xff0c;shift ctrl p选择Edit Configurations(JSON) 修改文件内容 原文件内容 修改之后的内容 {"configurations": [{"name": "Linux","includePath":…

计算机毕业设计推荐-基于python大数据的个性化图书数据可视化分析

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、个性化图书数据可视化分析-项…

828华为云征文 | 云服务器Flexus X实例:开源项目 LangChain 部署,实例测试

目录 一、LangChain 介绍 二、部署 LangChain 2.1 安装 langchain 2.2 安装 langchain_community 2.3 安装 qianfan 三、实例运行 3.1 Chat Models 3.2 LLMs 3.3 Embedding Models 四、总结 本篇文章主要通过 Flexus云服务器X实例 部署开源项目 LangChain&#xff0c…

【每日一题】LeetCode 2374.边积分最高节点(图、哈希表)

【每日一题】LeetCode 2374.边积分最高节点&#xff08;图、哈希表&#xff09; 题目描述 给定一个有向图&#xff0c;图中包含 n 个节点&#xff0c;节点编号从 0 到 n - 1。每个节点都有一个出边&#xff0c;指向图中的另一个节点。图由一个长度为 n 的整数数组 edges 表示…

【Linux学习】基本指令其一

命令行界面 命令行终端是一个用户界面&#xff0c;允许用户通过输入文本命令与计算机系统进行交互。 比如Windows下&#xff0c; 键入winR&#xff0c;然后输入cmd&#xff0c;就可以输入文本指令与操作系统交互了。 Windows有另一个命令行界面Powershell,它的功能比cmd更强大…

江协科技STM32学习- P15 TIM输出比较

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

【开源】LVGL+FreeRTOS 基于STM32F411CEU6的健康助手项目制作

视频演示 【开源】LVGLFreeRTOS 基于STM32F411的智能健康助手小项目 网盘链接在最底下&#xff01;&#xff01;&#xff01;无套路&#xff01;&#xff01;&#xff01;直接分享&#xff01;&#xff01;&#xff01; 硬件介绍 STM32F411CEU6 主控 TFT 1.8inch 显示屏 DTH…

WebGL缓冲区

一、缓冲区对象 缓冲区对象时WebGL系统中的一块内存区域&#xff0c;可以一次性地向缓冲区对象中填充大量的顶点数据&#xff0c;然后将这些数据保存其中&#xff0c;供顶点着色器使用。 类型化数组 这样程序可以预知数组中的类型&#xff0c;提高性能 类型描述Int8Array8位…

数据湖 Data Lake-概述

Data Lake 1. 数据湖的定义 数据湖是一种存储系统&#xff0c;用于集中存储大量的原始数据&#xff0c;可以按数据本来的原始格式进行存储&#xff0c;用户可以在需要时提取和分析这些数据。 A data lake is a centralized repository designed to hold vast volumes of data …