2015-04-18 15 views
5

Sto consumando un set di servizi protetti oAuth2. Funziona attualmente in questo modo: il client accede utilizzando il nome utente e la password. Scambio questi per un token Tengo il token nella sessione e lo invio ogni volta che voglio chiamare un servizio. Funziona, ma il problema è che lo faccio completamente manualmente, senza usare molto del supporto di Spring Security oAuth2. Ecco come appare:o client Aututh2 con concessione di password in Spring Security

<!-- Configure Authentication mechanism --> 
<authentication-manager alias="authenticationManager"> 
    <authentication-provider ref="oAuth2AuthenticationProvider"/> 
</authentication-manager> 


<beans:bean id="oAuth2AuthenticationProvider" class="my.custom.Oauth2AuthenticationProvider"> 
    <beans:constructor-arg name="accessTokenUri" value="http://x.x.x.x/oauth/token"/> 
    <beans:constructor-arg name="clientId" value="myClientId"/> 
    <beans:constructor-arg name="clientSecret" value="myClientSecret"/> 
    <beans:constructor-arg name="scope"> 
     <beans:list> 
      <beans:value>myScope</beans:value> 
     </beans:list> 
    </beans:constructor-arg> 
</beans:bean> 

<beans:bean id="resourceOwnerPasswordAccessTokenProvider" class="org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider"/> 

Come potete vedere ho fatto il provider di autenticazione me stesso. Accetta lo standard UsernamePasswordAuthenticationToken ma sta producendo la mia propria estensione che mantiene anche l'OAuth2AccessToken effettivo, mantenendo così il contesto di sicurezza.

public class Oauth2AuthenticationProvider implements AuthenticationProvider { 

@Autowired 
private ResourceOwnerPasswordAccessTokenProvider provider; 

private String accessTokenUri; 
private String clientId; 
private String clientSecret; 
private List<String> scope; 

public Oauth2AuthenticationProvider(String accessTokenUri, String clientId, String clientSecret, List<String> scope) { 
    this.accessTokenUri = accessTokenUri; 
    this.clientId = clientId; 
    this.clientSecret = clientSecret; 
    this.scope = scope; 
} 

@Override 
public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
    String username = authentication.getName(); 
    String password = authentication.getCredentials().toString(); 
    OAuth2AccessToken token = obtainToken(username, password); 
    return handleLogonSuccess(authentication, token); 
} 

private OAuth2AccessToken obtainToken(String username, String password) { 
    ResourceOwnerPasswordResourceDetails passwordResourceDetails = new ResourceOwnerPasswordResourceDetails(); 
    passwordResourceDetails.setUsername(username); 
    passwordResourceDetails.setPassword(password); 
    passwordResourceDetails.setClientId(clientId); 
    passwordResourceDetails.setClientSecret(clientSecret); 
    passwordResourceDetails.setScope(scope); 
    passwordResourceDetails.setAccessTokenUri(accessTokenUri); 
    DefaultAccessTokenRequest defaultAccessTokenRequest = new DefaultAccessTokenRequest(); 
    OAuth2AccessToken token; 
    try { 
     token = provider.obtainAccessToken(passwordResourceDetails, defaultAccessTokenRequest); 
    } catch (OAuth2AccessDeniedException accessDeniedException) { 
     throw new BadCredentialsException("Invalid credentials", accessDeniedException); 
    } 

    return token; 
} 

public OAuth2AccessToken refreshToken(OAuth2AuthenticationToken authentication) { 
    OAuth2AccessToken token = authentication.getoAuth2AccessToken(); 
    OAuth2RefreshToken refreshToken = token.getRefreshToken(); 
    BaseOAuth2ProtectedResourceDetails resourceDetails = new BaseOAuth2ProtectedResourceDetails(); 
    resourceDetails.setClientId(clientId); 
    resourceDetails.setClientSecret(clientSecret); 
    resourceDetails.setScope(scope); 
    resourceDetails.setAccessTokenUri(accessTokenUri); 
    OAuth2AccessToken newToken = provider.refreshAccessToken(resourceDetails, refreshToken, new DefaultAccessTokenRequest()); 
    authentication.setoAuth2AccessToken(newToken); 
    return newToken; 
} 

public boolean supports(Class<?> authentication) { 
    return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); 
} 

