2万字带你从0到1搭建一套企业级微服务安全框架

??《微服务核心技术》专栏已收录,欢迎订阅 ??

文章目录

  • 技术栈
  • 数据交互与实现
  • 认证设计
  • 授权设计
  • 核心配置
  • 自定义权限注解
  • 权限异常处理
  • 网关处理
  • 内部流量处理

基于上面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」即可获得。

在这里插入图片描述
有了数据表,我们来完善具体的代码实现。

数据交互的实现

在这里插入图片描述

部分代码:

package com.ossa.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ossa.common.api.bean.User;
import org.springframework.stereotype.Component;

@Component
public interface UserMapper extends BaseMapper {
}



package com.ossa.system.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.ossa.common.api.bean.User;

public interface UserService extends IService {
}



package com.ossa.system.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ossa.common.api.bean.User;
import com.ossa.system.mapper.UserMapper;
import com.ossa.system.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl  implements UserService {
}

认证设计

通过登录操作完成认证,首先在配置类中应该放过登录的请求,我在这里实现一个匿名注解,会在后面给出代码和解析。

整体的设计思想:通过用户名和密码完成认证,确认用户可信,根据用户信息获取token,每次请求都带上token,完成校验。

  1. 获取传参的用户信息,用户名、密码等。String password = authUser.getPassword();
  2. 将用户名、密码、封装成UsernamePasswordAuthenticationToken对象UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
  3. 获取认证管理器AuthenticationManager authenticationManager = authenticationManagerBuilder.getObject();
  4. 认证Authentication authentication = authenticationManager.authenticate(authenticationToken);
  5. 重写UserDetailsService,从数据库获取用户信息,以完成认证流程。
  6. 认证成功后,根据认证信息生成token
  7. 可将token作为key存入redis,用redis的过期时间代替jwt的token令牌的过期时间
  8. 获取用户身份信息
  9. 将token信息及用户信息返回。

代码实现:

    @PostMapping("/login")
    @AnonymousAccess
    public ResponseEntity login(@Validated @RequestBody AuthUserDto authUser){
        // 密码解密
//        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
        String password = authUser.getPassword();
        // 将用户名、密码、封装成UsernamePasswordAuthenticationToken对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
        // 获取认证管理器
        AuthenticationManager authenticationManager = authenticationManagerBuilder.getObject();
        // 认证核心方法
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
//        // 认证成功之后,将认证信息保存至SecurityContext中
//        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 根据认证信息生成token
        String token = tokenProvider.createToken(authentication);
        // 获取用户身份信息
        User one = userService.getOne(new QueryWrapper().eq("username", authUser.getUsername()));
        UserDto userDto = new UserDto();
        BeanUtils.copyProperties(one,userDto);

        stringRedisTemplate.opsForValue().set(properties.getOnlineKey() + token, JSONUtil.toJsonStr(userDto), properties.getTokenValidityInSeconds()/1000, TimeUnit.SECONDS);

        // 返回 token 与 用户信息
        Map authInfo = new HashMap(2) {{
            put("token", properties.getTokenStartWith() + token);
            put("user", userDto);
        }};
        return ResponseEntity.ok(authInfo);
    }


package com.ossa.system.filter;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ossa.common.api.bean.Privilege;
import com.ossa.common.api.bean.Role;
import com.ossa.common.api.bean.User;
import com.ossa.system.mapper.PrivilegeMapper;
import com.ossa.system.mapper.RoleMapper;
import com.ossa.system.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;

import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserService userService;
    private final RoleMapper roleMapper;
    private final PrivilegeMapper privilegeMapper ;


    @Override
    public UserDetails loadUserByUsername(String username) {
        User user;
        org.springframework.security.core.userdetails.User userDetails;
        try {
            user = userService.getOne(new QueryWrapper().eq("username", username));

        } catch (EntityNotFoundException e) {
            // SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
            throw new UsernameNotFoundException("", e);
        }
        if (user == null) {
            throw new UsernameNotFoundException("");
        } else {

            List roles = roleMapper.listByUserId(user.getId());

            ArrayList privileges = new ArrayList<>();

            roles.forEach(role -> privileges.addAll(privilegeMapper.listByRoleId(role.getId())));

            ArrayList tag = new ArrayList<>();

            privileges.forEach(p -> tag.add(p.getTag()));

            List collect = tag.stream().map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());
            userDetails = new org.springframework.security.core.userdetails.User(username, user.getPassword(), collect);

        }
        return userDetails;
    }
}


