代码仓库:地址
代码分支:lesson6
博客:地址
在先前文章中,我们使用SpringSecurity OAuth2搭建了一套基于OAuth2协议的授权系统,并扩展了手机验证码授权模式。在微服务架构下,网关承担着流量入口的角色,所有的请求都要先经过网关,然后由网关负责转发到具体的服务,因此可以在网关实现统一鉴权,网关对请求中的权限进行鉴定,然后将权限信息转发到具体的资源服务,在资源服务中只需要简单校验请求中的权限信息即可(查看信息是否有效),整体流程如下所示:
我们在上一篇的基础上引入网关服务,在这里使用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资源服务
使用密码模式进行授权登录,发送请求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": "无权限访问"
}
至此得到期望的访问结果,实现了统一权限控制
技术更新换代速度很快,我们无法在有限时间掌握全部知识,但我们可以在他人的基础上进行快速学习,学习也是枯燥无味的,加入我们学习牛人经验:
点击:加群讨论