Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configure Spring Security with Hydra OAuth 2.0

I configured an Hydra instance with my Spring Boot app. I just configured my app as a resource server using the annotation @EnableResourceServer. So, when I'm using the Bearer authorization header in my request, Spring uses the value that I specify in the property:

security.oauth2.resource.user-info-uri=...

To validate if the token is valid or not. Unfortunately, I don't find what's this URL with Hydra OAuth 2.0 (http://docs.hydra13.apiary.io/ / https://github.com/ory/hydra)

like image 604
Ben Chevallereau Avatar asked Dec 19 '22 04:12

Ben Chevallereau


1 Answers

First: Configure your resource server in Ory Hydra (you must add it to Ory Hydra with client_credentials and scope 'hydra.introspect' in order to be able to ask for token validity):

$> hydra clients create --skip-tls-verify \
    --id my-rest-api \
    --secret mypwd \
    --grant-types client_credentials \
    --response-types token \
    --allowed-scopes hydra.introspect

Second: Add a policy to let your resource server ask for token validity.

$> hydra policies create --skip-tls-verify \
    --actions introspect \
    --description "Policy to introspect tokens from my api" \
    --allow \
    --id accesstoken_introsp-policy \
    --resources "rn:hydra:oauth2:tokens" \
    --subjects my-rest-api

Third: Add oauth2 dependency in build.gradle (or pom.xml if its maven):

compile 'org.springframework.security.oauth:spring-security-oauth2:2.2.1.RELEASE'

Fourth: Configure application.yml to use Ory Hydra introspection endpoint to get token info.

security:
  user:
    password: none
  oauth2:
    resource:
      token-info-uri: https://yourserver.com/oauth2/introspect
    client:
      client-id: my-rest-api
      client-secret: mypwd
      scope: [ "hydra.introspect" ]

Fifth: Create a class to configure urls protected by access tokens

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Autowired
    private RemoteTokenServices tokenServices;

    @Value("${security.oauth2.client.client-id}")
    private String clientId;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.NEVER)
            .and()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS).permitAll()
            .antMatchers("/api/v1/**").access("#oauth2.hasScope('my.desired.scope')")
            .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId(clientId);
        tokenServices.setAccessTokenConverter(new OryHydraAccessTokenConverter());
        resources.tokenServices(tokenServices);
    }    
}

class OryHydraAccessTokenConverter extends DefaultAccessTokenConverter {
    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
        OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
        oAuth2Authentication.setDetails(map.get("ext"));
        return oAuth2Authentication;
    }
}

I need a custom AccessTokenConverter because my consent app add several properties to the token and we need to map all of them. With Ory, my properties are under 'ext' property. Here's an example access token:

{
    "active": true,
    "scope": "my.desired.scope",
    "client_id": "my-mobile-app",
    "sub": "123121e",
    "exp": 1520948372,
    "iat": 1520944772,
    "iss": "https://yourserver.com",
    "ext": {
        "custom_prop1": 12321,
        "custom_prop2": "Name Surname",
        "custom_prop3": false
    }
}

Last step: Now in your controllers you can autowire as parameter Oauth2Authentication object.

@GetMapping("/api/v1/data")
public MyBean findDataById(OAuth2Authentication auth,
                           @RequestParam("id") String id) {
    OAuth2AuthenticationDetails oAuth2AuthenticationDetails = (OAuth2AuthenticationDetails) auth.getDetails();
    Map<String, Object> ext = (Map<String, Object>) oAuth2AuthenticationDetails.getDecodedDetails();
    return MyBean.builder().name("Name:"+ext.get("custom_prop1")).build();
}
like image 118
David Fdez. Avatar answered Dec 20 '22 19:12

David Fdez.