Created
December 16, 2014 23:20
-
-
Save ccampo133/c3ae35a1fe5bfb794cc7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
protected static class CustomAuthorizationServerSecurityConfig extends AuthorizationServerSecurityConfiguration { | |
@Autowired | |
private AuthorizationServerEndpointsConfiguration endpoints; | |
@Autowired | |
private ClientDetailsService clientDetailsService; | |
@Override | |
protected void configure(final HttpSecurity http) throws Exception { | |
final AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer(); | |
final FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping(); | |
http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping); | |
configure(configurer); | |
http.apply(configurer); | |
final String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token"); | |
final String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key"); | |
final String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token"); | |
// @formatter:off | |
http | |
.authorizeRequests() | |
// Per the OAuth2 spec, we should only allow POST to the token endpoint. | |
// See: https://github.com/spring-projects/spring-security-oauth/issues/327 | |
.antMatchers(HttpMethod.GET, tokenEndpointPath).denyAll() | |
.antMatchers(HttpMethod.PUT, tokenEndpointPath).denyAll() | |
.antMatchers(HttpMethod.PATCH, tokenEndpointPath).denyAll() | |
.antMatchers(HttpMethod.DELETE, tokenEndpointPath).denyAll() | |
.and() | |
.authorizeRequests() | |
.antMatchers(tokenEndpointPath).fullyAuthenticated() | |
// Secure the token revocation endpoint so OAuth2 clients can revoke tokens. | |
.antMatchers("/oauth/revoke") | |
.hasAuthority("ROLE_CLIENT") | |
.antMatchers(tokenKeyPath) | |
.access(configurer.getTokenKeyAccess()) | |
.antMatchers(checkTokenPath) | |
.access(configurer.getCheckTokenAccess()) | |
.and() | |
.requestMatchers() | |
.antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath, "/oauth/revoke"); | |
// @formatter:on | |
http.setSharedObject(ClientDetailsService.class, clientDetailsService); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@RestController | |
public class OAuthController { | |
private static final Logger log = LoggerFactory.getLogger(OAuthController.class); | |
private final OAuthService oAuthService; | |
private final WebResponseExceptionTranslator exceptionTranslator; | |
@Autowired | |
public OAuthController(final OAuthService oAuthService, final WebResponseExceptionTranslator exceptionTranslator) { | |
this.oAuthService = oAuthService; | |
this.exceptionTranslator = exceptionTranslator; | |
} | |
/** | |
* This endpoint should ALWAYS be restricted to authenticated OAuth clients (has role ROLE_CLIENT). | |
* See {@link com.voxeo.armrest.config.OAuth2Configuration.CustomAuthorizationServerSecurityConfig}, | |
* where this restriction is explicitly enforced. This controller endpoint follows the spec defined | |
* in <a href="https://tools.ietf.org/html/rfc7009#section-2.2">RFC 7009 - OAuth 2.0 Token Revocation</a>. | |
*/ | |
@RequestMapping(value = "/oauth/revoke", method = RequestMethod.POST) | |
public ResponseEntity<Void> revokeToken(@RequestParam("token") final String token, | |
@RequestParam(value = "token_hint", required = false) final String tokenHint, final Principal principal) { | |
log.info("POST {}, /oauth/revoke; token = {}, tokenHint = {}", token, tokenHint); | |
// Invalid token revocations (token does not exist) still respond | |
// with HTTP 200. Still, log the result anyway for posterity. | |
// See: https://tools.ietf.org/html/rfc7009#section-2.2 | |
if (!oAuthService.revokeToken(token, tokenHint, (Authentication) principal)) { | |
log.debug("No token with value {} was revoked.", token); | |
} | |
return new ResponseEntity<>(HttpStatus.OK); | |
} | |
@ExceptionHandler(OAuth2Exception.class) | |
public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception { | |
log.info("Handling error: {}, {}", e.getClass().getSimpleName(), e.getMessage()); | |
return exceptionTranslator.translate(e); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Service | |
public class OAuthService { | |
private static final Logger log = LoggerFactory.getLogger(OAuthService.class); | |
private final TokenStore tokenStore; | |
@Autowired | |
public OAuthService(final TokenStore tokenStore) { | |
this.tokenStore = tokenStore; | |
} | |
public boolean revokeToken(final String token, final String tokenHint, final Authentication clientAuth) { | |
log.debug("revokeToken; token = {}, tokenHint = {}, clientAuth = {}", token, tokenHint, clientAuth); | |
// Check the refresh_token store first. Fall back to the access token store if we don't | |
// find anything. See RFC 7009, Sec 2.1: https://tools.ietf.org/html/rfc7009#section-2.1 | |
if (tokenHint != null && tokenHint.equals("refresh_token")) { | |
return revokeRefreshToken(token, clientAuth) || revokeAccessToken(token, clientAuth); | |
} | |
// The user didn't hint that this is a refresh token, so it MAY be an access | |
// token. If we don't find an access token... check if it's a refresh token. | |
return revokeAccessToken(token, clientAuth) || revokeRefreshToken(token, clientAuth); | |
} | |
private boolean revokeRefreshToken(final String token, final Authentication clientAuth) { | |
final OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(token); | |
if (refreshToken != null) { | |
log.debug("Found refresh token {}.", token); | |
final OAuth2Authentication authToRevoke = tokenStore.readAuthenticationForRefreshToken(refreshToken); | |
checkIfTokenIsIssuedToClient(clientAuth, authToRevoke); | |
tokenStore.removeAccessTokenUsingRefreshToken(refreshToken); | |
tokenStore.removeRefreshToken(refreshToken); | |
log.debug("Successfully removed refresh token {} (and any associated access token).", refreshToken); | |
return true; | |
} | |
log.debug("No refresh token {} found in the token store.", token); | |
return false; | |
} | |
private boolean revokeAccessToken(final String token, final Authentication clientAuth) { | |
final OAuth2AccessToken accessToken = tokenStore.readAccessToken(token); | |
if (accessToken != null) { | |
log.debug("Found access token {}.", token); | |
final OAuth2Authentication authToRevoke = tokenStore.readAuthentication(accessToken); | |
checkIfTokenIsIssuedToClient(clientAuth, authToRevoke); | |
if (accessToken.getRefreshToken() != null) { | |
tokenStore.removeRefreshToken(accessToken.getRefreshToken()); | |
} | |
tokenStore.removeAccessToken(accessToken); | |
log.debug("Successfully removed access token {} (and any associated refresh token).", token); | |
return true; | |
} | |
log.debug("No access token {} found in the token store.", token); | |
return false; | |
} | |
private void checkIfTokenIsIssuedToClient(final Authentication clientAuth, | |
final OAuth2Authentication authToRevoke) { | |
final String requestingClientId = clientAuth.getName(); | |
final String tokenClientId = authToRevoke.getOAuth2Request().getClientId(); | |
if (!requestingClientId.equals(tokenClientId)) { | |
log.debug("Revoke FAILED: requesting client = {}, token's client = {}.", requestingClientId, tokenClientId); | |
throw new InvalidGrantException("Cannot revoke tokens issued to other clients."); | |
} | |
log.debug("OK to revoke; token is issued to client \"{}\"", requestingClientId); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
We are integrating this piece of code. We are facing issue on triggering request for /oauth/revoke.
We are using /oauth/authorize?response_type=code to go to login page. Then getting token by /oauth/token by sending grant_type=authorization_code and Auth Header as 'Basic acme:acmesecret.
In table oauth_access_token, client_id 'acme' is being set and user_name 'john'.
We call /user to get UserName by sending Auth Header Bearer token
How should we call /oauth/revoke?
When we call it by sending Auth Header as Basic acme:acmesecret, it gives following error
"NetworkError: 401 Unauthorized - http://192.168.30.170:9999/uaa/oauth/revoke"
`2016-05-06 10:08:18.835 DEBUG 6121 --- [io-9999-exec-10] o.s.security.web.FilterChainProxy : /oauth/revoke at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
2016-05-06 10:08:18.835 DEBUG 6121 --- [io-9999-exec-10] o.s.security.web.FilterChainProxy : /oauth/revoke at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2016-05-06 10:08:18.835 DEBUG 6121 --- [io-9999-exec-10] o.s.security.web.FilterChainProxy : /oauth/revoke at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2016-05-06 10:08:18.835 DEBUG 6121 --- [io-9999-exec-10] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /oauth/revoke; Attributes: [#oauth2.throwOnError(authenticated)]
2016-05-06 10:08:18.835 DEBUG 6121 --- [io-9999-exec-10] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 192.168.30.170; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2016-05-06 10:08:18.835 DEBUG 6121 --- [io-9999-exec-10] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@139141a0, returned: -1
2016-05-06 10:08:18.836 DEBUG 6121 --- [io-9999-exec-10] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-4.0.3.RELEASE.jar:4.0.3.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:232) ~[spring-security-core-4.0.3.RELEASE.jar:4.0.3.RELEASE]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:123) ~[spring-security-web-4.0.3.RELEASE.jar:4.0.3.RELEASE]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90) ~[spring-
...
2016-05-06 10:08:18.837 DEBUG 6121 --- [io-9999-exec-10] o.s.s.w.a.ExceptionTranslationFilter : Calling Authentication entry point.
2016-05-06 10:08:18.843 DEBUG 6121 --- [io-9999-exec-10] s.s.o.p.e.DefaultOAuth2ExceptionRenderer : Written [error="unauthorized", error_description="Full authentication is required to access this resource"] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@52577106]
2016-05-06 10:08:18.843 DEBUG 6121 --- [io-9999-exec-10] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
`
And when we call it by using Auth Header as Bearer token
"NetworkError: 500 Internal Server Error - http://192.168.30.170:9999/uaa/oauth/revoke"
`2016-05-06 10:15:17.455 DEBUG 6220 --- [nio-9999-exec-9] o.s.security.web.FilterChainProxy : /oauth/revoke reached end of additional filter chain; proceeding with original chain
2016-05-06 10:15:17.462 INFO 6220 --- [nio-9999-exec-9] c.e.authserver.config.OAuthController : POST eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0NjI1MTUyODksInVzZXJfbmFtZSI6ImpvaG4iLCJzY29wZSI6WyJvcGVuaWQiXSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9DTElFTlQiXSwiYXVkIjpbInNwcmluZy1ib290LWFwcGxpY2F0aW9uIl0sImp0aSI6ImM2Zjc4NDY4LWZkYzAtNDRjZi1iMjM4LWY5ODgxOGU3NTllYSIsImNsaWVudF9pZCI6InNkZSJ9.aaGTsN-tqy2Tihi5ssxW-7FpRyDyNC8y4ZEwZdzr5g8xdR8lAd1Q69KTinsl5ZJynedrqZeRZvzumUY8n0s-iL4DwDsRtgxLOxAPgLdfzT_uShMn2cLVflMUDbZzxyeIZSwY-oh2P86KkzcMvVmaIMTZ-35xMHGBDdV_pzIz-tUQwmRPPgj-2WXHTgy0qWGh8GSv-cv-gG_KkDNXOpTWsTCKrRS2LxZYFsElIFS38wvCEtn_U7aKuNw7JSd-QL-mKKJ1yxMnoUvbGThwUd9IYI0uXUjQEsPIZcRMStH88nmYvfzQNcIr3Tqb2Ty3kQD4cSrSYauwUAsUkl8dPO8rAg, /oauth/revoke; token = null, tokenHint = {}
2016-05-06 10:15:17.497 DEBUG 6220 --- [nio-9999-exec-9] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2016-05-06 10:15:17.499 ERROR 6220 --- [nio-9999-exec-9] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [/uaa] threw exception [Request processing failed; nested exception is error="invalid_grant", error_description="Cannot revoke tokens issued to other clients.john:acme"] with root cause
org.springframework.security.oauth2.common.exceptions.InvalidGrantException: Cannot revoke tokens issued to other clients.john:acme
at com.etilize.authserver.service.OAuthService.checkIfTokenIsIssuedToClient(OAuthService.java:80) ~[classes/:na]
at com.etilize.authserver.service.OAuthService.revokeAccessToken(OAuthService.java:60) ~[classes/:na]
at com.etilize.authserver.service.OAuthService.revokeToken(OAuthService.java:36) ~[classes/:na]
at com.etilize.authserver.config.OAuthController.revokeToken(OAuthController.java:49) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_67]
`
In following method tokenClientId is acme and requestingclientid is john.
private void checkIfTokenIsIssuedToClient(final Authentication clientAuth, final OAuth2Authentication authToRevoke) { final String requestingClientId = clientAuth.getName(); final String tokenClientId = authToRevoke.getOAuth2Request().getClientId(); log.debug("Revoke FAILED: requesting client = {}, token's client = {}.", requestingClientId, tokenClientId); if (!requestingClientId.equals(tokenClientId)) { log.debug("Revoke FAILED: requesting client = {}, token's client = {}.", requestingClientId, tokenClientId); throw new InvalidGrantException("Cannot revoke tokens issued to other clients."+requestingClientId+":"+tokenClientId); } log.debug("OK to revoke; token is issued to client \"{}\"", requestingClientId); }
Please let us know, how can we solve it?