package com.ossa.system.filter;


import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.ossa.common.bean.SecurityProperties;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class TokenProvider implements InitializingBean {

    private final SecurityProperties properties;
    private final StringRedisTemplate stringRedisTemplate;
    public static final String AUTHORITIES_KEY = "user";
    private JwtParser jwtParser;
    private JwtBuilder jwtBuilder;

    public TokenProvider(SecurityProperties properties, StringRedisTemplate stringRedisTemplate) {
        this.properties = properties;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public void afterPropertiesSet() {
        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 /
     */
    public String createToken(Authentication authentication) {
        return jwtBuilder
                // 加入ID确保生成的 Token 都不一致
                .setId(IdUtil.simpleUUID())
                .claim(AUTHORITIES_KEY, authentication.getName())
                .setSubject(authentication.getName())
                .compact();
    }

    /**
     * 依据Token 获取鉴权信息
     *
     * @param token /
     * @return /
     */
    Authentication getAuthentication(String token) {
        Claims claims = getClaims(token);
        User principal = new User(claims.getSubject(), "******", new ArrayList<>());
        return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>());
    }

    public Claims getClaims(String token) {
        return jwtParser
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * @param token 需要检查的token
     */
    public void checkRenewal(String token) {
        // 判断是否续期token,计算token的过期时间
        Long expire = stringRedisTemplate.getExpire(properties.getOnlineKey() + token, TimeUnit.SECONDS);
        long time = expire == null ? 0 : expire * 1000;
        Date expireDate = DateUtil.offset(new Date(), 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);
        }
    }

    public String getToken(HttpServletRequest request) {
        final String requestHeader = request.getHeader(properties.getHeader());
        if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
            return requestHeader.substring(7);
        }
        return null;
    }
}
 
  

授权设计

  1. 设计自己filter,拦截我们生成的token,如果token合法,则将token解析并封装成UsernamePasswordAuthenticationToken,存到安全上下文中

  2. 为了确保授权成功,我们需要将我们的filter放在UsernamePasswordAuthenticationFilter前执行

    package com.ossa.system.filter;

    import cn.hutool.core.util.StrUtil;
    import com.ossa.common.bean.SecurityProperties;
    import io.jsonwebtoken.ExpiredJwtException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.util.StringUtils;
    import org.springframework.web.filter.GenericFilterBean;

    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;

    public class OssaTokenFilter extends GenericFilterBean {
    private static final Logger log = LoggerFactory.getLogger(OssaTokenFilter.class);

    private final StringRedisTemplate stringRedisTemplate;
    
    private final TokenProvider tokenProvider;
    private final SecurityProperties properties;
    
    /**
     * @param tokenProvider     Token
     * @param properties        JWT
     */
    public OssaTokenFilter(TokenProvider tokenProvider, SecurityProperties properties, StringRedisTemplate stringRedisTemplate) {
        this.properties = properties;
        this.tokenProvider = tokenProvider;
        this.stringRedisTemplate = stringRedisTemplate;
    }
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String token = resolveToken(httpServletRequest);
        // 对于 Token 为空的不需要去查 Redis
        if (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 /
     */
    private String resolveToken(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);
        }
        return null;
    }
    

    }

核心配置

package com.ossa.common.security.core.config;

