注销在 Spring 安全 OAuth2 中无法正常工作
Log Out does not work correctly in Spring Security OAuth2
有一个 Zuul 网关作为 Spring-Security-OAuth2 客户端和授权服务器。这些位于 here
Zuul配置部分:
http
.csrf()
.disable()
.headers().cacheControl().disable()
.and()
.headers()
.cacheControl()
.disable()
.frameOptions()
.sameOrigin()
.and()
.httpBasic().disable()
.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.mvcMatchers("/uaa/**", "/login**", "/favicon.ico", "/error**").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl("/app/Index.jsp")
.logoutRequestMatcher(new AntPathRequestMatcher("/reza"))
.addLogoutHandler(ssoLogoutHandler);
Zuul应用的SsoLogoutHandlerclass作为Spring-Security-OAuth2客户端:
@Component
public class SSOLogoutHandler implements LogoutHandler {
@Override
public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
Object details = authentication.getDetails();
String token = ((OAuth2AuthenticationDetails) details).getTokenValue();
RestTemplate restTemplate = new RestTemplate();
String url = "http://192.168.10.97:9191/uaa/token/revoke?token=" + token;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers);
ResponseEntity<Boolean> result = restTemplate.exchange(url, HttpMethod.GET, requestEntity, new ParameterizedTypeReference<Boolean>() {
});
}
}
和授权服务器的RevokeTokenController
配置class:
@RestController
public class RevokeTokenController {
@Autowired
private TokenStore tokenStore;
@RequestMapping(method = RequestMethod.GET, value = "/token/revoke")
@ResponseBody
public Boolean revoke(String token) throws Exception {
OAuth2AccessToken tokenObj = tokenStore.readAccessToken(token);
tokenStore.removeAccessToken(tokenObj);
tokenStore.removeRefreshToken(tokenObj.getRefreshToken());
return true;
}
}
为了您看到的上述配置,SsoLogoutHandler
的客户端作为 restTemplate 调用授权服务器的 RevokeTokenController
来注销,Token 和 Refresh Token 被删除但是客户端再次请求 /uaa/authorize... 获取新的访问令牌并注销不会发生 .
哪里错了?我想在删除令牌和刷新令牌后注销,而不是再次获取访问令牌。另一方面,我想在删除令牌后重定向到登录页面。
更新:
我在去掉token后深入客户端请求,客户端请求像.../uaa/authorize?client_id=...
,所以其响应的location
属性是.../gateway/login?code=[code]
,因为代码,客户端不会被重定向到登录页面。
我建议将您的令牌撤销逻辑移到 SSOLogoutHandler 中,然后从那里重定向到登录页面,而不是调用单独的 API 调用。
因为如果某些东西由于 API 调用而无法执行令牌撤销逻辑,那么该令牌将在那里,您稍后必须单独处理这些令牌,这将更加复杂。
在这种特殊情况下,如果 TokenStore 的自动装配不起作用,那么 register/create 配置文件之一中的 SSOLogoutHandler bean 并从那里为 SSOLogoutHandler 提供 TokeStore 依赖项。
我已经通过 Gateway 和 UAA 中的两个注销端点解决了这个问题,首先,通过 /logout
端点,请求被重定向到 Gateway 以注销,所以它自己的 logoutSuccessUrl
是 UAA 的 /uaa/logout
端点,为了这些端点,网关和 UAA 都会注销。
像这样:
在网关中
.and()
.logout()
.logoutSuccessUrl("/uaa/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
UAA配置部分为:
.and()
.logout()
.logoutSuccessUrl("/login")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
有一个 Zuul 网关作为 Spring-Security-OAuth2 客户端和授权服务器。这些位于 here
Zuul配置部分:
http
.csrf()
.disable()
.headers().cacheControl().disable()
.and()
.headers()
.cacheControl()
.disable()
.frameOptions()
.sameOrigin()
.and()
.httpBasic().disable()
.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.mvcMatchers("/uaa/**", "/login**", "/favicon.ico", "/error**").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl("/app/Index.jsp")
.logoutRequestMatcher(new AntPathRequestMatcher("/reza"))
.addLogoutHandler(ssoLogoutHandler);
Zuul应用的SsoLogoutHandlerclass作为Spring-Security-OAuth2客户端:
@Component
public class SSOLogoutHandler implements LogoutHandler {
@Override
public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
Object details = authentication.getDetails();
String token = ((OAuth2AuthenticationDetails) details).getTokenValue();
RestTemplate restTemplate = new RestTemplate();
String url = "http://192.168.10.97:9191/uaa/token/revoke?token=" + token;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers);
ResponseEntity<Boolean> result = restTemplate.exchange(url, HttpMethod.GET, requestEntity, new ParameterizedTypeReference<Boolean>() {
});
}
}
和授权服务器的RevokeTokenController
配置class:
@RestController
public class RevokeTokenController {
@Autowired
private TokenStore tokenStore;
@RequestMapping(method = RequestMethod.GET, value = "/token/revoke")
@ResponseBody
public Boolean revoke(String token) throws Exception {
OAuth2AccessToken tokenObj = tokenStore.readAccessToken(token);
tokenStore.removeAccessToken(tokenObj);
tokenStore.removeRefreshToken(tokenObj.getRefreshToken());
return true;
}
}
为了您看到的上述配置,SsoLogoutHandler
的客户端作为 restTemplate 调用授权服务器的 RevokeTokenController
来注销,Token 和 Refresh Token 被删除但是客户端再次请求 /uaa/authorize... 获取新的访问令牌并注销不会发生 .
哪里错了?我想在删除令牌和刷新令牌后注销,而不是再次获取访问令牌。另一方面,我想在删除令牌后重定向到登录页面。
更新:
我在去掉token后深入客户端请求,客户端请求像.../uaa/authorize?client_id=...
,所以其响应的location
属性是.../gateway/login?code=[code]
,因为代码,客户端不会被重定向到登录页面。
我建议将您的令牌撤销逻辑移到 SSOLogoutHandler 中,然后从那里重定向到登录页面,而不是调用单独的 API 调用。
因为如果某些东西由于 API 调用而无法执行令牌撤销逻辑,那么该令牌将在那里,您稍后必须单独处理这些令牌,这将更加复杂。
在这种特殊情况下,如果 TokenStore 的自动装配不起作用,那么 register/create 配置文件之一中的 SSOLogoutHandler bean 并从那里为 SSOLogoutHandler 提供 TokeStore 依赖项。
我已经通过 Gateway 和 UAA 中的两个注销端点解决了这个问题,首先,通过 /logout
端点,请求被重定向到 Gateway 以注销,所以它自己的 logoutSuccessUrl
是 UAA 的 /uaa/logout
端点,为了这些端点,网关和 UAA 都会注销。
像这样:
在网关中
.and()
.logout()
.logoutSuccessUrl("/uaa/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
UAA配置部分为:
.and()
.logout()
.logoutSuccessUrl("/login")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));