💥《微服务核心技术》专栏已收录,欢迎订阅 💥
文章目录
基于上面Spring Security的几十个章节的学习,想必大家对Spring Security框架已经有了一定的了解。
那么我们开始从零开始搭建一套微服务的安全框架,希望其中的一些思想能给大家一些启发。
技术栈
- spiring security
- jwt
- redis
- nacos registry
- spring cloud gateway
- sentinel
- nacos config
- seata
- mybatis
- mybatis-plus
- xxl-job
- rocketmq
数据交互与实现
说到安全就会涉及认证和授权,那么对什么认证,对什么授权,于是引出如下几张表。
- 用户表
- 角色表
- 权限表
这也是典型的RBAC模型。
所有数据表以及项目源码可以搜公号【
步尔斯特
】回复「1024」即可获得。
有了数据表,我们来完善具体的代码实现。
数据交互的实现
部分代码:
packagecom.ossa.system.mapper;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.ossa.common.api.bean.User;importorg.springframework.stereotype.Component;@ComponentpublicinterfaceUserMapperextendsBaseMapper<User>{}
packagecom.ossa.system.service;importcom.baomidou.mybatisplus.extension.service.IService;importcom.ossa.common.api.bean.User;publicinterfaceUserServiceextendsIService<User>{}
packagecom.ossa.system.service.impl;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importcom.ossa.common.api.bean.User;importcom.ossa.system.mapper.UserMapper;importcom.ossa.system.service.UserService;importorg.springframework.stereotype.Service;@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{}
认证设计
通过登录操作完成认证,首先在配置类中应该放过登录的请求,我在这里实现一个匿名注解,会在后面给出代码和解析。
整体的设计思想:通过用户名和密码完成认证,确认用户可信,根据用户信息获取token,每次请求都带上token,完成校验。
- 获取传参的用户信息,用户名、密码等。
String password = authUser.getPassword();
- 将用户名、密码、封装成UsernamePasswordAuthenticationToken对象
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
- 获取认证管理器
AuthenticationManager authenticationManager = authenticationManagerBuilder.getObject();
- 认证
Authentication authentication = authenticationManager.authenticate(authenticationToken);
- 重写
UserDetailsService
,从数据库获取用户信息,以完成认证流程。 - 认证成功后,根据认证信息生成token
- 可将token作为key存入redis,用redis的过期时间代替jwt的token令牌的过期时间
- 获取用户身份信息
- 将token信息及用户信息返回。
代码实现:
@PostMapping("/login")@AnonymousAccesspublicResponseEntity<Object>login(@Validated@RequestBodyAuthUserDto authUser){// 密码解密// String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());String password = authUser.getPassword();// 将用户名、密码、封装成UsernamePasswordAuthenticationToken对象UsernamePasswordAuthenticationToken authenticationToken =newUsernamePasswordAuthenticationToken(authUser.getUsername(), password);// 获取认证管理器AuthenticationManager authenticationManager = authenticationManagerBuilder.getObject();// 认证核心方法Authentication authentication = authenticationManager.authenticate(authenticationToken);// // 认证成功之后,将认证信息保存至SecurityContext中// SecurityContextHolder.getContext().setAuthentication(authentication);// 根据认证信息生成tokenString token = tokenProvider.createToken(authentication);// 获取用户身份信息User one = userService.getOne(newQueryWrapper<User>().eq("username", authUser.getUsername()));UserDto userDto =newUserDto();BeanUtils.copyProperties(one,userDto);
stringRedisTemplate.opsForValue().set(properties.getOnlineKey()+ token,JSONUtil.toJsonStr(userDto), properties.getTokenValidityInSeconds()/1000,TimeUnit.SECONDS);// 返回 token 与 用户信息Map<String,Object> authInfo =newHashMap<String,Object>(2){{put("token", properties.getTokenStartWith()+ token);put("user", userDto);}};returnResponseEntity.ok(authInfo);}
packagecom.ossa.system.filter;importcom.baomidou.mybatisplus.core.conditions.query.QueryWrapper;importcom.ossa.common.api.bean.Privilege;importcom.ossa.common.api.bean.Role;importcom.ossa.common.api.bean.User;importcom.ossa.system.mapper.PrivilegeMapper;importcom.ossa.system.mapper.RoleMapper;importcom.ossa.system.service.UserService;importlombok.RequiredArgsConstructor;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.stereotype.Service;importjavax.persistence.EntityNotFoundException;importjava.util.ArrayList;importjava.util.List;importjava.util.stream.Collectors;@RequiredArgsConstructor@Service("userDetailsService")publicclassUserDetailsServiceImplimplementsUserDetailsService{privatefinalUserService userService;privatefinalRoleMapper roleMapper;privatefinalPrivilegeMapper privilegeMapper ;@OverridepublicUserDetailsloadUserByUsername(String username){User user;org.springframework.security.core.userdetails.User userDetails;try{
user = userService.getOne(newQueryWrapper<User>().eq("username", username));}catch(EntityNotFoundException e){// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsExceptionthrownewUsernameNotFoundException("", e);}if(user ==null){thrownewUsernameNotFoundException("");}else{List<Role> roles = roleMapper.listByUserId(user.getId());ArrayList<Privilege> privileges =newArrayList<>();
roles.forEach(role -> privileges.addAll(privilegeMapper.listByRoleId(role.getId())));ArrayList<String> tag =newArrayList<>();
privileges.forEach(p -> tag.add(p.getTag()));List<SimpleGrantedAuthority> collect = tag.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
userDetails =neworg.springframework.security.core.userdetails.User(username, user.getPassword(), collect);}return userDetails;}}
packagecom.ossa.system.filter;importcn.hutool.core.date.DateField;importcn.hutool.core.date.DateUtil;importcn.hutool.core.util.IdUtil;importcom.ossa.common.bean.SecurityProperties;importio.jsonwebtoken.*;importio.jsonwebtoken.io.Decoders;importio.jsonwebtoken.security.Keys;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.InitializingBean;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.security.authentication.UsernamePasswordAuthenticationToken;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.userdetails.User;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjava.security.Key;importjava.util.ArrayList;importjava.util.Date;importjava.util.concurrent.TimeUnit;@Slf4j@ComponentpublicclassTokenProviderimplementsInitializingBean{privatefinalSecurityProperties properties;privatefinalStringRedisTemplate stringRedisTemplate;publicstaticfinalString AUTHORITIES_KEY ="user";privateJwtParser jwtParser;privateJwtBuilder jwtBuilder;publicTokenProvider(SecurityProperties properties,StringRedisTemplate stringRedisTemplate){this.properties = properties;this.stringRedisTemplate = stringRedisTemplate;}@OverridepublicvoidafterPropertiesSet(){byte[] keyBytes =Decoders.BASE64.decode(properties.getBase64Secret());Key key =Keys.hmacShaKeyFor(keyBytes);
jwtParser =Jwts.parserBuilder().setSigningKey(key).build();
jwtBuilder =Jwts.builder().signWith(key,SignatureAlgorithm.HS512);}/**
* 创建Token 设置永不过期,
* Token 的时间有效性转到Redis 维护
*
* @param authentication /
* @return /
*/publicStringcreateToken(Authentication authentication){return jwtBuilder
// 加入ID确保生成的 Token 都不一致.setId(IdUtil.simpleUUID()).claim(AUTHORITIES_KEY, authentication.getName()).setSubject(authentication.getName()).compact();}/**
* 依据Token 获取鉴权信息
*
* @param token /
* @return /
*/AuthenticationgetAuthentication(String token){Claims claims =getClaims(token);User principal =newUser(claims.getSubject(),"******",newArrayList<>());returnnewUsernamePasswordAuthenticationToken(principal, token,newArrayList<>());}publicClaimsgetClaims(String token){return jwtParser
.parseClaimsJws(token).getBody();}/**
* @param token 需要检查的token
*/publicvoidcheckRenewal(String token){// 判断是否续期token,计算token的过期时间Long expire = stringRedisTemplate.getExpire(properties.getOnlineKey()+ token,TimeUnit.SECONDS);long time = expire ==null?0: expire *1000;Date expireDate =DateUtil.offset(newDate(),DateField.MILLISECOND,(int) time);// 判断当前时间与过期时间的时间差long differ = expireDate.getTime()-System.currentTimeMillis();// 如果在续期检查的范围内,则续期if(differ <= properties.getDetect()){long renew = time + properties.getRenew();
stringRedisTemplate.expire(properties.getOnlineKey()+ token, renew,TimeUnit.MILLISECONDS);}}publicStringgetToken(HttpServletRequest request){finalString requestHeader = request.getHeader(properties.getHeader());if(requestHeader !=null&& requestHeader.startsWith(properties.getTokenStartWith())){return requestHeader.substring(7);}returnnull;}}
授权设计
- 设计自己filter,拦截我们生成的token,如果token合法,则将token解析并封装成
UsernamePasswordAuthenticationToken
,存到安全上下文中 - 为了确保授权成功,我们需要将我们的filter放在
UsernamePasswordAuthenticationFilter
前执行
packagecom.ossa.system.filter;importcn.hutool.core.util.StrUtil;importcom.ossa.common.bean.SecurityProperties;importio.jsonwebtoken.ExpiredJwtException;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.context.SecurityContextHolder;importorg.springframework.util.StringUtils;importorg.springframework.web.filter.GenericFilterBean;importjavax.servlet.FilterChain;importjavax.servlet.ServletException;importjavax.servlet.ServletRequest;importjavax.servlet.ServletResponse;importjavax.servlet.http.HttpServletRequest;importjava.io.IOException;publicclassOssaTokenFilterextendsGenericFilterBean{privatestaticfinalLogger log =LoggerFactory.getLogger(OssaTokenFilter.class);privatefinalStringRedisTemplate stringRedisTemplate;privatefinalTokenProvider tokenProvider;privatefinalSecurityProperties properties;/**
* @param tokenProvider Token
* @param properties JWT
*/publicOssaTokenFilter(TokenProvider tokenProvider,SecurityProperties properties,StringRedisTemplate stringRedisTemplate){this.properties = properties;this.tokenProvider = tokenProvider;this.stringRedisTemplate = stringRedisTemplate;}@OverridepublicvoiddoFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throwsIOException,ServletException{HttpServletRequest httpServletRequest =(HttpServletRequest) servletRequest;String token =resolveToken(httpServletRequest);// 对于 Token 为空的不需要去查 Redisif(StrUtil.isNotBlank(token)){String s =null;try{
s = stringRedisTemplate.opsForValue().get(properties.getOnlineKey()+ token);}catch(ExpiredJwtException e){
log.error(e.getMessage());}if(s !=null&&StringUtils.hasText(token)){Authentication authentication = tokenProvider.getAuthentication(token);SecurityContextHolder.getContext().setAuthentication(authentication);// Token 续期
tokenProvider.checkRenewal(token);}}
filterChain.doFilter(servletRequest, servletResponse);}/**
* 初步检测Token
*
* @param request /
* @return /
*/privateStringresolveToken(HttpServletRequest request){String bearerToken = request.getHeader(properties.getHeader());if(StringUtils.hasText(bearerToken)&& bearerToken.startsWith(properties.getTokenStartWith())){// 去掉令牌前缀return bearerToken.replace(properties.getTokenStartWith(),"");}else{
log.debug("非法Token:{}", bearerToken);}returnnull;}}
核心配置
packagecom.ossa.common.security.core.config;importcom.ossa.common.api.anno.AnonymousAccess;importcom.ossa.common.api.bean.SecurityProperties;importcom.ossa.common.api.enums.RequestMethodEnum;importcom.ossa.common.security.core.filter.OssaTokenFilter;importcom.ossa.common.security.core.filter.TokenProvider;importcom.ossa.common.security.core.handler.JwtAccessDeniedHandler;importcom.ossa.common.security.core.handler.JwtAuthenticationEntryPoint;importlombok.RequiredArgsConstructor;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.http.HttpMethod;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.config.core.GrantedAuthorityDefaults;importorg.springframework.security.config.http.SessionCreationPolicy;importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;importorg.springframework.web.bind.annotation.RequestMethod;importorg.springframework.web.method.HandlerMethod;importorg.springframework.web.servlet.mvc.method.RequestMappingInfo;importorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;importjava.util.*;@Configuration@EnableWebSecurity@RequiredArgsConstructor@EnableGlobalMethodSecurity(prePostEnabled =true, securedEnabled =true)publicclassOssaSecurityConfigurerextendsWebSecurityConfigurerAdapter{privatefinalTokenProvider tokenProvider;privatefinalSecurityProperties properties;privatefinalApplicationContext applicationContext;privatefinalJwtAuthenticationEntryPoint authenticationErrorHandler;privatefinalJwtAccessDeniedHandler jwtAccessDeniedHandler;privatefinalStringRedisTemplate stringRedisTemplate;@BeanpublicAuthenticationManagerauthenticationManagerBean()throwsException{returnsuper.authenticationManagerBean();}@BeanGrantedAuthorityDefaultsgrantedAuthorityDefaults(){// 去除 ROLE_ 前缀returnnewGrantedAuthorityDefaults("");}@BeanpublicPasswordEncoderpasswordEncoder(){// 密码加密方式returnnewBCryptPasswordEncoder();}@Overrideprotectedvoidconfigure(HttpSecurity httpSecurity)throwsException{OssaTokenFilter customFilter =newOssaTokenFilter(tokenProvider, properties,stringRedisTemplate);// 搜寻匿名标记 url: @AnonymousAccessRequestMappingHandlerMapping requestMappingHandlerMapping =(RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");Map<RequestMappingInfo,HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();// 获取匿名标记Map<String,Set<String>> anonymousUrls =getAnonymousUrl(handlerMethodMap);
httpSecurity
// 禁用 CSRF.csrf().disable().addFilterBefore(customFilter,UsernamePasswordAuthenticationFilter.class)// 授权异常.exceptionHandling().authenticationEntryPoint(authenticationErrorHandler).accessDeniedHandler(jwtAccessDeniedHandler)// 防止iframe 造成跨域.and().headers().frameOptions().disable()// 不创建会话.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 静态资源等等.antMatchers(HttpMethod.GET,"/*.html","/**/*.html","/**/*.css","/**/*.js","/webSocket/**").permitAll()// swagger 文档.antMatchers("/swagger-ui.html").permitAll().antMatchers("/swagger-resources/**").permitAll().antMatchers("/webjars/**").permitAll().antMatchers("/*/api-docs").permitAll()// 文件.antMatchers("/avatar/**").permitAll().antMatchers("/file/**").permitAll()// 阿里巴巴 druid.antMatchers("/druid/**").permitAll()// 放行OPTIONS请求.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()// 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型// GET.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(newString[0])).permitAll()// POST.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(newString[0])).permitAll()// PUT.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(newString[0])).permitAll()// PATCH.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(newString[0])).permitAll()// DELETE.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(newString[0])).permitAll()// 所有类型的接口都放行.antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(newString[0])).permitAll()// 所有请求都需要认证.anyRequest().authenticated();}privateMap<String,Set<String>>getAnonymousUrl(Map<RequestMappingInfo,HandlerMethod> handlerMethodMap){Map<String,Set<String>> anonymousUrls =newHashMap<>(6);Set<String> get =newHashSet<>();Set<String> post =newHashSet<>();Set<String> put =newHashSet<>();Set<String> patch =newHashSet<>();Set<String> delete =newHashSet<>();Set<String> all =newHashSet<>();for(Map.Entry<RequestMappingInfo,HandlerMethod> infoEntry : handlerMethodMap.entrySet()){HandlerMethod handlerMethod = infoEntry.getValue();AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);if(null!= anonymousAccess){List<RequestMethod> requestMethods =newArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());RequestMethodEnum request =RequestMethodEnum.find(requestMethods.size()==0?RequestMethodEnum.ALL.getType(): requestMethods.get(0).name());switch(Objects.requireNonNull(request)){case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;case POST:
post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;case PUT:
put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;case PATCH:
patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;case DELETE:
delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;default:
all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());break;}}}
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);return anonymousUrls;}}
自定义权限注解
packagecom.ossa.common.security.core.config;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.context.SecurityContextHolder;importorg.springframework.stereotype.Service;importjava.util.Arrays;importjava.util.List;importjava.util.stream.Collectors;@Service(value ="pc")publicclassPermissionConfig{publicBooleancheck(String... permissions){// 获取当前用户的所有权限List<String> permission =SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());// 判断当前用户的所有权限是否包含接口上定义的权限return permission.contains("ADMIN")|| permission.contains("INNER")|| permission.contains("OFFICEIT")||Arrays.stream(permissions).anyMatch(permission::contains);}}
权限异常处理
packagecom.ossa.common.security.core.handler;importorg.springframework.security.access.AccessDeniedException;importorg.springframework.security.web.access.AccessDeniedHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;@ComponentpublicclassJwtAccessDeniedHandlerimplementsAccessDeniedHandler{@Overridepublicvoidhandle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException accessDeniedException)throwsIOException{//当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());}}
packagecom.ossa.common.security.core.handler;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.web.AuthenticationEntryPoint;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;@ComponentpublicclassJwtAuthenticationEntryPointimplementsAuthenticationEntryPoint{@Overridepublicvoidcommence(HttpServletRequest request,HttpServletResponse response,AuthenticationException authException)throwsIOException{// 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException ==null?"Unauthorized": authException.getMessage());}}
网关处理
网关只需要转发token到具体服务即可
在写这篇文章之前,此部分我已经升级成UAA认证授权中心,故没有此处相关代码。
内部流量处理
在内部流量的设计过程中,我们并不需要网关分发的token,故在此设计时,我只在feign的api接口处统一增加权限标识,并经过简单加密。
并在上述的自定的权限注解处放过该标识,不进行权限校验。
packagecom.ossa.feign.config;importcom.ossa.feign.util.EncryptUtil;importfeign.Logger;importfeign.Request;importfeign.RequestInterceptor;importfeign.RequestTemplate;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.context.request.RequestAttributes;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;importjava.util.Enumeration;importjava.util.Objects;importjava.util.concurrent.TimeUnit;/**
* @author issavior
*
* =================================
* **
* * 修改契约配置,支持Feign原生的注解
* * @return 返回 new Contract.Default()
* *
* @Bean
* public Contract feignContract(){
* return new Contract.Default();
* }
* ====================================
*/@ConfigurationpublicclassFeignClientConfigimplementsRequestInterceptor{/**
* 超时时间配置
*
* @return Request.Options
*/@BeanpublicRequest.Optionsoptions(){returnnewRequest.Options(5,TimeUnit.SECONDS,5,TimeUnit.SECONDS,true);}/**
* feign的日志级别
*
* @return 日志级别
*/@BeanpublicLogger.LevelfeignLoggerLevel(){returnLogger.Level.FULL;}/**
* 重写请求拦截器apply方法,循环请求头
*
* @param requestTemplate 请求模版
*/@Overridepublicvoidapply(RequestTemplate requestTemplate){RequestAttributes requestAttributes =RequestContextHolder.getRequestAttributes();if(Objects.isNull(requestAttributes)){return;}HttpServletRequest request =((ServletRequestAttributes)(requestAttributes)).getRequest();Enumeration<String> headerNames = request.getHeaderNames();if(headerNames !=null){while(headerNames.hasMoreElements()){String name = headerNames.nextElement();String values = request.getHeader(name);
requestTemplate.header(name, values);}}Enumeration<String> bodyNames = request.getParameterNames();// body.append("token").append("=").append(EncryptUtil.encodeUTF8StringBase64("INNER")).append("&");if(bodyNames !=null){while(bodyNames.hasMoreElements()){String name = bodyNames.nextElement();String values = request.getParameter(name);
requestTemplate.header(name,values);}}
requestTemplate.header("inner",EncryptUtil.encodeUTF8StringBase64("INNER"));}// /**// * 修改契约配置,支持Feign原生的注解// * @return 返回 new Contract.Default()// */// @Bean// public Contract feignContract(){// return new Contract.Default();// }}
热门专栏 欢迎订阅
- 《Java系核心技术》
- 《中间件核心技术》
- 《微服务核心技术》
- 《云原生核心技术》
版权归原作者 步尔斯特 所有, 如有侵权,请联系我们删除。