SpringCloud微服务安全API安全 2-8 授权

Posted 鮀城小帅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud微服务安全API安全 2-8 授权相关的知识,希望对你有一定的参考价值。

登录

  • 登录的本质
  • 保持登录状态的方法
  • 常见的登录攻击及防护

1. 登录

在认证部分用过滤器实现HttpBasic 认证 ,在请求头里携带用户名和密码,存在的问题是,你不可能让用户每个请求都输入用户名密码吧,即使前端把用户名密码存起来,这也是不安全的。

1.1 基于Token的身份认证的实现

1.2 基于cookie和session的实现

2. 登录

2.1 登录接口

    @GetMapping("/login")
    public void login(@Validated UserInfo user, HttpServletRequest request,HttpServletResponse response){
        // 登录
        UserInfo info = userService.login(user);
        request.getSession().setAttribute("user",info);
    }

2.2 授权拦截重构

登录操作不需要拦截

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        boolean result = true;

        // 登录:该操作不做授权的校验
        if (!ArrayUtils.contains(permitUrls,request.getRequestURI())){
            User user = (User)request.getAttribute("user");
            if(user == null){
                response.setContentType("text/plain");
                response.getWriter().write("need authentication");
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                result = false;
            }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;
    }

登录操作 result 返回true;其他请求则进行授权校验。

2.3 访问登录接口

记录JSESSIONID

2.4 再次请求,浏览器Cookie携带JSESSIONID到服务器。

2.5 基于cookie-session的登录方式的优劣势

优点:就是弥补了HttpBasic的缺点,使用起来很方便

缺点:只适用于浏览器,浏览器接收到Response Header里的jessionId后去设置 Cookie,无法对App、第三方的服务器,因为他们不认Cookie

集群环境下,需要进行Session共享处理

3. 自定义Token实现

主要通过基于 Token实现身份认证

4. 防范Session固定攻击

    @GetMapping("/login")
    public void login(@Validated UserInfo user, HttpServletRequest request,HttpServletResponse response){
        // 登录
        UserInfo info = userService.login(user);
//        request.getSession().setAttribute("user",info);
        // 优化,解决Session固定攻击
        HttpSession session = request.getSession(false); // 将当前请求的sessionId失效
        if(session != null ){
            session.invalidate();
        }
        // true用于标识生成新的JSESSIONID
        request.getSession(true).setAttribute("user", user);
    }

5.代码重构

重构内容:

  1. 同时提供 Authorization 和 cookie-session 两种授权策略
  2. 所有接口都允许使用 authorization 策略进行请求
  3. cookie-session 策略由 login() 接口登录后生成 cookie ,其他的接口携带 cookie 进行请求

5.1 认证功能重构

BasicAuthecationFilter.java 
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取请求头中的认证信息
        String authHeader = request.getHeader("Authorization");
        // 请求进来的时候调用
        // 判断认证信息是否存在,有就进行验证
        if(StringUtils.isNotBlank(authHeader)){
            // 截取认证信息中的basic信息
            String token64 = StringUtils.substringAfter(authHeader,"Basic ");
            String token = new String(Base64Utils.decodeFromString(token64));
            String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(token,":");

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

            User user = userRepository.findByUsername(username);
//          当前用户不为空与密码正确的情况下,认证通过,并将相关用户数据存入到请求域中
            boolean bo = SCryptUtil.check(password,user.getPassword());
//            if(user != null && StringUtils.equals(password , user.getPassword())){
            if(user != null && bo){
                //认证通过,存放用户信息
                request.getSession().setAttribute("user", user.buildInfo());
                request.getSession().setAttribute("temp","yes");
            }
        }
        try {
            //不管认证是否正确,继续往下走,是否可以访问,交给授权处理
            filterChain.doFilter(request, response);
        }finally {
            // finally 中是响应的时候调用
            HttpSession session = request.getSession();
            if (session.getAttribute("temp")!= null){
                session.invalidate();
            }
        }

    }

5.2 授权功能重构

AclInterceptor.java
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        boolean result = true;

        // 登录:该操作不做授权的校验
        if (!ArrayUtils.contains(permitUrls,request.getRequestURI())){
            UserInfo userInfo = (UserInfo)request.getSession().getAttribute("user");
            if(userInfo == null){
                response.setContentType("text/plain");
                response.getWriter().write("need authentication");
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                result = false;
            }else{
                String method = request.getMethod();
                if(!userInfo.hasPermission(method)){
                    response.setContentType("text/plain");
                    response.getWriter().write("forbidden");
                    response.setStatus(HttpStatus.FORBIDDEN.value());
                    result = false;
                }
            }
        }
        return result;
    }

5.3 RW权限校验重构

UserInfo.java , 这里主要将 User 和 UserInfo 做分离
//    @NotBlank(message = "权限不能为空")
    private String permissions;

    /**
     * @Description TODO 根据请求的method来判断是否有访问当前接口的权限
     * @param method
     * @return
     */
    public boolean hasPermission(String method) {
        boolean result = false;
        // 判断该权限是读还是写
        if(StringUtils.equalsIgnoreCase("get",method)){
            result = StringUtils.contains(permissions, "r");
        }else {
            result = StringUtils.contains(permissions,"w");
        }
        return result;
    }

5.4重构当前用户获取的逻辑

SecurityConfig.java  ,这里主要提供审计日志记录所用的用户信息,重构后更加灵活。
    @Bean
    public AuditorAware<String> auditorAware(){
        return new AuditorAware<String>() {
            @Override
            public Optional<String> getCurrentAuditor() {
                // 从session中拿到用户信息
                ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                UserInfo info = (UserInfo) servletRequestAttributes.getRequest().getSession().getAttribute("user");
                String username = null;
                if (info != null){
                    username = info.getUsername();
                }
//                return Optional.of("wsp");
                return Optional.ofNullable(username);
            }
        };
    }

5.5 重构后测试

(1)由 login() 接口生成 cookie

(2)例:查询接口携带Cookie 请求

 

以上是关于SpringCloud微服务安全API安全 2-8 授权的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud微服务安全API安全 2-2 注入攻击防护

SpringCloud微服务安全实战API安全 3-9 总结

SpringCloud微服务安全API安全 2-7 授权

SpringCloud微服务安全API安全 2-6 审计

SpringCloud微服务安全实战API安全 3-5 认证

SpringCloud微服务安全API安全 2-4 认证