春天的安全.如何注销用户(撤销oauth2令牌)

gst*_*low 25 java spring spring-security oauth-2.0

当我想要注销时,我调用此代码:

request.getSession().invalidate();
SecurityContextHolder.getContext().setAuthentication(null);
Run Code Online (Sandbox Code Playgroud)

但在它之后(在使用旧的oauth令牌的下一个请求中)我调用

SecurityContextHolder.getContext().getAuthentication();

我在那里看到我的老用户

怎么解决?

cam*_*ser 39

这是我的实现(Spring OAuth2):

@Controller
public class OAuthController {
    @Autowired
    private TokenStore tokenStore;

    @RequestMapping(value = "/oauth/revoke-token", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    public void logout(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null) {
            String tokenValue = authHeader.replace("Bearer", "").trim();
            OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
            tokenStore.removeAccessToken(accessToken);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用于检测:

curl -X GET -H "Authorization: Bearer $TOKEN" http://localhost:8080/backend/oauth/revoke-token
Run Code Online (Sandbox Code Playgroud)

  • 您可以直接使用ConsumerTokenServices的撤销功能 (3认同)
  • 您可以直接使用`@RequestHeader(value =“ Authorization”)字符串authHeader`代替HttpServletRequest作为参数。 (2认同)

Cla*_*sso 10

使用Spring OAuth提供的API可以改善camposer的响应.实际上,没有必要直接访问HTTP头,但删除访问令牌的REST方法可以实现如下:

@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;

@Autowired
private ConsumerTokenServices consumerTokenServices;

@RequestMapping("/uaa/logout")
public void logout(Principal principal, HttpServletRequest request, HttpServletResponse response) throws IOException {

    OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) principal;
    OAuth2AccessToken accessToken = authorizationServerTokenServices.getAccessToken(oAuth2Authentication);
    consumerTokenServices.revokeToken(accessToken.getValue());

    String redirectUrl = getLocalContextPathUrl(request)+"/logout?myRedirect="+getRefererUrl(request);
    log.debug("Redirect URL: {}",redirectUrl);

    response.sendRedirect(redirectUrl);

    return;
}
Run Code Online (Sandbox Code Playgroud)

我还向Spring Security注销过滤器的端点添加了重定向,因此会话无效,客户端必须再次提供凭据才能访问/ oauth/authorize端点.


rpr*_*rpr 5

这取决于您使用的oauth2“授予类型”的类型。

如果您@EnableOAuth2Sso在客户端应用中使用过spring的话,最常见的是“授权码”。在这种情况下,Spring安全性会将登录请求重定向到“授权服务器”,并在客户端应用程序中使用从“授权服务器”接收到的数据创建会话。

您可以轻松地在客户端应用程序调用/logout端点处销毁会话,但是客户端应用程序会将用户再次发送到“授权服务器”并再次返回已记录的日志。

我建议创建一种机制来拦截客户端应用程序中的注销请求,并从此服务器代码中调用“授权服务器”以使令牌无效。

我们需要做的第一个更改是使用Claudio Tasso提出的代码在授权服务器上创建一个端点,以使用户的access_token无效。

@Controller
@Slf4j
public class InvalidateTokenController {


    @Autowired
    private ConsumerTokenServices consumerTokenServices;


    @RequestMapping(value="/invalidateToken", method= RequestMethod.POST)
    @ResponseBody
    public Map<String, String> logout(@RequestParam(name = "access_token") String accessToken) {
        LOGGER.debug("Invalidating token {}", accessToken);
        consumerTokenServices.revokeToken(accessToken);
        Map<String, String> ret = new HashMap<>();
        ret.put("access_token", accessToken);
        return ret;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在客户端应用上,创建一个LogoutHandler

@Slf4j
@Component
@Qualifier("mySsoLogoutHandler")
public class MySsoLogoutHandler implements LogoutHandler {

    @Value("${my.oauth.server.schema}://${my.oauth.server.host}:${my.oauth.server.port}/oauth2AuthorizationServer/invalidateToken")
    String logoutUrl;

    @Override
    public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {

        LOGGER.debug("executing MySsoLogoutHandler.logout");
        Object details = authentication.getDetails();
        if (details.getClass().isAssignableFrom(OAuth2AuthenticationDetails.class)) {

            String accessToken = ((OAuth2AuthenticationDetails)details).getTokenValue();
            LOGGER.debug("token: {}",accessToken);

            RestTemplate restTemplate = new RestTemplate();

            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
            params.add("access_token", accessToken);

            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization", "bearer " + accessToken);

            HttpEntity<String> request = new HttpEntity(params, headers);

            HttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
            HttpMessageConverter stringHttpMessageConverternew = new StringHttpMessageConverter();
            restTemplate.setMessageConverters(Arrays.asList(new HttpMessageConverter[]{formHttpMessageConverter, stringHttpMessageConverternew}));
            try {
                ResponseEntity<String> response = restTemplate.exchange(logoutUrl, HttpMethod.POST, request, String.class);
            } catch(HttpClientErrorException e) {
                LOGGER.error("HttpClientErrorException invalidating token with SSO authorization server. response.status code: {}, server URL: {}", e.getStatusCode(), logoutUrl);
            }
        }


    }
}
Run Code Online (Sandbox Code Playgroud)

并在WebSecurityConfigurerAdapter以下位置注册:

@Autowired
MySsoLogoutHandler mySsoLogoutHandler;

@Override
public void configure(HttpSecurity http) throws Exception {
    // @formatter:off
    http
        .logout()
            .logoutSuccessUrl("/")
            // using this antmatcher allows /logout from GET without csrf as indicated in
            // https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-logout
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            // this LogoutHandler invalidate user token from SSO
            .addLogoutHandler(mySsoLogoutHandler)
    .and()
            ...
    // @formatter:on
}
Run Code Online (Sandbox Code Playgroud)

注意事项:如果您使用的是JWT Web令牌,则无法使其无效,因为该令牌不是由授权服务器管理的。


Sun*_*l S 1

您可以通过编程方式注销:

public void logout(HttpServletRequest request, HttpServletResponse response) {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
      if (auth != null){    
         new SecurityContextLogoutHandler().logout(request, response, auth);
      }
    SecurityContextHolder.getContext().setAuthentication(null);
}
Run Code Online (Sandbox Code Playgroud)