2

I am trying to front or "wrap" a RESTful API service application in front of a backend HTTP service. The backend service maintains authentication state via proprietary sessions. Authentication with the backend server is achieved through a convoluted conversation between the authenticating client and the server. In general, interaction with this server is non-standard and not good enough to expose to our customers as an API. However there is too much legacy to overhaul and re-engineed the entire HTTP interface.

My objective is to abstract the interface to this backend server by fronting this server with a RESTful server that implements OAuth2 authentication via password grant so that 3rd party APIs can interface with the backend server in a standard and well understood way. I have built a simple REST SpringBoot application that can authenticate with the backend. The part I am battling with is figuring out how best to map authenticated tokens/requests to the backend's session IDs.

In essence I am trying to associate a bit of state on each authenticated authorization. As a solution I've thought about implementing my own TokenStore which will maintain the mapping. Alternatively I have thought about implementing a "Custom Token Granter". However I have a strong intuition that there is a better and easier way to achieve my objective.

Additional information:

@EnableAuthorizationServer and @EnableResourceServer are in the same application.

Version information:

org.springframework.security.oauth:spring-security-oauth2 2.0.13.RELEASE org.springframework.security:spring-security-config 4.2.2.RELEASE org.springframework.boot:spring-boot-starter-security 1.5.3.RELEASE

This is my Resource Server Config:

@Configuration
@Profile(Common.CONST_EXTAPI_PROFILE)
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and()
        .headers().frameOptions().disable().and()            
            .authorizeRequests()                
            .antMatchers(HttpMethod.GET, "/", "/home","/register","/login", "/auth").permitAll()
            .antMatchers(HttpMethod.OPTIONS,"/oauth/token").permitAll()
            .antMatchers("/extapi/agent", "/extapi/agent/**", "/extapi/account/**", "/extapi/sale/**").authenticated();
    }
}

This is my Authentication Server Config:

@Configuration
@Profile(Common.CONST_EXTAPI_PROFILE)
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Order(-1)
    public class MyWebSecurity extends WebSecurityConfigurerAdapter {
       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http
              .authorizeRequests()
              .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
       }
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "foobar".toCharArray())
            .getKeyPair("test");
        converter.setKeyPair(keyPair);
        return converter;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
    {
        clients.inMemory()
            .withClient("acme")
            .autoApprove(true)
            .secret("acmesecret")
            .authorizedGrantTypes("password", "authorization_code", "refresh_token").scopes("openid");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            .authenticationManager(authenticationManager)
            .accessTokenConverter(jwtAccessTokenConverter());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer)
        throws Exception {
    oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
        "isAuthenticated()");
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
}

And the Custom Authentication Provider

@Component
@Profile(Common.CONST_EXTAPI_PROFILE)
public class CustomAuthenticationProvider implements AuthenticationProvider 
{
    @Autowired
    private LoginSessionData sessionData;

    @Autowired
    private GuiAuthenticationService authService;

    @Autowired
    private ContextService contextService;

    @Autowired
    private RSAEncryptionService rsaService;

    @Autowired
    private ObjectMapper mapper;

    @Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {
        final String username = auth.getName();
        final String password = auth.getCredentials().toString();
        Authentication result = null;
        DataWrapper oauthResponse = new DataWrapper();
    try
    {
        BaseResponse resp = authService.authenticate(contextService.getCompanyID(), false, null);        
        oauthResponse.setAuth(rsaService.getPublicKey());
        oauthResponse.setData(mapper.writeValueAsString(resp));
        if(resp.getState().equals("REQUIRE_UTF8_USERNAME")){
            BaseRequest request = new BaseRequest();        
            request.setData(username);
            request.setCid(Integer.toString(contextService.getCompanyID()));
            sessionData.captureData(request);
            resp = authService.process(request);
        }
        if(resp.getState().equals("REQUIRE_RSA_PASSWORD"))
        {
            BaseRequest request = new BaseRequest();        
            request.setData(password);
            request.setCid(Integer.toString(contextService.getCompanyID()));
            resp = authService.process(request);
        }
        if(resp.getState().equals("AUTHENTICATED"))
        {
            UsernamePasswordAuthenticationToken token = 
                new UsernamePasswordAuthenticationToken(username, password, Collections.emptyList());        
            result = token;
        }
    }
    catch (Exception e)
    {
        sessionData.setCurrentState(AuthenticationState.INVALID);
        oauthResponse.setError(e.getLocalizedMessage());
        oauthResponse.setData(Common.CONST_FATAL_ERROR);
        e.printStackTrace();
    }
    if(result == null)
        throw new BadCredentialsException("External system authentication failed");

    return result;
    }

    @Override
    public boolean supports(Class<?> auth) {
        return auth.equals(UsernamePasswordAuthenticationToken.class);
    }
}
Donovan
  • 88
  • 8
  • Do you need the session on authentication server or on resource server? And why you need any session at all? BTW: REST is stateless and has no session. – dur Dec 04 '17 at 15:55
  • _Do you need the session on authentication server or on resource server?_ I imagine that I would need to save the session somewhere (to a map) in the authentication server. I then need to access this session data from the resource server when I forward calls onto the backend server. The backend server requires the session ID with every request. Its not an option to re-authenticate. _And why you need any session at all? REST is stateless and has no session._ In my view REST is stateless apart from authentication tokens and Session IDs. – Donovan Dec 04 '17 at 16:02
  • DId you consider JWT? You could put the session ID in JWT. – dur Dec 04 '17 at 16:04
  • I've been through all Dave Syer's examples 1000s of times but for some reason I didn't consider that as a viable option. Thanks I will look into that. – Donovan Dec 04 '17 at 16:26

0 Answers0