private Authentication handleLogonSuccess(Authentication authentication, OAuth2AccessToken token) { 

    MyCustomOAuth2AuthenticationToken successAuthenticationToken = new MyCustomOAuth2AuthenticationToken(user, authentication.getCredentials(), calculateAuthorities(authentication), token); 

    return successAuthenticationToken; 
} 

public list<GrantedAuthority> calculateAuthorities(Authentication authentication) { 
     //my custom logic that assigns the correct role. e.g. ROLE_USER 
} 

}

Come si vede, si rende praticamente sicuro che il token rimane nel perimetro di sicurezza da dove posso semplicemente estrarlo manualmente prima di ogni chiamata ai servizi di back-end. Allo stesso modo, controllerò la freschezza del token prima di ogni chiamata. Funziona bene, ma sono sicuro di poter utilizzare lo spazio dei nomi di oauth di Spring in XML (non sto usando la configurazione di Java) per ottenere lo stesso risultato in un modo più senza configurazione di codice. La maggior parte degli esempi che ho trovato includono l'implementazione del server oAuth che non mi interessa e che mi confonde.

Qualcuno può aiutarmi per favore con questo?

risposta

3

Ho messo insieme una soluzione simile sfogliando le fonti OAuth di Spring Security e frammenti di altre soluzioni trovate online. Sto utilizzando Java Config ma forse può aiutare a mappare a una configurazione XML, qui va:

@Configuration 
@EnableOAuth2Client 
public class RestClientConfig { 

    @Value("${http.client.maxPoolSize}") 
    private Integer maxPoolSize; 

    @Value("${oauth2.resourceId}") 
    private String resourceId; 

    @Value("${oauth2.clientId}") 
    private String clientId; 

    @Value("${oauth2.clientSecret}") 
    private String clientSecret; 

    @Value("${oauth2.accessTokenUri}") 
    private String accessTokenUri; 


    @Autowired 
    private OAuth2ClientContext oauth2ClientContext; 


    @Bean 
    public ClientHttpRequestFactory httpRequestFactory() { 
     return new HttpComponentsClientHttpRequestFactory(httpClient()); 
    } 

    @Bean 
    public HttpClient httpClient() { 
     PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); 
     connectionManager.setMaxTotal(maxPoolSize); 
     // This client is for internal connections so only one route is expected 
     connectionManager.setDefaultMaxPerRoute(maxPoolSize); 
     return HttpClientBuilder.create().setConnectionManager(connectionManager).build(); 
    } 

    @Bean 
    public OAuth2ProtectedResourceDetails oauth2ProtectedResourceDetails() { 
     ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails(); 
     details.setId(resourceId); 
     details.setClientId(clientId); 
     details.setClientSecret(clientSecret); 
     details.setAccessTokenUri(accessTokenUri); 
     return details; 
    } 

    @Bean 
    public AccessTokenProvider accessTokenProvider() { 
     ResourceOwnerPasswordAccessTokenProvider tokenProvider = new ResourceOwnerPasswordAccessTokenProvider(); 
     tokenProvider.setRequestFactory(httpRequestFactory()); 
     return new AccessTokenProviderChain(
        Arrays.<AccessTokenProvider> asList(tokenProvider) 
       ); 
    } 

    @Bean 
    public OAuth2RestTemplate restTemplate() { 
     OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2ProtectedResourceDetails(), oauth2ClientContext); 
     template.setRequestFactory(httpRequestFactory()); 
     template.setAccessTokenProvider(accessTokenProvider()); 
     return template; 
    } 
} 

Un po 'importante che ho trovato è che è necessario utilizzare l'AccessTokenProviderChain anche per un solo provider altrimenti l'automatica l'aggiornamento del token (dopo l'autenticazione) non funzionerà.

Per impostare le credenziali utente alla prima richiesta avrete bisogno di questo:

@Autowired 
private OAuth2RestTemplate restTemplate; 

restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("username", username); 
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("password", password); 

Quindi è possibile inviare richieste normalmente usando i metodi RestTemplate, ad esempio:

String url = "http://localhost:{port}/api/users/search/findByUsername?username={username}"; 

    ResponseEntity<User> responseEntity = restTemplate.getForEntity(
      url, User.class, 8081, username); 

Se si desidera per tracciare le richieste sul filo è possibile impostare il livello di log sul client http apache su DEBUG, ad es con la primavera Boot:

logging.level.org.apache.http = DEBUG

+0

cosa succede se il nome utente e la password sono nel corpo POST? come x-www-url-encoded – cosbor11

Problemi correlati