it-swarm.com.de

Frühlingssicherheit. Abmelden des Benutzers (oauth2-Token widerrufen)

Wenn ich mich abmelden möchte, rufe ich diesen Code auf:

request.getSession().invalidate();
SecurityContextHolder.getContext().setAuthentication(null);

Aber nachdem es (in der nächsten Anfrage mit alten Oauth-Token) Ich rufe auf

SecurityContextHolder.getContext().getAuthentication();

und ich sehe dort meinen alten user.

Wie man es repariert?

20
gstackoverflow

Hier ist meine Implementierung (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);
        }
    }
}

Zum Prüfen:

curl -X GET -H "Authorization: Bearer $TOKEN" http://localhost:8080/backend/oauth/revoke-token
31
camposer

Die Antwort von camposer kann mithilfe der von Spring OAuth bereitgestellten API verbessert werden. Tatsächlich ist es nicht notwendig, direkt auf die HTTP-Header zuzugreifen, aber die Methode REST, die das Zugriffstoken entfernt, kann wie folgt implementiert werden:

@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;
}

Ich habe auch eine Umleitung zum Endpunkt des Spring Security-Logout-Filters hinzugefügt, sodass die Sitzung ungültig wird und der Client erneut Anmeldeinformationen angeben muss, um auf den/oauth/authorize-Endpunkt zugreifen zu können.

9
Claudio Tasso

Es hängt von der Art des oauth2-Grant-Typs ab, den Sie verwenden.

Am häufigsten, wenn Sie @EnableOAuth2Sso von Spring in Ihrer Client-App verwendet haben, ist 'Authorization Code'. In diesem Fall leitet Spring Security die Anmeldeanforderung an den 'Authorization Server' um und erstellt in Ihrer Client-App eine Sitzung mit den von 'Authorization Server' erhaltenen Daten. 

Sie können Ihre Sitzung einfach an der Client-App zerstören, die /logout-Endpunkt aufruft, aber dann sendet die Client-App den Benutzer erneut an den Autorisierungsserver und gibt das Protokoll erneut zurück.

Ich schlage vor, einen Mechanismus zum Abfangen der Abmeldeanforderung bei der Client-App zu erstellen. Rufen Sie von diesem Servercode aus "Autorisierungsserver" auf, um das Token ungültig zu machen.

Die erste Änderung, die wir benötigen, ist das Erstellen eines Endpunkts auf dem Autorisierungsserver. Verwenden Sie den von Claudio Tasso vorgeschlagenen Code, um das access_token des Benutzers für ungültig zu erklären.

@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;
    }
}

Dann erstellen Sie in der Client-App eine 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);
            }
        }


    }
}

Und registrieren Sie es unter 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
}

Ein Hinweis: Wenn Sie JWT-Web-Token verwenden, können Sie sie nicht ungültig machen, da das Token nicht vom Autorisierungsserver verwaltet wird.

4
rpr

Es liegt an Ihrer Token Store-Implementierung.

Wenn SieJDBCtoken stroke verwenden, müssen Sie ihn nur aus der Tabelle ... .__ entfernen. Auf jeden Fall müssen Sie/logout-Endpunkt manuell hinzufügen und dann Folgendes aufrufen:

@RequestMapping(value = "/logmeout", method = RequestMethod.GET)
@ResponseBody
public void logmeout(HttpServletRequest request) {
    String token = request.getHeader("bearer ");
    if (token != null && token.startsWith("authorization")) {

        OAuth2AccessToken oAuth2AccessToken = okenStore.readAccessToken(token.split(" ")[1]);

        if (oAuth2AccessToken != null) {
            tokenStore.removeAccessToken(oAuth2AccessToken);
        }
}
2
Pasha Gharibi

Fügen Sie im <http></http>-Tag die folgende Zeile hinzu.

<logout invalidate-session="true" logout-url="/logout" delete-cookies="JSESSIONID" />

Dies löscht JSESSIONID und macht die Sitzung ungültig. Ein Link zum Logout-Button oder -Beschriftung wäre etwa so: 

<a href="${pageContext.request.contextPath}/logout">Logout</a>

BEARBEITEN: Sie möchten die Sitzung aus Java-Code ungültig machen. Ich gehe davon aus, dass Sie einige Aufgaben direkt ausführen müssen, bevor Sie den Benutzer abmelden, und dann die Sitzung ungültig machen. Wenn dies der Anwendungsfall ist, sollten Sie custome Logout-Handler verwenden. Besuchen Sie this site für weitere Informationen.

0
Jeevan Patil

Dies funktioniert für die Abmeldung des vertraulichen Keycloak-Clients. Ich habe keine Ahnung, warum die Leute bei keycloak im Allgemeinen keine stabileren Docs auf Java-Nicht-Web-Clients und deren Endpunkte im Allgemeinen haben. Ich denke, das ist das Wesen der Bestie mit Open Source-Bibliotheken. Ich musste etwas Zeit in ihrem Code verbringen:

    //requires a Keycloak Client to be setup with Access Type of Confidential, then using the client secret
public void executeLogout(String url){

    HttpHeaders requestHeaders = new HttpHeaders();
    //not required but recommended for all components as this will help w/t'shooting and logging
    requestHeaders.set( "User-Agent", "Keycloak Thick Client Test App Using Spring Security OAuth2 Framework");
    //not required by undertow, but might be for Tomcat, always set this header!
    requestHeaders.set( "Accept", "application/x-www-form-urlencoded" );

    //the keycloak logout endpoint uses standard OAuth2 Basic Authentication that inclues the
    //Base64-encoded keycloak Client ID and keycloak Client Secret as the value for the Authorization header
     createBasicAuthHeaders(requestHeaders);

    //we need the keycloak refresh token in the body of the request, it can be had from the access token we got when we logged in:
    MultiValueMap<String, String> postParams = new LinkedMultiValueMap<String, String>();
    postParams.set( OAuth2Constants.REFRESH_TOKEN, accessToken.getRefreshToken().getValue() );

    HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(postParams, requestHeaders);
    RestTemplate restTemplate = new RestTemplate();
    try {
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
        System.out.println(response.toString());

    } catch (HttpClientErrorException e) {
        System.out.println("We should get a 204 No Content - did we?\n" + e.getMessage());          
    }
} 

//has a hard-coded client ID and secret, adjust accordingly
void createBasicAuthHeaders(HttpHeaders requestHeaders){
     String auth = keycloakClientId + ":" + keycloakClientSecret;
     byte[] encodedAuth = Base64.encodeBase64(
        auth.getBytes(Charset.forName("US-ASCII")) );
     String authHeaderValue = "Basic " + new String( encodedAuth );
     requestHeaders.set( "Authorization", authHeaderValue );
}
0
heregear

Lösung von Benutzer Komponist hat für mich perfekt funktioniert. Ich habe einige kleinere Änderungen am Code vorgenommen,

@Controller
public class RevokeTokenController {

    @Autowired
    private TokenStore tokenStore;

    @RequestMapping(value = "/revoke-token", method = RequestMethod.GET)
    public @ResponseBody ResponseEntity<HttpStatus> logout(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null) {
            try {
                String tokenValue = authHeader.replace("Bearer", "").trim();
                OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
                tokenStore.removeAccessToken(accessToken);
            } catch (Exception e) {
                return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
            }           
        }

        return new ResponseEntity<HttpStatus>(HttpStatus.OK);
    }
}

Ich habe dies getan, weil, wenn Sie versuchen, dasselbe Zugriffstoken erneut zu annullieren, eine Nullzeiger-Ausnahme ausgelöst wird.

0
Nikhil