Spring gateway配置Spring Security实现统一权限验证与授权示例源码

2023-08-06 0 2,916

目录

在使用Spring Cloud 进行微服务,分布式开发时,网关是请求的第一入口,所以一般把客户端请求的权限验证统一放在网关进行认证与鉴权。因为Spring Cloud Gateway使用是基于WebFlux与Netty开发的,所以与传统的Servlet方式不同。而且网关一般不会直接请求数据库,不提供用户管理服务,所以如果想在网关处进行登陆验证与授权就需要做一些额外的开发了。

需求设求

众所周知,一切架构都必须按需求来设计,万能构架基本上是不存在的,即使是像Spring Security安全架构也只是实现了一个能用方式,并不是放之四海而皆准的,但是一个构架的良好扩展性是必须的,可以让使用者按照自己的需要进行扩展使用。所以为了说明本示例的实现,先假定这样一个需求

1,需要有一个Web网关服务进行权限统一认证
2,网关后面有一个用户管理服务,负责用户账号的管理
3,网关后面还存在其它的服务,但是这些服务需要认证成功之后才能访问
4,需要支持同一个请求可以被多个角色访问

服务搭建请参考源码 https://gitee.com/wgslucky/Spring-Gateway-Security

主要技能点说明

修改默认登陆页面

在项目中添加完spring security依赖之后,如果不添加任何额外的配置,这时不管发送任何请求,都会跳到spring security提供的默认登陆页面。这显然不是我们想要的,那么第一步就是要显示自定义的登陆页面。在Spring Gateway 网关项目中添加Security的配置,如下面代码所示:

