SpringCloud(一) 微服务安全实战 API安全机制

API安全

    常见的安全机制:

    风险与安全机制的对应关系:

    访问控制处理:

        访问控制

    ACL:Access Control Lists:简单易用,实现容易。无法满足复杂的业务需求,不易管理

    RBAC:Role Based Access Control:引入角色(客服)概念,简化管理。开发起来相对于ACL复杂

    1.判断当前请求是否需要身份认证 没有返回401

    2.判断有没有权限 没有返回403

    流控:

        为防止大流量把系统压死,一般在反向代理和负载均衡处处理。简单的限流器:

<!--        做流量控制pom -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>28.0-jre</version>
        </dependency>
<!--     end         -->

/**
 * @author aric
 * @create 2021-03-30-18:00
 * @fun 限流器
 */
//继承OncePerRequestFilter过滤只会过滤一次
@Component
@Order(1)  //执行顺序
public class RateLimitFilter extends OncePerRequestFilter {

    private RateLimiter rateLimiter = RateLimiter.create(1); //1s允许1个连接的过滤器

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if(rateLimiter.tryAcquire()){
            //没达到限流
            filterChain.doFilter(request,response);
        }else{
            //太多请求
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("too many request!!!");
            response.getWriter().flush();
            return;
        }
    }
}

    认证:

        不管有没有成功,都继续进行

        HttpBasic认证:

 请求部携带加密后的认证信息

pom文件
<!--        认证需要字符串处理工具-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
<!--        end-->

BasicAuthecationFilter.java:
/**
 * @author aric
 * @create 2021-03-30-19:20
 * @fun 认证字符串过滤器  一般开发中不用,调用密码加密验证很耗CPU,一般用TOKEN
 */
@Component
@Order(2)  //执行顺序
public class BasicAuthecationFilter extends OncePerRequestFilter {

    @Autowired
    private UserRepository userRepository;

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        //只有请求头带有认证信息才能进入
        if(StringUtils.isNotBlank(authHeader)){
            //拿到编译后的认证后的字符串
            String token64 = StringUtils.substringAfterLast(authHeader,"Basic ");
            String token = new String(Base64Utils.decodeFromString(token64));
            //根据“:”拆成2个字符串
            String [] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(token,":");

            String username = items[0];
            String password = items[1];

            User user = userRepository.findByUsername(username);
            if(user != null && StringUtils.equals(password,user.getPassword())){
                request.setAttribute("user",user);
            }
        }
        //请求头没有携带认证信息,继续往下走
        filterChain.doFilter(request,response);
    }
}

注:测试时发送请求头部必须携带认证信息

Controller.java:
@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/{id}")
    public UserInfo get(@PathVariable Long id,HttpServletRequest request){
        //用户只能获取自己的用户信息
        User user = (User)request.getAttribute("user");
        if(user == null || !user.getId().equals(id)){
            throw new RuntimeException("身份认证信息异常,获取用户信息失败。");
        }
        return userService.get(id); //做查询操作即可
    }
}

    校验:

        一般在接口层面或数据库层面。

@NotBlank(message = "用户名不能为空")  //为空校验  应用层面
@Column(unique = true)  //不能重复  数据库层面,会改数据库配置
private String username;

@PostMapping
public UserInfo create(@RequestBody @Validated UserInfo user){ //@Validated:认证规则生效注解
    return userService.create(user);
}

    密码加密

        如果输入密码明文能加密成密文串则成功,不能再解密成明文,再用随机字符串做盐加在密文串中。

<!--        加密相关-->
        <dependency>
            <groupId>com.lambdaworks</groupId>
            <artifactId>scrypt</artifactId>
            <version>1.4.0</version>
        </dependency>
<!--        end-->

@Component
public class BasicAuthecationFilter extends OncePerRequestFilter {
    /********

            if(user != null && SCryptUtil.check(password,user.getPassword())){  //加密后的密码验证
                request.setAttribute("user",user);
            }
    ********/
}

public class UserServiceImpl implements UserService {
    public UserInfo create(UserInfo info) {
        User user = new User();
        BeanUtils.copyProperties(info,user);
        //加密,参数主要是控制加密位数,控制CPU使用频率
        user.setPassword(SCryptUtil.scrypt(user.getPassword(),32768,8,1));
        userRepository.save(user);
        info.setId(user.getId());
        return info;
    }
}

    Https访问:

        可以验证双方的身份,在数据传输的过程中,对数据进行记录,封装,加密。

#    //Https安全,RSA加密配置
server:
  ssl:
    key-store: classpath:xuyu.key
    key-store-password: 123456
    key-password: 123456

    审计日志处理:

        日志一定要持久化。

        一般拦截器关系:

所以我们在Interceptor做审计日志功能。

        日志类:

/**
 * @author aric
 * @create 2021-03-31-11:32
 * @fun
 */
@EntityListeners(AuditingEntityListener.class)  //增加监听器
@Data
public class AuditLog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String method;
    private String path;
    private Integer status;
    @CreatedBy  //获取上传人 须在SecurityConfig中配置
    private String username;
    @Temporal(TemporalType.TIMESTAMP)  //时间戳注解
    @CreatedDate  //创建时间注解,jpa做判断
    private Date createdTime;
    @Temporal(TemporalType.TIMESTAMP)
    @LastModifiedDate
    private Date modifyTime;
}

        日志Jpa

/**
 * @author aric
 * @create 2021-03-31-11:37
 * @fun
 */
