cloud-gateway 集成 spring-security-client 单点登录调用链

HI

调试网关和认证服务之间的单点登录流程并且简单记录了一下,主要是留着自己备用,开发过程中其实有挺多问题,简要记录下,如果后面有空的话再好好整理吧。
已经是开发了一段时间的项目,所以可能个别有自定义的类,但是自定义的类基本也是从原来的类中cp出来的,源码名字会很类似

过滤器链

-> DefaultWebFilterChain // 遍历调用器链入口

WebFilterChainProxy
WebFluxHttpSecurity
HttpHeaderWriterWebFilter
ReactorContextWebFilter
OAuth2AuthorizationRequestRedirectWebFilter // 匹配/oauth2/authorization/{registrationId},识别客户端信息
AuthenticationWebFilter // 匹配/login/oauth2/code/{registrationId}
AuthenticationWebFilter
LoginPageGeneratingWebFilter // GET /login
LogoutPageGeneratingWebFilter // GET /logout
SecurityContextServerWebExchangeWebFilter
ServerRequestCacheWebFilter
LogoutWebFilter // POST /logout
ExceptionTranslationWebFilter
AuthorizationWebFilter

主要流程调用链

请求认证

  • 发起认证 http://10.14.3.244:9007/oauth2/authorization/venus-test
OAuth2AuthorizationRequestRedirectWebFilter.filter()
    -> DefaultServerOAuth2AuthorizationRequestResolver.resolve()
        -> authorizationRequest(exchange, clientRegistration) // 拼接redirectUriStr:http://10.14.3.244:9007/login/oauth2/code/venus-test
    -> sendRedirectForAuthorization(exchange, clientRegistration)
        -> WebSessionOAuth2ServerAuthorizationRequestRepository.saveAuthorizationRequest() // 存储<state, authorizationRequest> 到 exchange.getSession()
        -> DefaultServerRedirectStrategy.sendRedirect() // 重定向到
  • 请求授权 http://10.14.3.244:9007/oauth/authorize?response_type=code&client_id=venus-test&scope=admin&state=nzDaWairJyBbopcR7_XYYrSL-0s9hqesFKYJfUOhf0Y%3D&redirect_uri=http://10.14.3.244:9007/login/oauth2/code/venus-test
  • code 模式请求 token: http://10.14.3.244:9007/login/oauth2/code/venus-test?code=skrqTM&state=nKOkQvzZf_sml-qTwrr_uXrkVvjkmAoMBw35xvyoqc0%3D
OAuth2LoginAuthenticationWebFilter.authenticate()
-> (super)AuthenticationWebFilter.authenticate() // 开始认证
    -> this.authenticationManager.authenticate(token) // token <- OAuth2AuthorizationCodeAuthenticationToken
        // DelegatingReactiveAuthenticationManager类
        -> m.authenticate(authentication) // 第二次遍历
            -> OAuth2LoginReactiveAuthenticationManager.authenticate(Authentication authentication)
                -> OAuth2AuthorizationCodeReactiveAuthenticationManager.authenticate(token)
                    -> this.accessTokenResponseClient.getTokenResponse(authzRequest) // 实际发送请求,获取 token 数据
                -> OAuth2AuthorizationCodeReactiveAuthenticationManager.onSuccess // refreshToken 和 accessToken 构造到 OAuth2AuthorizationCodeAuthenticationToken 中
            -> OAuth2LoginReactiveAuthenticationManager.onSuccess
                -> DefaultReactiveOAuth2UserService.loadUser // 查询用户信息、构造OAuth2LoginAuthenticationToken   
-> OAuth2LoginAuthenticationWebFilter.onAuthenticationSuccess
    -> AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(默认,该bean后修改为WebSessionServerOAuth2AuthorizedClientRepository).saveAuthorizedClient
        -> InMemoryReactiveOAuth2AuthorizedClientService.saveAuthorizedClient // 存储到map,<base64([venus-test][管理员]),认证信息OAuth2AuthorizedClient>
-> (super)AuthenticationWebFilter.onAuthenticationSuccess 
    -> securityContext.setAuthentication(authentication) // 上下文存储认证信息
    // context当前格式:org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken@fb50e915: Principal: Name: [管理员], Granted Authorities: [ROLE_USER], User Attributes: [sub=管理员, org=预留科室, roles=[auditor, admin, guard], scope=[admin], oid=25, exp=2023-03-24T20:41:07Z, iat=2023-01-14T10:01:07Z, jti=1cd7ab5a-5a3f-4cf5-b93c-03d4203fecbe, client_id=venus-test, sid=0]; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
    -> session.getAttributes().put("SPRING_SECURITY_CONTEXT", context)  

