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」即可获得。

![在这里插入图片描述](https://img-
blog.csdnimg.cn/cc75371041234ad9b2dcce17e4e3e8b5.png#pic_center)
有了数据表,我们来完善具体的代码实现。

数据交互的实现

在这里插入图片描述

部分代码:

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<User> {
}




package com.ossa.system.service;

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

public interface UserService extends IService<User> {
}




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<UserMapper, User>  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<Object> 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<User>().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<String, Object> authInfo = new HashMap<String, Object>(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<User>().eq("username", username));

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

            List<Role> roles = roleMapper.listByUserId(user.getId());

            ArrayList<Privilege> privileges = new ArrayList<>();

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

            ArrayList<String> tag = new ArrayList<>();

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

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

权限异常处理

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()
 *      *
 *  &#064;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<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();
//    }
}

热门专栏 欢迎订阅

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

题外话

初入计算机行业的人或者大学计算机相关专业毕业生,很多因缺少实战经验,就业处处碰壁。下面我们来看两组数据:

2023届全国高校毕业生预计达到1158万人,就业形势严峻;

国家网络安全宣传周公布的数据显示,到2027年我国网络安全人员缺口将达327万。

一方面是每年应届毕业生就业形势严峻,一方面是网络安全人才百万缺口。

6月9日,麦可思研究2023年版就业蓝皮书(包括《2023年中国本科生就业报告》《2023年中国高职生就业报告》)正式发布。

2022届大学毕业生月收入较高的前10个专业

本科计算机类、高职自动化类专业月收入较高。2022届本科计算机类、高职自动化类专业月收入分别为6863元、5339元。其中,本科计算机类专业起薪与2021届基本持平,高职自动化类月收入增长明显,2022届反超铁道运输类专业(5295元)排在第一位。

具体看专业,2022届本科月收入较高的专业是信息安全(7579元)。对比2018届,电子科学与技术、自动化等与人工智能相关的本科专业表现不俗,较五年前起薪涨幅均达到了19%。数据科学与大数据技术虽是近年新增专业但表现亮眼,已跻身2022届本科毕业生毕业半年后月收入较高专业前三。五年前唯一进入本科高薪榜前10的人文社科类专业——法语已退出前10之列。

“没有网络安全就没有国家安全”。当前,网络安全已被提升到国家战略的高度,成为影响国家安全、社会稳定至关重要的因素之一。

网络安全行业特点

1、就业薪资非常高,涨薪快 2022年猎聘网发布网络安全行业就业薪资行业最高人均33.77万!

img

2、人才缺口大,就业机会多

2019年9月18日《中华人民共和国中央人民政府》官方网站发表:我国网络空间安全人才 需求140万人,而全国各大学校每年培养的人员不到1.5W人。猎聘网《2021年上半年网络安全报告》预测2027年网安人才需求300W,现在从事网络安全行业的从业人员只有10W人。
img

行业发展空间大,岗位非常多

网络安全行业产业以来,随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…

职业增值潜力大

网络安全专业具有很强的技术特性,尤其是掌握工作中的核心网络架构、安全技术,在职业发展上具有不可替代的竞争优势。

随着个人能力的不断提升,所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟,升值空间一路看涨,这也是为什么受大家欢迎的主要原因。

从某种程度来讲,在网络安全领域,跟医生职业一样,越老越吃香,因为技术愈加成熟,自然工作会受到重视,升职加薪则是水到渠成之事。

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

1.学习路线图

行业发展空间大,岗位非常多

网络安全行业产业以来,随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…

职业增值潜力大

网络安全专业具有很强的技术特性,尤其是掌握工作中的核心网络架构、安全技术,在职业发展上具有不可替代的竞争优势。

随着个人能力的不断提升,所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟,升值空间一路看涨,这也是为什么受大家欢迎的主要原因。

从某种程度来讲,在网络安全领域,跟医生职业一样,越老越吃香,因为技术愈加成熟,自然工作会受到重视,升职加薪则是水到渠成之事。

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

1.学习路线图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

内容涵盖了网络安全法学习、网络安全运营等保测评、渗透测试基础、漏洞详解、计算机基础知识等,都是网络安全入门必知必会的学习内容。

3.技术文档和电子书

技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。

4.工具包、面试题和源码

“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。

还有我视频里讲的案例源码和对应的工具包,需要的话也可以拿走。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…

img

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

序言 自从Martin Fowler对微服务作出定义之后,微服务便火遍大江南北, 网上出现很多文章来描述它的好处,也有很多文章来说明它的弊端。这便 让很多小伙伴无所适从,微服务究竟是什么,要不要使用微服务架构,怎 么实施微服务架构?我一直认为,微服务架构只是新瓶装老酒,这老酒就 是模块化。如果在做系统设计时,已经把模块化做得很好,转型微服务只 是顺理成章的事。如果模块化都做不好,转型微服务只会来灾难。 2014 年底,我们团队意识到 Docker 技术可以帮我们大幅度提高软 件产品的性能,降低硬件的投入,提高运维效率,便开始着手研发基于 Docker 的 PaaS 平台。随后,很快发现,PaaS 平台只是解决了软件生命周 期后半部分(运维)的问题,就思考能否通过 Docker 技术来提高开发团 队的效率。例如,降低团队成员流动来的风险,提高多团队协作的效率, 找到组件或知识积累的方法,让同一个软件产品能够适应不同客户的定制 化需求,等等。从此,就与微服务结下了不解之缘。这些目标确定后,通 用的PaaS平台的研发目标也就变成了解决以上问题的微服务平台的研发, 以及后来的青柳云平台本身的微服务化的实践。 在做微服务架构技术选型的时候,我们以“无侵入”和“社区活跃” 为最主要的考量点,也只有这样,将来在升级为原子服务架构、量子服务 架构的时候,甚至是恢复成单体架构的时候,代价才是最小的。所以,在 3 InfoQ 中文站 为数不多的可选项中,我们拥抱了 Spring Cloud。最后的结果就是使用 基于 Docker 的微服务平台进行开发和运行运维支撑,使用 Spring Cloud 进行业务系统开发,两者相互独立,并可被独立替换。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值