六、SpringSecurity OAuth2 + SpringCloud Gateway实现统一鉴权管理

代码

代码仓库:地址

代码分支:lesson6

博客:地址

简介

在先前文章中,我们使用SpringSecurity OAuth2搭建了一套基于OAuth2协议的授权系统,并扩展了手机验证码授权模式。在微服务架构下,网关承担着流量入口的角色,所有的请求都要先经过网关,然后由网关负责转发到具体的服务,因此可以在网关实现统一鉴权,网关对请求中的权限进行鉴定,然后将权限信息转发到具体的资源服务,在资源服务中只需要简单校验请求中的权限信息即可(查看信息是否有效),整体流程如下所示:

六、SpringSecurity OAuth2 + SpringCloud Gateway实现统一鉴权管理_第1张图片

统一鉴权

SpringCloud Gateway网关

我们在上一篇的基础上引入网关服务,在这里使用SpringCloud Gateway组件进行搭建,引入依赖:


  org.springframework.cloud
  spring-cloud-starter-gateway

网关在OAuth2授权协议中承担着资源服务的角色,对请求进行身份鉴定和访问权限控制,身份鉴定需要访问OAuth2授权服务,因此需要引入OAuth2资源服务以及客户端依赖:


  org.springframework.security
  spring-security-oauth2-resource-server



  org.springframework.boot
  spring-boot-starter-oauth2-client

通过之前的文章,我们可以知道SpringSecurity 通过组装一系列的Filter来完成身份验证和权限访问控制功能,但是SpringCloud Gateway使用了新技术框架Reactive Stack(响应式编程),在Spring中提供了Spring WebFlux模块支持响应式编程,传统的Spring MVC都是基于阻塞I/O编程,而Spring WebFlux是基于非阻塞I/O,我们不再这里讨论这两个的区别,只需要知道WebFlux特别适合I/O密集型性应用,网关就是典型的I/O密集应用(网络I/O处理频繁)。SpringSecurity对WebFlux提供了支持,在WebFlux中WebFilter组件承担着与Filter相似的功能。

我们在先前的应用中通过HttpSecurity组件来组装SpringSecurity功能,在这里要使用新的组件ServerHttpSecurity来组装SpringSecurity功能,配置如下所示:

///启用WebFlux下的SpringSecurity配置
@EnableWebFluxSecurity
public class ResourceServerConfig {
     访问权限验证
    @Autowired
    AuthManagerHandler authManagerHandler;
     无权限访问处理器
    @Autowired
    AccessDeniedHandler accessDeniedHandler;
    /// 登录信息失效处理器
    @Autowired
    LoginLoseHandler loginLoseHandler;
    访问白名单,对白名单路径可以实现匿名访问
    @Autowired
    private WhiteUrlProperties whiteUrlProperties;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.oauth2ResourceServer()
                /// 这里配置对令牌的校验,从OAuth2授权服务中获取令牌对应的授权信息
                .opaqueToken()
                ///令牌校验地址,用于校验令牌是否有效,已经令牌对应的授权信息
                .introspectionUri("http://localhost:8081/oauth/check_token")
                 客户端信息
                .introspectionClientCredentials("blog", "blog")
                .and()
                .accessDeniedHandler(accessDeniedHandler)
                .authenticationEntryPoint(loginLoseHandler)
                .and().authorizeExchange()
                .pathMatchers(HttpMethod.OPTIONS).permitAll() //o
                .pathMatchers("/**").access(authManagerHandler)
                .anyExchange().authenticated()
                .and()
                .addFilterBefore(securityGlobalFilter(whiteUrlProperties), SecurityWebFiltersOrder.FIRST)
                .cors().disable().csrf().disable();
        return http.build();
    }
    /// 该过滤器实现将获取到的授权信息转发到下游服务中,方便后续校验
    public WebFilter securityGlobalFilter(WhiteUrlProperties properties) {
        return new SecurityGlobalFilter(properties);
    }

}

网关路由配置以及其它细节信息可以前往代码仓库进行查看,在此不做过多解释。

资源服务器

资源服务器也需要做一些调整,不需要对请求进行严格的访问控制,只需要校验网关传递的授权信息即可,然后将授权信息放入到SecurityContext中方便后续处理,同时需要注意在资源服务中还是使用Spring MVC框架进行处理(Spring WebFlux可以提高系统吞吐量,但是也会增加编程难度,例如原先的线程变量将不适用,具体需要考量整体编程人员掌握的技术栈来做决定)。

这里的资源服务器不再依赖OAuth授权服务,因此可以移除@EnableResourceServer配置(不直接参与权限控制,只需要校验上游传递的授权信息是否有效即可),同时增加对上游SpringCloud Gateway传递的授权信息进行解析处理,增加自定义SecurityAuthTokenFilter组件:

public class SecurityAuthTokenFilter extends OncePerRequestFilter {
    private static final String AUTH_TOKEN_NAME = "token";
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader(AUTH_TOKEN_NAME);
        if (StringUtils.isEmpty(token)) {
            /// 继续处理
            filterChain.doFilter(request, response);
            return;
        }
        ///....省略处理细节,具体前往代码仓库进行查看
         创建自定义的Authentication对象,必须申明为已授权,也就是isAuthenticated()方法返回为true
        BlogAuthentication authentication = new BlogAuthentication(userId, clientId, authorities);
        //....省略处理细节,具体前往代码仓库进行查看
        /// 将授权信息放入到SecurityContext中,方便后续使用
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}

运行验证

分别运行Gateway网关服务、OAuth授权服务、Resource资源服务

  • Gateway 8080端口
  • OAuth 8081端口
  • Resource 8082端口

授权登录

使用密码模式进行授权登录,发送请求POST http://localhost:8080/blog-oauth/oauth/token,请求参数:

client_id:blog
client_secret:blog
grant_type:password
username:admin
password:admin

返回结果:

{
    "access_token": "4aace702-cc9d-4a92-b507-9b65f192a65f",
    "token_type": "bearer",
    "refresh_token": "a50a6cff-97b0-4d0f-b3d2-e0fdcee6f142",
    "expires_in": 5591,
    "scope": "all user"
}

资源访问

使用得到的access_token访问资源服务器中的/admin/hello接口,发送请求GET http://localhost:8080/blog-resource/admin/hello,请求头中携带参数:

Authorization:Bearer 4aace702-cc9d-4a92-b507-9b65f192a65f

返回结果:

{
    "code": 200,
    "data": "Hello Admin"
}
访问其他权限的接口,发送请求GET http://localhost:8080/blog-resource/user/hello,请求头中携带参数:
Authorization:Bearer 4aace702-cc9d-4a92-b507-9b65f192a65f

返回结果:

{
    "code": 400,
    "message": "无权限访问"
}

至此得到期望的访问结果,实现了统一权限控制

总结

  • SpringCloud Gateway使用WebFlux技术进行开发
  • SpringSecurity提供了@EnableWebFluxSecurity来支持WebFlux
  • SpringSecurity使用ReactiveSecurityContextHolder.getContext()来实现SecurityContextHolder功能

参考文档

  • Spring Reactive文档

联系方式

技术更新换代速度很快,我们无法在有限时间掌握全部知识,但我们可以在他人的基础上进行快速学习,学习也是枯燥无味的,加入我们学习牛人经验:

六、SpringSecurity OAuth2 + SpringCloud Gateway实现统一鉴权管理_第2张图片

点击:加群讨论 

你可能感兴趣的:(SpringSecurity,spring,cloud,gateway,eureka)