public interface AuditLogRepository extends JpaSpecificationExecutor<AuditLog>, CrudRepository<AuditLog,Long> {
}

        记录日志的拦截器:

/**
 * @author aric
 * @create 2021-03-31-17:23
 * @fun  记录日志
 */
@Component
public class AuditLogInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private AuditLogRepository auditLogRepository;

    //之前记录日志
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception{
        AuditLog log = new AuditLog();
        log.setMethod(request.getMethod());
        log.setPath(request.getRequestURI());

        User user = (User) request.getAttribute("user");
        if(user != null){
            log.setUsername(user.getUsername());
        }
        auditLogRepository.save(log);
        request.setAttribute("auditLogId",log.getId());
        return true;  //返回false不会被执行
    }

    //postHandler是处理成功才会调用,不要覆盖

    //afterCompletion不管处理成功与否都会调用
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        Long auditLofId = (Long)request.getAttribute("auditLofId");

        AuditLog log = auditLogRepository.findById(auditLofId).get();

        log.setStatus(response.getStatus());

        auditLogRepository.save(log);
    }
}

        错误处理接口:

/**
 * @author aric
 * @create 2021-03-31-18:12
 * @fun 接管错误处理(比如返回状态码200的错误处理),不再走spring默认的处理
 */
@RestControllerAdvice
public class ErrorHandler {

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)  //将所有异常代码转化为服务器异常
    @ExceptionHandler(Exception.class)
    public Map<String, Object> handler(Exception ex){
        HashMap<String, Object> info = new HashMap<>();
        info.put("message",ex.getMessage());
        info.put("time",new Date().getTime());
        return info;
    }
}

        Security配置类:

/**
 * @author aric
 * @create 2021-03-31-17:56
 * @fun  让AuditLogInterceptor生效
 */
@Configuration
@EnableJpaAuditing  //总开关
public class SecurityConfig implements WebMvcConfigurer {

    @Autowired
    private AuditLogInterceptor auditLogInterceptor;

    //执行顺序:先add先执行
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(auditLogInterceptor);  //还可以add属性只对某些请求生效
    }

    //在AuditLog中获取上传人 ->注解@CreatedBy实现
    @Bean
    public AuditorAware<String> auditorAware(){
        return new AuditorAware<String>() {
            @Override
            public Optional<String> getCurrentAuditor() {
                return Optional.of("xuyu");
            }
        };
    }
}

    授权

        登录安全:

        1.Servlet实现:

        优点:提升用户体验,比在客户端只保存信息安全,真正的信息保存在服务器,所有的内容都在Servlet容器中实现好了,只需要getSession获取和SetSession就好了

        缺点:只针对浏览器才支持,不支持app和第三方服务,当服务器传送cookie时会被劫持,不是很安全,服务器一般不是一台,如果请求落在另一台,需要重新登录

        登录Controller:

/**
 * @author aric
 * @create 2021-03-30-16:08
 * @fun
 */
@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/login")
    public void login(@Validated UserInfo user,HttpServletRequest request) throws IOException{
        UserInfo info = userService.login(user);
        request.getSession().setAttribute("User",info);
    }

    @GetMapping("/logout")
    public void logout(@Validated UserInfo user,HttpServletRequest request){
        request.getSession().invalidate();
    }
}

        登录拦截器:

/**
 * @author aric
 * @create 2021-03-31-18:50
 * @fun
 */
@Component
public class AclInterceptor extends HandlerInterceptorAdapter {

    private String[] permitUrls = new String[] {"/users/login"};  //授权数组,不能拦截登录

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handle)throws Exception{
        boolean result = true;  //访问权限

        //如果请求路径不再授权数组中
        if(!ArrayUtils.contains(permitUrls,request.getRequestURL())){
            User user = (User)request.getAttribute("user");
            if(user == null){
                response.setContentType("text/plain");
                response.getWriter().write("need authentication");
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
            }else{
                String method = request.getMethod();
                if(!user.hasPermission(method)){
                    response.setContentType("text/plain");
                    response.getWriter().write("forbidden");
                    response.setStatus(HttpStatus.FORBIDDEN.value());
                    result = false;
                }
            }
        }
        return result;
    }
}

        User类:

/**
 * @author aric
 * @create 2021-03-30-16:06
 * @fun
 */
@Entity //jpa和数据库表绑定
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  //主键生成策略
    private Long id;
    private String name;
    private String username;
    private String password;
    private String permissions;
    public UserInfo buildInfo(){
        UserInfo info = new UserInfo();
        BeanUtils.copyProperties(this,info);
        return info;
    }

    public boolean hasPermission(String method) {
        boolean result = false;
        if(StringUtils.equalsIgnoreCase("get",method)){
            result = StringUtils.contains(permissson,"r");
        }else{
            result = StringUtils.contains(permission,"w");
        }
        return result;
    }
}

        Session Fixation攻击:

        解决:

    @GetMapping("/login")
    public void login(@Validated UserInfo user,HttpServletRequest request) throws IOException{
        UserInfo info = userService.login(user);
        HttpSession session = request.getSession(false);
        if(session != null){
            session.invalidate();  //说明之前有人用过,使它失效
        }
        request.getSession(true).setAttribute("User",info);
    }

        cookie信息:

        

            Path = / : 代表只有/ 下的请求才会携带cookie

            Secure:只有在Https下才携带

            HttpOnly:不能被javaScrpt发送,防止跨站攻击

        2.自己实现:

            见后面文章。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值