您的位置:1010cc时时彩经典版 > 1010cc时时彩经典版 > 【1010cc时时彩经典版】微服务架构中整合网关,

【1010cc时时彩经典版】微服务架构中整合网关,

发布时间:2019-10-07 06:40编辑:1010cc时时彩经典版浏览(144)

    欢迎关注我的公众号

    1010cc时时彩经典版 1微信公众号

    三、微服务认证和鉴权

    5.1 存在的不足

    • API级别操作权限校验的通用性

      (1). 对于API级别操作权限校验,需要在网关处调用时构造相应的上下文信息。上下文信息基本依赖于 token中的payload,如果信息太多引起token太长,导致每次客户端的请求头部长度变长。

      (2). 并不是所有的操作接口都能覆盖到,这个问题是比较严重的,根据上下文集合很可能出现好多接口 的权限没法鉴定,最后的结果就是API级别操作权限校验失败的是绝对没有权限访问该接口,而通过不一定能访问,因为该接口涉及到的上下文根本没法完全得到。我们的项目在现阶段,定义的最小上下文集合能勉强覆盖到,但是对于后面扩增的服务接口真的是不乐观。

      (3). 每个服务的每个接口都在Auth服务注册其所需要的权限,太过麻烦,Auth服务需要额外维护这样的信息。

    • 网关处调用Auth服务带来的系统吞吐量瓶颈

      (1). 这个其实很容易理解,Auth服务作为公共的基础服务,大多数服务接口都会需要鉴权,Auth服务需要经过复杂。

      (2). 网关调用Auth服务,阻塞调用,只有等Auth服务返回校验结果,才会做进一步处理。虽说Auth服务可以多实例部署,但是并发量大了之后,其瓶颈明显可见,严重可能会造成整个系统的不可用。

    3. auth整合

    auth服务的整合修改,其实没那么多,之前对于user、role以及permission之间的定义和关系没有给出实现,这部分的sql语句已经在auth.sql中。所以为了能给出一个完整的实例,笔者把这部分实现给补充了,主要就是user-role,role、role-permission的相应接口定义与实现,实现增删改查。

    读者要是想参考整合项目进行实际应用,这部分完全可以根据自己的业务进行增强,包括token的创建,其自定义的信息还可以在网关中进行统一处理,构造好之后传递给后端服务。

    这边的接口只是列出了需要的几个,其他接口没写(因为懒。。)

    这两个接口也是给backend项目用来获取相应的userId权限。

    //根据userId获取用户对应的权限
     @RequestMapping(method = RequestMethod.GET, value = "/api/userPermissions?userId={userId}",
                consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
        List<Permission> getUserPermissions(@RequestParam("userId") String userId);
    
    //根据userId获取用户对应的accessLevel(好像暂时没用到。。)
        @RequestMapping(method = RequestMethod.GET, value = "/api/userAccesses?userId={userId}",
                consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
        List<UserAccess> getUserAccessList(@RequestParam("userId") String userId);
    

    好了,这边的实现已经讲完了,具体见项目中的实现。

    引言: 本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现。

    3.2.1 用户身份认证

    在微服务体系架构中,微服务应用是由多个相互独立的微服务进程组成的,对每个微服务的访问都需要进行用户认证。
    微服务应用应遵循单一职责原理,即一个微服务只处理单一的业务逻辑。认证和鉴权的公共逻辑不应该放到微服务实现中。因此需要考虑一个抽象、公共的逻辑对用户进行身份认证和鉴权服务。由于在微服务架构中以API Gateway作为对外提供服务的入口,因此可以考虑在API Gateway处提供统一的用户认证。具体就技术实现采用Zuul Spring Security OAuth2/JWT。

    4. 总结

    Auth系统主要功能是授权认证和鉴权。项目微服务化后,原有的单体应用基于HttpSession认证鉴权不能满足微服务架构下的需求。每个微服务都需要对访问进行鉴权,每个微应用都需要明确当前访问用户以及其权限,尤其当有多个客户端,包括web端、移动端等等,单体应用架构下的鉴权方式就不是特别合适了。权限服务作为基础的公共服务,也需要微服务化。

    笔者的设计中,Auth服务一方面进行授权认证,另一方面是基于token进行身份合法性和API级别的权限校验。对于某个服务的请求,经过网关会调用Auth服务,对token合法性进行验证。同时笔者根据当前项目的整体情况,存在部分遗留服务,这些遗留服务又没有足够的时间和人力立马进行微服务改造,而且还需要继续运行。为了适配当前新的架构,采取的方案就是对这些遗留服务的操作API,在Auth服务进行API级别的操作权限鉴定。API级别的操作权限校验需要的上下文信息需要结合业务,与客户端进行商定,应该在token能取到相应信息,传递给Auth服务,不过应尽量减少在header取上下文校验的信息。

    笔者将本次开发Auth系统所涉及的大部分代码及源码进行了解析,至于没有讲到的一些内容和细节,读者可以自行扩展。

    5. 总结

    如上,首先讲了整合的设计思路,主要包含三个服务:gateway、auth和backend demo。整合的项目,总体比较复杂,其中gateway服务扩充了好多内容,对于暴露的接口进行路由转发,这边引入了Spring Security 的starter,配置资源服务器对暴露的路径进行放行;对于其他接口需要调用auth服务进行身份合法性校验,保证到达backend的请求都是合法的或者公开的接口;auth服务在之前的基础上,补充了role、permission、user相应的接口,供外部调用;backend demo是新起的服务,实现了接口级别的操作权限的校验,主要用到了自定义注解和Spring AOP切面。

    由于实现的细节实在有点多,本文限于篇幅,只对部分重要的实现进行列出与讲解。如果读者有兴趣实际的应用,可以根据实际的业务进行扩增一些信息,如auth授权的token、网关拦截请求构造的头部信息、注解支持的表达式等等。

    可以优化的地方当然还有很多,整合项目中设计不合理的地方,各位同学可以多多提意见。

    4.6 ResourceServer配置

    资源服务器的配置,覆写了默认的配置。为了支持logout,这边自定义了一个CustomLogoutHandler并且将logoutSuccessHandler指定为返回http状态的HttpStatusReturningLogoutSuccessHandler

     @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .requestMatchers().antMatchers .and().authorizeRequests() .antMatchers.permitAll() .anyRequest().authenticated.logout() .logoutUrl("/logout") .clearAuthentication .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler .addLogoutHandler(customLogoutHandler;
    
    3.2.3 单点登录

    我们看下维基百科中,对单点登录的解释。

    单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。相同的,单一注销(single sign-off)就是指,只需要单一的注销动作,就可以结束对于多个系统的访问权限。

    在微服务架构中,单点登录可以理解为当用户登录成功后,就可以访问所有有权限访问的微服务。API Gateway提供了客户端访问微服务的入口,Token实现了无状态的用户认证。结合这两种技术,我们就可以为微服务应用实现一个单点登录方案。

    微服务单点登录流程下,用户的认证和鉴权如下图:

    1010cc时时彩经典版 2

    • 用户登录
    1. 客户端发送登录请求到API Gateway

    2. API Gateway将登录请求转发到Security Service

    3. Security Service验证用户身份,并颁发Token

    • 用户请求
    1. 客户端请求发送到API Gateway

    2. API Gateway调用的Security Service对请求中的Token进行验证,检查用户的身份

    3. 如果请求中没有Token,Token过期或者Token验证非法,则拒绝用户请求。

    4. Security Service检查用户是否具有该操作权

    5. 如果用户具有该操作权限,则把请求发送到后端的Business Service,否则拒绝用户请求。

    1. 前文回顾

    首先还是照例对前文进行回顾。在第一篇 认证鉴权与API权限控制在微服务架构中的设计与实现(一)介绍了该项目的背景以及技术调研与最后选型。第二篇认证鉴权与API权限控制在微服务架构中的设计与实现(二)画出了简要的登录和校验的流程图,并重点讲解了用户身份的认证与token发放的具体实现。第三篇认证鉴权与API权限控制在微服务架构中的设计与实现(三)先介绍了资源服务器配置,以及其中涉及的配置类,后面重点讲解了token以及API级别的鉴权。

    本文将会讲解剩余的两个内置端点:注销和刷新token。注销token端点的处理与Spring Security默认提供的有些'/logout'有些区别,不仅清空SpringSecurityContextHolder中的信息,还要增加对存储token的清空。另一个刷新token端点其实和之前的请求授权是一样的API,只是参数中的grant_type不一样。

    除了以上两个内置端点,后面将会重点讲下几种Spring Security过滤器。API级别的操作权限校验本来设想是通过Spring Security的过滤器实现,特地把这边学习了一遍,踩了一遍坑。

    最后是本系列的总结,并对于存在的不足和后续工作进行论述。

    4.4 为什么这样设计?

    有些读者看了上面的设计,既然好多用到了Spring Security的工具类,肯定会问,为什么要引入这么复杂的工具类?

    其实很简单,首先因为SecurityExpressionOperations接口中定义的表达式足够多,且较为合理,能够覆盖我们在平时用到的大部分场景;其次,笔者之前的设计是直接在注解中指定所需权限,没有扩展性,且可读性查;最后,Spring Security 4 确实引入了@PreAuthorize,@PostAuthorize等注解,本来想用来着,自己尝试了一下,发现对于微服务架构这样的接口级别的操作权限校验不是很适合,十多个过滤器太过复杂,而且还涉及到的Principal、Credentials等信息,这些已经在auth系统实现了身份合法性校验。笔者认为这边的功能实现并不是很复杂,需要很轻量的实现,读者有兴趣可以试着这部分的实现封装成jar包或者Spring Boot的starter。

    分布式架构,特别是微服务架构的优点是可以清晰的划分出业务逻辑来,让每个微服务承担职责单一的功能,毕竟越简单的东西越稳定。

    一、前言

    我们知道微服务技术或微服务架构是一把双刃剑,其给我们带来了简单、灵活的部署,聚焦、专注快速迭代,低耦合、高内聚、易扩展等优势;与此同时,也引入了更加复杂的问题。本文要着重阐述的如何在微服务架构中实现一个安全、高效的认证和鉴权方案。

    3. Spring Security过滤器

    在上一节我们介绍了内置的两个端点的实现细节,还提到了HttpSecurity过滤器,因为注销端点的实现就是通过过滤器的作用。核心的过滤器主要有:

    • FilterSecurityInterceptor
    • UsernamePasswordAuthenticationFilter
    • SecurityContextPersistenceFilter
    • ExceptionTranslationFilter

    这一节将重点介绍其中的UsernamePasswordAuthenticationFilterFilterSecurityInterceptor

    4.1 filter过滤器

    Filter过滤器,和上面网关使用一样,拦截客户的HttpServletRequest。

    public class AuthorizationFilter implements Filter {
    
        @Autowired
        private FeignAuthClient feignAuthClient;
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            logger.info("过滤器正在执行...");
            // pass the request along the filter chain
            String userId = ((HttpServletRequest) servletRequest).getHeader(SecurityConstants.USER_ID_IN_HEADER);
    
            if (StringUtils.isNotEmpty(userId)) {
                UserContext userContext = new UserContext(UUID.fromString(userId));
                userContext.setAccessType(AccessType.ACCESS_TYPE_NORMAL);
    
                List<Permission> permissionList = feignAuthClient.getUserPermissions(userId);
                List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
                for (Permission permission : permissionList) {
                    SimpleGrantedAuthority authority = new SimpleGrantedAuthority();
                    authority.setAuthority(permission.getPermission());
                    authorityList.add(authority);
                }
    
                CustomAuthentication userAuth  = new CustomAuthentication();
                userAuth.setAuthorities(authorityList);
                userContext.setAuthorities(authorityList);
                userContext.setAuthentication(userAuth);
                SecurityContextHolder.setContext(userContext);
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        //...
    }
    

    上述代码主要实现了,根据请求头中的userId,利用feign client获取auth服务中的该user所具有的权限集合。之后构造了一个UserContext,UserContext是自定义的,实现了Spring Security的UserDetails, SecurityContext接口。

    4.3 endpoint

    提供的endpoint:

    /oauth/token?grant_type=password #请求授权token/oauth/token?grant_type=refresh_token #刷新token/oauth/check_token #校验token/logout #注销token及权限相关信息
    

    3.2 技术方案

    2.1 注销端点

    在第一篇中提到了Auth系统内置的注销端点 /logout,如果还记得第三篇资源服务器的配置,下面的关于/logout配置一定不陌生。

                //...
                    .and().logout()
                    .logoutUrl("/logout")
                    .clearAuthentication(true)
                    .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
                    .addLogoutHandler(customLogoutHandler());
    

    上面配置的主要作用是:

    • 设置注销的URL
    • 清空Authentication信息
    • 设置注销成功的处理方式
    • 设置自定义的注销处理方式

    当然在LogoutConfigurer中还有更多的设置选项,笔者此处列出项目所需要的配置项。这些配置项围绕着LogoutFilter过滤器。顺带讲一下Spring Security的过滤器。其使用了springSecurityFillterChian作为了安全过滤的入口,各种过滤器按顺序具体如下:

    • SecurityContextPersistenceFilter:与SecurityContext安全上下文信息有关
    • HeaderWriterFilter:给http响应添加一些Header
    • CsrfFilter:防止csrf攻击,默认开启
    • LogoutFilter:处理注销的过滤器
    • UsernamePasswordAuthenticationFilter:表单认证过滤器
    • RequestCacheAwareFilter:缓存request请求
    • SecurityContextHolderAwareRequestFilter:此过滤器对ServletRequest进行了一次包装,使得request具有更加丰富的API
    • AnonymousAuthenticationFilter:匿名身份过滤器
    • SessionManagementFilter:session相关的过滤器,常用来防止session-fixation protection attack,以及限制同一用户开启多个会话的数量
    • ExceptionTranslationFilter:异常处理过滤器
    • FilterSecurityInterceptor:web应用安全的关键Filter

    各种过滤器简单标注了作用,在下一节重点讲其中的几个过滤器。注销过滤器排在靠前的位置,我们一起看下LogoutFilter的UML类图。

    1010cc时时彩经典版 3

    logoutFilter

    类图和我们之前配置时的思路是一致的,HttpSecurity创建了LogoutConfigurer,我们在这边配置了LogoutConfigurer的一些属性。同时LogoutConfigurer根据这些属性创建了LogoutFilter

    LogoutConfigurer的配置,第一和第二点就不用再详细解释了,一个是设置端点,另一个是清空认证信息。
    对于第三点,配置注销成功的处理方式。由于项目是前后端分离,客户端只需要知道执行成功该API接口的状态,并不用返回具体的页面或者继续向下传递请求。因此,这边配置了默认的HttpStatusReturningLogoutSuccessHandler,成功直接返回状态码200。
    对于第四点配置,自定义注销处理的方法。这边需要借助TokenStore,对token进行操作。TokenStore在之前文章的配置中已经讲过,使用的是JdbcTokenStore。首先校验请求的合法性,如果合法则对其进行操作,先后移除refreshTokenexistingAccessToken

    public class CustomLogoutHandler implements LogoutHandler {
    
        //...
    
        @Override
        public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
            //确定注入了tokenStore
            Assert.notNull(tokenStore, "tokenStore must be set");
           //获取头部的认证信息
            String token = request.getHeader("Authorization");
            Assert.hasText(token, "token must be set");
            //校验token是否符合JwtBearer格式
            if (isJwtBearerToken(token)) {
                token = token.substring(6);
                OAuth2AccessToken existingAccessToken = tokenStore.readAccessToken(token);
                OAuth2RefreshToken refreshToken;
                if (existingAccessToken != null) {
                    if (existingAccessToken.getRefreshToken() != null) {
                        LOGGER.info("remove refreshToken!", existingAccessToken.getRefreshToken());
                        refreshToken = existingAccessToken.getRefreshToken();
                        tokenStore.removeRefreshToken(refreshToken);
                    }
                    LOGGER.info("remove existingAccessToken!", existingAccessToken);
                    tokenStore.removeAccessToken(existingAccessToken);
                }
                return;
            } else {
                throw new BadClientCredentialsException();
            }
    
        }
    
        //...
    }
    

    执行如下请求:

    method: get
    url: http://localhost:9000/logout
    header:
    {
        Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=
    }
    

    注销成功则会返回200,将token和SecurityContextHolder进行清空。

    2.2 加强头部

    Filter过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源进行拦截。这边使用Filter进行头部增强,解析请求中的token,构造统一的头部信息,到了具体服务,可以利用头部中的userId进行操作权限获取与判断。

    public class HeaderEnhanceFilter implements Filter {
    
        //...
    
        @Autowired
        private PermitAllUrlProperties permitAllUrlProperties;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        //主要的过滤方法
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            String authorization = ((HttpServletRequest) servletRequest).getHeader("Authorization");
            String requestURI = ((HttpServletRequest) servletRequest).getRequestURI();
            // test if request url is permit all , then remove authorization from header
            LOGGER.info(String.format("Enhance request URI : %s.", requestURI));
            //将isPermitAllUrl的请求进行传递
            if(isPermitAllUrl(requestURI) && isNotOAuthEndpoint(requestURI)) {
                //移除头部,但不包括登录端点的头部
                HttpServletRequest resetRequest = removeValueFromRequestHeader((HttpServletRequest) servletRequest);
                filterChain.doFilter(resetRequest, servletResponse);
                return;
            }
            //判断是不是符合规范的头部
            if (StringUtils.isNotEmpty(authorization)) {
                if (isJwtBearerToken(authorization)) {
                    try {
                        authorization = StringUtils.substringBetween(authorization, ".");
                        String decoded = new String(Base64.decodeBase64(authorization));
    
                        Map properties = new ObjectMapper().readValue(decoded, Map.class);
                        //解析authorization中的token,构造USER_ID_IN_HEADER
                        String userId = (String) properties.get(SecurityConstants.USER_ID_IN_HEADER);
    
                        RequestContext.getCurrentContext().addZuulRequestHeader(SecurityConstants.USER_ID_IN_HEADER, userId);
                    } catch (Exception e) {
                        LOGGER.error("Failed to customize header for the request", e);
                    }
                }
            } else {
              //为了适配,设置匿名头部
                RequestContext.getCurrentContext().addZuulRequestHeader(SecurityConstants.USER_ID_IN_HEADER, ANONYMOUS_USER_ID);
            }
    
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
    
        }
    
        //...
    
    }
    

    上面代码列出了头部增强的基本处理流程,将isPermitAllUrl的请求进行直接传递,否则判断是不是符合规范的头部,然后解析authorization中的token,构造USER_ID_IN_HEADER。最后为了适配,设置匿名头部。
    需要注意的是,HeaderEnhanceFilter也要进行注册。Spring 提供了FilterRegistrationBean类,此类提供setOrder方法,可以为filter设置排序值,让spring在注册web filter之前排序后再依次注册。

    本文由1010cc时时彩经典版发布于1010cc时时彩经典版,转载请注明出处:【1010cc时时彩经典版】微服务架构中整合网关,

    关键词:

上一篇:MySQL容器部署,为什么需要Docker

下一篇:没有了