登陆后,各个请求网关将携带headers

-> TokenRelayGatewayFilterFactory.apply(Object config)
    -> authorizedClient(exchange, authentication)
        -> WebSessionServerOAuth2AuthorizedClientRepository.loadAuthorizedClient // 根据登录时存储的 map,将人员认证信息取出来
    -> withBearerAuth(exchange, token) // 添加 token 到 headers 中

Token 信息存储

DefaultWebSessionManager.save

需要定义 ServerOAuth2AuthorizedClientRepository
作用:
维护一个map:<base64([client_id] [username]), OAuth2AuthorizedClient>,登录时存入认证信息,调用接口时在此处取出,并将 token 添加到 headers 中
实现:
默认使用类 AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository -> InMemoryReactiveOAuth2AuthorizedClientService,map存储在内存中,重启后必须重新登录
若修改到 redis 存储方式,使用 WebSessionServerOAuth2AuthorizedClientRepository,存储到 session 中,该信息会一并存入 redis

@Bean
public ServerOAuth2AuthorizedClientRepository authorizedClientRepository() {
   return new WebSessionServerOAuth2AuthorizedClientRepository();
}

已遇到的问题

重复登录 404 问题

首次登录,发送单点请求,未认证状态,请求到 sso
登陆后发送单点登录请求,已认证状态,不再请求 sso,直接报404

ServerHttpSecurity 的 createAttemptAuthenticationRequestMatcher 方法指定了请求路径/login/oauth2/code/{registrationId}并且未认证状态才能进入登录页面,我们项目中重写了 ServerHttpSecurity 屏蔽了这段代码:

private ServerWebExchangeMatcher createAttemptAuthenticationRequestMatcher() {
			PathPatternParserServerWebExchangeMatcher loginPathMatcher =
				new PathPatternParserServerWebExchangeMatcher("/login/oauth2/code/{registrationId}");
			/// 此处限制重复发起认证 2 次请求时会导致404,暂时取消 notAuthenticatedMatcher
//			ServerWebExchangeMatcher notAuthenticatedMatcher = e -> ReactiveSecurityContextHolder.getContext()
//				.flatMap(p -> ServerWebExchangeMatcher.MatchResult.notMatch())
//				.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.match());
//			return new AndServerWebExchangeMatcher(loginPathMatcher, notAuthenticatedMatcher);
			return new AndServerWebExchangeMatcher(loginPathMatcher);
		}

怎么指定接口校验权限?

ServerHttpSecurity 类中有 hasAuthority 和 hasRole 方法,其中 hasRole 会自动指定 ROLE_前缀来拼接一下,实际调用的还是hasAuthority。
role 和 scope 的来源正常是根据 token 属性获取的,token 中原本是不带 SCOPE_、ROLE_前缀的,通过 JwtAuthenticationConverter 的 extractAuthorities 方法,将 token 中的 scope 属性增加前缀后,再去与要 check 的属性作比较。我截图的 JwtAuthenticationConverter 这里是只有 SCOPE_的,但是这个类我们项目自己重写了,处理了 SCOPE_和 ROLE_,默认流程的话是否直接包含 ROLE_的处理要自己调试看看。

在这里插入图片描述

// 校验 /api/test/ 开头的 url,认证的 scope 属性包含 test
http.authorizeExchange().pathMatchers("/api/test/**").hasAuthority("SCOPE_test");
// 校验 /api/test/ 开头的 url,认证的 role 属性包含 admin
http.authorizeExchange().pathMatchers("/api/test/**").hasRole("admin");
// 效果与上一行一致
http.authorizeExchange().pathMatchers("/api/test/**").hasAuthority("ROLE_admin");

// 指定匹配的 Matchers:
http.authorizeExchange().matchers( 一个ServerWebExchangeMatcher 的子类) .hasRole("admin");
// 如指定携带头信息 X-Type=test的 才校验
http.authorizeExchange().matchers(new HeaderServerWebExchangeMatcher("X-Type", "test")) .hasRole("admin");
// 指定 /api/test/ 开头并且头信息存在才校验
AndServerWebExchangeMatcher requestMatcher = new AndServerWebExchangeMatcher(
	new PathPatternParserServerWebExchangeMatcher("/api/test/**"), new HeaderServerWebExchangeMatcher("X-Type", "test"));
http.authorizeExchange().matchers(requestMatcher ) .hasRole("admin");

  • 13
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值