import com.ossa.common.api.anno.AnonymousAccess;
import com.ossa.common.api.bean.SecurityProperties;
import com.ossa.common.api.enums.RequestMethodEnum;
import com.ossa.common.security.core.filter.OssaTokenFilter;
import com.ossa.common.security.core.filter.TokenProvider;
import com.ossa.common.security.core.handler.JwtAccessDeniedHandler;
import com.ossa.common.security.core.handler.JwtAuthenticationEntryPoint;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.*;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class OssaSecurityConfigurer extends WebSecurityConfigurerAdapter {
    private final TokenProvider tokenProvider;
    private final SecurityProperties properties;
    private final ApplicationContext applicationContext;
    private final JwtAuthenticationEntryPoint authenticationErrorHandler;

    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    private final StringRedisTemplate stringRedisTemplate;

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
        // 去除 ROLE_ 前缀
        return new GrantedAuthorityDefaults("");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 密码加密方式
        return new BCryptPasswordEncoder();

    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        OssaTokenFilter customFilter = new OssaTokenFilter(tokenProvider, properties,stringRedisTemplate);

        // 搜寻匿名标记 url: @AnonymousAccess
        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
        Map handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
        // 获取匿名标记
        Map> 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(new String[0])).permitAll()
                // POST
                .antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
                // PUT
                .antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
                // PATCH
                .antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
                // DELETE
                .antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
                // 所有类型的接口都放行
                .antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()

                // 所有请求都需要认证
                .anyRequest().authenticated();
    }

    private Map> getAnonymousUrl(Map handlerMethodMap) {
        Map> anonymousUrls = new HashMap<>(6);
        Set get = new HashSet<>();
        Set post = new HashSet<>();
        Set put = new HashSet<>();
        Set patch = new HashSet<>();
        Set delete = new HashSet<>();
        Set all = new HashSet<>();
        for (Map.Entry infoEntry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
            if (null != anonymousAccess) {
                List requestMethods = new ArrayList<>(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;
    }
}

自定义权限注解

package com.ossa.common.security.core.config;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service(value = "pc")
public class PermissionConfig {

    public Boolean check(String... permissions) {
        // 获取当前用户的所有权限
        List 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);
    }
}

权限异常处理

package com.ossa.common.security.core.handler;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        //当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
        response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
    }
}




package com.ossa.common.security.core.handler;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        // 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());
    }
}

网关处理

网关只需要转发token到具体服务即可

在写这篇文章之前,此部分我已经升级成UAA认证授权中心,故没有此处相关代码。

内部流量处理

在内部流量的设计过程中,我们并不需要网关分发的token,故在此设计时,我只在feign的api接口处统一增加权限标识,并经过简单加密。

并在上述的自定的权限注解处放过该标识,不进行权限校验。

package com.ossa.feign.config;

import com.ossa.feign.util.EncryptUtil;
import feign.Logger;
import feign.Request;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author issavior
 *
 * =================================
 *     **
 *      * 修改契约配置,支持Feign原生的注解
 *      * @return 返回 new Contract.Default()
 *      *
 *  @Bean
 *  public Contract feignContract(){
 *      return new Contract.Default();
 *  }
 * ====================================
 */
@Configuration
public class FeignClientConfig implements RequestInterceptor {

    /**
     * 超时时间配置
     *
     * @return Request.Options
     */
    @Bean
    public Request.Options options() {

        return new Request.Options(5, TimeUnit.SECONDS,
                5, TimeUnit.SECONDS, true);
    }

    /**
     * feign的日志级别
     *
     * @return 日志级别
     */
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

    /**
     * 重写请求拦截器apply方法,循环请求头
     *
     * @param requestTemplate 请求模版
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(requestAttributes)) {
            return;
        }
        HttpServletRequest request = ((ServletRequestAttributes) (requestAttributes)).getRequest();
        Enumeration headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                requestTemplate.header(name, values);
            }
        }
        Enumeration 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();
//    }
}

热门专栏 欢迎订阅

  1. 《Java系核心技术》
  2. 《中间件核心技术》
  3. 《微服务核心技术》
  4. 《云原生核心技术》

你可能感兴趣的:(面试,学习路线,阿里巴巴,android,前端,后端)