@EnableWebFluxSecurity
public class WebSecurityConfig {
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        RedirectServerAuthenticationEntryPoint loginPoint = new RedirectServerAuthenticationEntryPoint(\"/xinyue-server-a/account/index\");
        http.authorizeExchange().pathMatchers(\"/xinyue-server-a/easyui/**\",\"/xinyue-server-a/js/**\",\"/xinyue-server-a/account/index\",\"/xinyue-server-a/account/login\").permitAll()    
        .and().formLogin().loginPage(\"/xinyue-server-a/account/authen\").authenticationEntryPoint(loginPoint)
        .and().authorizeExchange().anyExchange().authenticated()
        .and().csrf().disable();
        SecurityWebFilterChain chain = http.build();
        return chain;
    }
}

这里有一个容易出现理解错误的地址,网上有好多示例说是直接只配置loginPage("/my/login")即可,这样配置的话,需要你的登陆页面,和提交登陆信息的form的action都必须是一致的,只不过,一个是get方式请求/my/login,一个是post方式请求/my/login,但是大多数据情况下,我们的登陆页面地址,和登陆form的action地址是分离的,所以需要按我上面的方式进行配置才可以。

 http.authorizeExchange().pathMatchers(\"/xinyue-server-a/easyui/**\",\"/xinyue-server-a/js/**\",\"/xinyue-server-a/account/index\",\"/xinyue-server-a/account/login\").permitAll()    

这个配置表示这些请求都不做验证,直接放过。

.and().formLogin().loginPage(\"/xinyue-server-a/account/authen\").authenticationEntryPoint(loginPoint)

这段配置表示需要认证的请求是/xinyue-server-a/account/authen(对手正常的Springmvc 服务来说,这个应该是登陆时form的action请求地址),如果没有认证,跳转到loginPoint设置的地址:/xinyue-server-a/account/index,即登陆页面。

 .and().authorizeExchange().anyExchange().authenticated()

这段配置表示其它请求都必须是认证(登陆成功)之后才可以访问。

Spring Cloud Gateway 认证方式

如果是微服务模式,在Spring cloud gateway网关处进行用户认证与授权有两种方式:

1,在Spring Cloud Gateway服务这里添加数据库访问,直接检测登陆信息是否正确,如果正确,再给此用户授权。
2,在网关后面专门的认证服务进行登陆信息认证,如果登陆成功,在返回的Header中添加用户认证与授权需要的信息,然后在网关处理再完成认证与授权

Ajax Post登陆与认证

本示例采用第二种方式,首先是客户端向xinyue-server-a服务发送登陆请求,如下面代码所示:

<script type=\"text/javascript\">
	function postAjax(url, json, success) {
		$.ajax({
			type : \"POST\",
			url : url,
			data : JSON.stringify(json),
			dataType : \"json\",
			contentType : \"application/json\",
			success : function(data) {
				if (data.code == 0) {
					success(data);
				} else {
					alert(\"服务器异常,请联系开发者\");
				}
			},
			error : function(data) {
				alert(url + \"请求错误:\" + JSON.stringify(data));
			}
		});
	}
	function submitForm() {
		$(\"#errorTips\").html(\"\");
		var username = $(\"#username\").val();
		var password = $(\"#password\").val();
		var url = \"/xinyue-server-a/account/login\";
		var json = {
			\"username\" : username,
			\"password\" : password
		};
		postAjax(
				url,
				json,
				function(data) {
					if (data.code == 0) { //如果登陆成功,发送认证请求
						var authUrl = \"/xinyue-server-a/account/authen\";
						var param = {};
						postAjax(
								authUrl,
								param,
								function(data) {
									if (data.code == 0) {//认证成功之后,跳转请求
										window.location.href = \"/xinyue-server-a/account/main\";
									} else {
										$(\"#errorTips\").html(data.msg);
									}
								});
					} else {
						$(\"#errorTips\").html(data.msg);
					}
				});
	}
</script>

这里使用ajax post方式向服务端发送登陆请求,如果登陆成功,然后再发送认证请求,在网关处完成认证。

登陆成功之后,返回用户信息缓存在网关session中

在本示例的源码中,在xinyue-server-a服务中模拟用户登陆成功,并返回此登陆用户的信息,主要是权限信息,如下面代码所示:

    @RequestMapping(\"login\")
    @ResponseBody
    public Object login(HttpServletResponse response) {
        JSONObject userInfo = new JSONObject();
        userInfo.put(\"username\", \"xinyues\");
        List<String> roles = new ArrayList<>();
        roles.add(\"Admin\");
        roles.add(\"Dev\");
        userInfo.put(\"roles\", roles);//添加角色信息
        response.addHeader(\"AccountInfo\", userInfo.toJSONString());//将信息放入响应的包头
        JSONObject result = new JSONObject();
        result.put(\"code\", 0);
        return result;
    }

然后在网关处添加过滤器,拦截登陆请求的响应信息,如下面代码所示:

@Service
public class AuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    private Logger logger = LoggerFactory.getLogger(AuthenticationGatewayFilterFactory.class);
    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> chain.filter(exchange).then(Mono.fromRunnable(() -> {
                List<String> gmAccountInfoJsons = exchange.getResponse().getHeaders().get(\"AccountInfo\");
                exchange.getResponse().getHeaders().remove(\"AccountInfo\");//移除包头中的用户信息不需要返回给客户端
                if(gmAccountInfoJsons != null && gmAccountInfoJsons.size() > 0) {
                    String gmAccountInfoJson = gmAccountInfoJsons.get(0);//获取header中的用户信息
                    //将信息放在session中,在后面认证的请求中使用
                    exchange.getSession().block().getAttributes().put(\"AccountInfo\", gmAccountInfoJson);
                }
                logger.debug(\"登陆返回信息:{}\",gmAccountInfoJsons);
        }));
    }
}

请求认证过滤器,AuthenticationWebFilter

当有请求过来时,AuthenticationWebFilter用来拦截认证请求,如果客户端是认证请求的话,在这里实现对此客户端的认证,一般来说拦截的是登陆form中的action地址,可以从form提交的数据中获取用户名和密码,然后使用用户和密码进行用户验证。但是本示例中并没有使用form提交登陆,而是使用Ajax Post方式在网关后面的xinyue-server-a服务中进行的登陆验证。在AuthenticationWebFilter中可以看到,如果是认证请求的话,需要使用.flatMap( matchResult -> this.authenticationConverter.convert(exchange))方式从认证请求获取认证需要的信息,默认是获取登陆的用户名和密码。但是我们在上面已经将登陆信息存在session中了,所示需要重新提供一个authenticationConverter类,如下面代码所示:

public class XinyueAuthenticationConverter extends ServerFormLoginAuthenticationConverter{
    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange) {
       //从session中获取登陆用户信息
       String value = exchange.getSession().block().getAttribute(\"AccountInfo\");
       if(value == null) {
           return Mono.empty();
       } else {
           List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
           //获取权限信息
           List<String> roels = JSON.parseObject(value).getJSONArray(\"roles\").toJavaList(String.class);
               roels.forEach(role->{
                  //这里必须添加前缀,参考:AuthorityReactiveAuthorizationManager.hasRole(role)
                   SimpleGrantedAuthority auth = new SimpleGrantedAuthority(\"ROLE_\" + role);
                   simpleGrantedAuthorities.add(auth);
               });
            //添加用户信息到spring security之中。
           XinyueAccountAuthentication  xinyueAccountAuthentication = new XinyueAccountAuthentication(null, value, simpleGrantedAuthorities);
           return Mono.just(xinyueAccountAuthentication);
       }
    }
}

然后将XinyueAuthenticationConverter添加到WebSecurityConfig配置中(完成代码请参考源码)

        SecurityWebFilterChain chain = http.build();
        Iterator<WebFilter>  weIterable = chain.getWebFilters().toIterable().iterator();
        while(weIterable.hasNext()) {
            WebFilter f = weIterable.next();
            if(f instanceof AuthenticationWebFilter) {
              AuthenticationWebFilter webFilter = (AuthenticationWebFilter) f;
              //将自定义的AuthenticationConverter添加到过滤器中
              webFilter.setServerAuthenticationConverter(new XinyueAuthenticationConverter());      
            }
        }

然后添加认证成功操作,如下面代码所示:

    @Bean
    public ReactiveAuthenticationManager reactiveAuthenticationManager() {
        return new ReactiveAuthenticationManagerAdapter((authentication)->{
            if(authentication instanceof XinyueAccountAuthentication) {
                XinyueAccountAuthentication gmAccountAuthentication = (XinyueAccountAuthentication) authentication;
                if(gmAccountAuthentication.getPrincipal() != null) {
                    authentication.setAuthenticated(true);
                    return authentication;
                } else {
                    return authentication;
                }
            } else {
                return authentication;
            }
        });
    }

到此,就可以算是认证成功了,登陆成功之后,就会跳到转到主页面了。

请求权限验证

一般来说,在管理系统中,用户拥有不同的角色,不同的角色拥有不同的权限,那么在收到请求的时候,就需要在网关验证当前用户是否拥有访问这个请求的权限,或是否是某一个角色,如果是才能进行访问,否则返回用户权限不足,拒绝访问。现在给下面这个请求配置必须拥有Manager权限才可以访问

.and().authorizeExchange().pathMatchers(\"/xinyue-server-a/account/main\").hasRole(\"Manager\")

如果这个时候再登陆,会发现服务器返回Access Denied,如果配置为Dev权限

.and().authorizeExchange().pathMatchers(\"/xinyue-server-a/account/main\").hasRole(\"Dev\")

因为此用户拥有Dev权限(模拟账号),所以可以正常访问。

多个角色判断

目前Spring Security提供的模式是一个请求配置一个角色,有些复杂的系统,要求一个请求的访问权限可以被多个角色共同拥有。这就需要我们自定义一个权限的验证了。比如添加如下配置:

.and().authorizeExchange().pathMatchers(\"/xinyue-server-a/account/main\").access(new XinyueReactiveAuthorizationManager(\"Manager\", \"Dev\"))

表示这个请求需要Manager或Dev其中一个角色就可以访问。然后在XinyueReactiveAuthorizationManager中实现权限验证判断,详细请考参源码

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
        return authentication
                .filter(a -> a.isAuthenticated())
                .flatMapIterable( a -> a.getAuthorities())
                .map( g-> g.getAuthority())
                .any(c->{
                    //检测权限是否匹配
                    String[] roles = c.split(\",\");
                    for(String role:roles) {
                        if(authorities.contains(role)) {
                            return true;
                        }
                    }
                    return false;
                })
                .map( hasAuthority -> new AuthorizationDecision(hasAuthority))
                .defaultIfEmpty(new AuthorizationDecision(false));
    }

到此,Spring Cloud Gateway + Spring Security配置完毕,在实际应用中,可以根据自己的需求再进行适当的封装。

源码地址:https://gitee.com/wgslucky/Spring-Gateway-Security

资源下载此资源下载价格为1小猪币,终身VIP免费,请先
由于本站资源来源于互联网,以研究交流为目的,所有仅供大家参考、学习,不存在任何商业目的与商业用途,如资源存在BUG以及其他任何问题,请自行解决,本站不提供技术服务! 由于资源为虚拟可复制性,下载后不予退积分和退款,谢谢您的支持!如遇到失效或错误的下载链接请联系客服QQ:442469558

:本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可, 转载请附上原文出处链接。
1、本站提供的源码不保证资源的完整性以及安全性,不附带任何技术服务!
2、本站提供的模板、软件工具等其他资源,均不包含技术服务,请大家谅解!
3、本站提供的资源仅供下载者参考学习,请勿用于任何商业用途,请24小时内删除!
4、如需商用,请购买正版,由于未及时购买正版发生的侵权行为,与本站无关。
5、本站部分资源存放于百度网盘或其他网盘中,请提前注册好百度网盘账号,下载安装百度网盘客户端或其他网盘客户端进行下载;
6、本站部分资源文件是经压缩后的,请下载后安装解压软件,推荐使用WinRAR和7-Zip解压软件。
7、如果本站提供的资源侵犯到了您的权益,请邮件联系: 442469558@qq.com 进行处理!

猪小侠源码-最新源码下载平台 Java教程 Spring gateway配置Spring Security实现统一权限验证与授权示例源码 http://www.20zxx.cn/806427/xuexijiaocheng/javajc.html

猪小侠源码,优质资源分享网

常见问题
  • 本站所有资源版权均属于原作者所有,均只能用于参考学习,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担
查看详情
  • 最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,建议提前注册好百度网盘账号,使用百度网盘客户端下载
查看详情

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务