title: OAuth2 SSO for multiple resource servers with spring boot and jHipster
body: <p>So, I have an oAuth2 app which is jHipster app (using mongodb). I want to connect 3 resource apps to that app but all of them should share the same user base, so that the users should be able to login only once. </p>
  
  <p>Is there a way to configure multiple resources in Spring Boot with jHipster so that it won't be as a separate client that would need username and password before accessing the resource? </p>
  
  <p>And also how can I specify user role for each resource server?</p>
  
  <p>All of the app are based on spring-boot.</p>
  
  <p>The diagram below is a simplistic view of what I'm trying to accomplish.</p>
  
  <p><img src="https://i.stack.imgur.com/sSXH1.png" alt="enter image description here"></p>
  
  <p>So the OAuth2 app has has the Authorization Server configuration:</p>
  
  <pre><code>@Configuration
  @EnableAuthorizationServer
  protected static class AuthorizationServerConfiguration extends
          AuthorizationServerConfigurerAdapter implements EnvironmentAware {
  
      private static final String ENV_OAUTH = "authentication.oauth.";
      private static final String PROP_CLIENTID = "clientid";
      private static final String PROP_SECRET = "secret";
      private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
  
      private RelaxedPropertyResolver propertyResolver;
  
      @Inject
      private OAuth2AccessTokenRepository oAuth2AccessTokenRepository;
  
      @Inject
      private OAuth2RefreshTokenRepository oAuth2RefreshTokenRepository;
  
      @Bean
      public TokenStore tokenStore() {
          return new MongoDBTokenStore(oAuth2AccessTokenRepository,
                  oAuth2RefreshTokenRepository);
      }
  
      @Inject
      @Qualifier("authenticationManagerBean")
      private AuthenticationManager authenticationManager;
  
      @Override
      public void configure(AuthorizationServerEndpointsConfigurer endpoints)
              throws Exception {
  
          endpoints.tokenStore(tokenStore()).authenticationManager(
                  authenticationManager);
      }
  
      @Override
      public void configure(ClientDetailsServiceConfigurer clients)
              throws Exception {
          clients.inMemory()
                  .withClient("app-auth")
                  .scopes("read", "write")
                  .authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
                  .authorizedGrantTypes("password", "refresh_token")
                  .secret(propertyResolver.getProperty(PROP_SECRET))
                  .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800))
  
                  .and()
  
                  .withClient("app-A")
                  .scopes("read", "write")
                  .authorities(AuthoritiesConstants.ADMIN,AuthoritiesConstants.USER)
                  .authorizedGrantTypes("password", "refresh_token")
                  .secret(propertyResolver.getProperty(PROP_SECRET))
                  .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800))
  
                  .and()
  
                  .withClient("app-A")
                  .scopes("read", "write")
                  .authorities(AuthoritiesConstants.ADMIN,AuthoritiesConstants.USER)
                  .authorizedGrantTypes("password", "refresh_token")
                  .secret(propertyResolver.getProperty(PROP_SECRET))
                  .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800))
  
                  .and()
  
                  .withClient("app-C")
                  .scopes("read", "write")
                  .authorities(AuthoritiesConstants.ADMIN,AuthoritiesConstants.USER)
                  .authorizedGrantTypes("password", "refresh_token")
                  .secret(propertyResolver.getProperty(PROP_SECRET))
                  .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
  
  
      }
  
      @Override
      public void setEnvironment(Environment environment) {
          this.propertyResolver = new RelaxedPropertyResolver(environment,
                  ENV_OAUTH);
      }
  }
  </code></pre>
  
  <p>As well the OAuth2 app has has the Resource Server configuration:</p>
  
  <pre><code>@Configuration
  @EnableResourceServer
  protected static class ResourceServerConfiguration extends
          ResourceServerConfigurerAdapter {
  
  @Inject
  private Http401UnauthorizedEntryPoint authenticationEntryPoint;
  
  @Inject
  private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
  
  @Override
  public void configure(HttpSecurity http) throws Exception {
      http.exceptionHandling()
              .authenticationEntryPoint(authenticationEntryPoint)
              .and()
              .logout()
              .logoutUrl("/api/logout")
              .logoutSuccessHandler(ajaxLogoutSuccessHandler)
              .and()
              .csrf()
              .requireCsrfProtectionMatcher(
                      new AntPathRequestMatcher("/oauth/authorize"))
              .disable().headers().frameOptions().disable()
              .sessionManagement()
              .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
              .and().authorizeRequests().antMatchers("/api/authenticate")
              .permitAll().antMatchers("/api/register").permitAll()
              .antMatchers("/api/logs/**")
              .hasAnyAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/api/**").authenticated()
              .antMatchers("/metrics/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/health/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/trace/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/dump/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/shutdown/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/beans/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/configprops/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/info/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/autoconfig/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/env/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/trace/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/api-docs/**")
              .hasAuthority(AuthoritiesConstants.ADMIN)
              .antMatchers("/protected/**").authenticated();
          }
      }
  </code></pre>
  
  <p>And the Resource Server on App A (which is almost the same for B and C):</p>
  
  <pre><code>@Configuration
  @EnableResourceServer
  protected static class ResourceServer extends ResourceServerConfigurerAdapter {
  
  @Override
  public void configure(HttpSecurity http) throws Exception {
      http.requestMatchers().antMatchers("/api/**")
              .and()
              .authorizeRequests()
              .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
              .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
              .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
              .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
              .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
              .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')");
  }
  
      @Override
      public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
          resources.resourceId("app-A");
      }
  
  }
  </code></pre>