How to configure multiple OAuth2RestTemplates for different services? - spring-boot

How can I configure multiple OAuth2RestTemplates (via OAuth2ProtectedResourceDetails) using Spring Boot so that I can access multiple APIs. They are all configured in same tenant as we see with all configuration being the same except for the scopes.
I believe I did read you cannot have multiple scopes because each JWT token is resource specific but I cannot see examples of having multiple RestTemplates.
Thank you!
security:
oauth2:
client:
client-id: x
client-secret: y
user-authorization-uri: z
access-token-uri: a
scope: B
grant-type: client_credentials
client2:
client-id: x
client-secret: y
user-authorization-uri: z
access-token-uri: a
scope: Q
grant-type: client_credentials
#Bean(name="ngsWbRestTemplate")
public OAuth2RestTemplate buildNgsWbRestTemplate(
OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails
){
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(oAuth2ProtectedResourceDetails);
restTemplate.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
restTemplate.getAccessToken().getValue();
return restTemplate;
}
#Bean(name="OdpRestTemplate")
public OAuth2RestTemplate buildOdpRestTemplate(
OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails,
#Value("#{propertyService.getValue('ODP_BASE_URI')}") String odpBaseUri
){
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(oAuth2ProtectedResourceDetails);
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(odpBaseUri));
restTemplate.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
// test access token retrieval
restTemplate.getAccessToken().getValue();
return restTemplate;
}

I recently made a client that integrates the information of multiple providers who protect their APIs with the OAUth2 protocol. I used this dependency:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
In the configuration.yml file you have to set all the properties needed for your client in order to get tokens from Authorization Servers.
example:
oauth2:
okta:
clientId: XXX-XXX-XXX
clientSecret: XXX-XXX-XXX
grantType: client_credentials
accessTokenUri: https://dev-xxxx.okta.com/oauth2/default/v1/token
scope: custom_service #Created in okta
keycloak:
clientId: app-resource-server
clientSecret: 60bc378c-5c95-4dee-b525-e71993d1596d
grantType: password #For password grant_type
username: user
password: user
accessTokenUri: http://localhost:9001/auth/realms/development/protocol/openid-connect/token
scope: openid profile email
In the main class you need to create a different bean for each resource server that you'd like to send requests with its corresponding access_token in the Authorization header.
#SpringBootApplication
public class DemoApplication {
/* Inject your client properties into ClientCredentialsResourceDetails object
if you need to get tokens using client_credentials grant type*/
#Bean
#ConfigurationProperties("example.oauth2.okta")
protected ClientCredentialsResourceDetails oktaOAuth2Details() {
return new ClientCredentialsResourceDetails();
}
/*Inject your client properties into ResourceOwnerPasswordResourceDetails object
if you need to pass an username and a password*/
#Bean
#ConfigurationProperties("example.oauth2.keycloak")
protected ResourceOwnerPasswordResourceDetails keycloakOAuth2Details() {
return new ResourceOwnerPasswordResourceDetails();
}
//Create the OAuth2RestTemplate bean with the corresponding clientOAuth2Details
#Bean("oktaOAuth2RestTemplate")
protected OAuth2RestTemplate oktaOAuth2RestTemplate() {
return new OAuth2RestTemplate(oktaOAuth2Details());
}
#Bean("keycloakOAuth2RestTemplate")
protected OAuth2RestTemplate keycloakOAuth2RestTemplate() {
return new OAuth2RestTemplate(keycloakOAuth2Details());
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Then you can autowire the OAuth2RestTemplate choosing the desired implementation with the #Qualifier annotation as shown below
#Component
public class AsyncService {
//#Value("#{ #environment['example.baseUrl'] }")
private static final String API_URL1 = "http://localhost:8081";
private static final String API_URL2 = "http://localhost:8082";
private final Logger log = LoggerFactory.getLogger(AsyncService.class);
#Autowired
#Qualifier("oktaOAuth2RestTemplate")
OAuth2RestTemplate oktaOAuth2RestTemplate;
#Autowired
#Qualifier("keycloakOAuth2RestTemplate")
OAuth2RestTemplate keycloakOAuth2RestTemplate;
public void oktaRequest() {
log.info("Okta access_token: {}", oktaOAuth2RestTemplate.getAccessToken());
log.info(oktaOAuth2RestTemplate.getForObject(API_URL1 + "/message", String.class));
}
public void keylcloakRequest() {
log.info("Keycloak access_token: {}", keycloakOAuth2RestTemplate.getAccessToken());
log.info(keycloakOAuth2RestTemplate.getForObject(API_URL2 + "/message", String.class));
}
//#Async
public void requestMessage() {
oktaRequest();
keycloakRequest();
}
}
Spring will refresh the tokens automatically when they expire and that's so cool.

Related

Spring Cloud Gateway redirects to Keycloak login page although Bearer token is set

I am using a setup with Keycloak as Identity Provider, Spring Cloud Gateway as API Gateway and multiple Microservices.
I can receive a JWT via my Gateway (redirecting to Keycloak) via http://localhost:8050/auth/realms/dev/protocol/openid-connect/token.
I can use the JWT to access a resource directly located at the Keycloak server (e.g. http://localhost:8080/auth/admin/realms/dev/users).
But when I want to use the Gateway to relay me to the same resource (http://localhost:8050/auth/admin/realms/dev/users) I get the Keycloak Login form as response.
My conclusion is that there must me a misconfiguration in my Spring Cloud Gateway application.
This is the Security Configuration in the Gateway:
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository) {
// Authenticate through configured OpenID Provider
http.oauth2Login();
// Also logout at the OpenID Connect provider
http.logout(logout -> logout.logoutSuccessHandler(
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
//Exclude /auth from authentication
http.authorizeExchange().pathMatchers("/auth/realms/ahearo/protocol/openid-connect/token").permitAll();
// Require authentication for all requests
http.authorizeExchange().anyExchange().authenticated();
// Allow showing /home within a frame
http.headers().frameOptions().mode(Mode.SAMEORIGIN);
// Disable CSRF in the gateway to prevent conflicts with proxied service CSRF
http.csrf().disable();
return http.build();
}
}
This is my application.yaml in the Gateway:
spring:
application:
name: gw-service
cloud:
gateway:
default-filters:
- TokenRelay
discovery:
locator:
lower-case-service-id: true
enabled: true
routes:
- id: auth
uri: http://localhost:8080
predicates:
- Path=/auth/**
security:
oauth2:
client:
registration:
keycloak:
client-id: 'api-gw'
client-secret: 'not-relevant-but-correct'
authorizationGrantType: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
scope: openid,profile,email,resource.read
provider:
keycloak:
issuerUri: http://localhost:8080/auth/realms/dev
user-name-attribute: preferred_username
server:
port: 8050
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka
register-with-eureka: true
fetch-registry: true
How can I make the Gateway able to know that the user is authenticated (using the JWT) and not redirect me to the login page?
If you want to make requests to Spring Gateway with access token you need to make it a resource server. Add the following:
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
application.yml
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://.../auth/realms/...
SecurityConfiguration.java
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ReactiveClientRegistrationRepository clientRegistrationRepository) {
// Authenticate through configured OpenID Provider
http.oauth2Login();
// Also logout at the OpenID Connect provider
http.logout(logout -> logout.logoutSuccessHandler(
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
// Require authentication for all requests
http.authorizeExchange().anyExchange().authenticated();
http.oauth2ResourceServer().jwt();
// Allow showing /home within a frame
http.headers().frameOptions().mode(Mode.SAMEORIGIN);
// Disable CSRF in the gateway to prevent conflicts with proxied service CSRF
http.csrf().disable();
return http.build();
}
I bypassed the problem by communicating directly with Keycloak without relaying requests to it via Spring Cloud Gateway.
That's actually not a workaround but actually best practice/totally ok as far as I understand.
This code is for Client_credentials grant_type. if you use other grant type you need to add client_id and client_secret in request parameters.
public class MyFilter2 extends OncePerRequestFilter {
private final ObjectMapper mapper = new ObjectMapper();
#Value("${auth.server.uri}")
private String authServerUri;
#Value("${client_id}")
private String clientId;
#Value("${client_secret}")
private String clientSecret;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws IOException {
try {
String token = httpServletRequest.getHeader("Authorization");
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type","application/x-www-form-urlencoded");
headers.set("Authorization",token);
final HttpEntity finalRequest = new HttpEntity("{}", headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(authServerUri,finalRequest,String.class);
if (!HttpStatus.OK.equals(response.getStatusCode())) {
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("status", HttpStatus.UNAUTHORIZED.value());
errorDetails.put("message", "Invalid or empty token");
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
} else {
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}catch(HttpClientErrorException he) {
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("status", HttpStatus.UNAUTHORIZED.value());
errorDetails.put("message", "Invalid or empty token");
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
}catch (Exception exception) {
}
}

How to persist OAuth2AuthorizedClient in redis-session

My project uses redis session with springboot session and spring security 5.1.10. I just migrated the old oauth2 implementation. Before, when I restarted the app I still had the access_token and refresh_token. With this implementation the user is logged in, but I loose the AuthorizedClients so loadAuthorizedClient function returns null after restarting. Also in production we have many containers with the same app. Is there any springboot stardard way to achieve this? like register some bean or something.
application.yml
...
session:
store-type: redis
redis:
namespace: spring:session:${spring.application.name}
redis:
host: ${redissession.host}
password: ${redissession.password}
port: ${redissession.port}
security:
oauth2:
client:
registration:
biocryptology:
provider: example
client-id: client
client-secret: xxx
client-authentication-method: basic
authorization-grant-type: authorization_code
redirect-uri-template: "{baseUrl}/login"
scope:
- openid
provider:
example:
issuer-uri: https://....
...
Controller.java
#Autowired
private OAuth2AuthorizedClientService clientService;
#GetMapping("/user")
public String getOidcUserPrincipal() throws InvalidSessionException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!(authentication.getPrincipal() instanceof OidcUser)) {
throw new InvalidSessionException();
}
OidcUser principal = ((OidcUser) authentication.getPrincipal());
LOG.info("oidc: {}", principal.getName());
OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;
LOG.info("authentication: {}", oauth2Token);
OAuth2AuthorizedClient client = clientService
.loadAuthorizedClient(oauth2Token.getAuthorizedClientRegistrationId(), authentication.getName());
LOG.info("client: {}", client);
return "logged";
}
The goal is getting the access_token and refresh_token across containers, any other way without OAuth2AuthorizedClientService maybe?
EDIT:
<!-- Spring -->
<spring-cloud.version>Greenwich.SR5</spring-cloud.version>
Registering a bean did the trick, it saves it in session, but then OAuth2AuthorizedClientService breaks down for every case and needs a workaround searching in session directly or using OAuth2AuthorizedClientRepository autowired:
#Bean
public OAuth2AuthorizedClientRepository authorizedClientRepository() {
return new HttpSessionOAuth2AuthorizedClientRepository();
}
controller.java
#Autowired
private OAuth2AuthorizedClientRepository clientRepository;
#GetMapping("/user")
public Map<String, Object> getOidcUserPrincipal(HttpServletRequest request) throws InvalidSessionException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!(authentication.getPrincipal() instanceof OidcUser)) {
throw new InvalidSessionException();
}
OidcUser principal = ((OidcUser) authentication.getPrincipal());
OAuth2AuthorizedClient client = clientRepository
.loadAuthorizedClient(oauth2Token.getAuthorizedClientRegistrationId(), authentication, request);
LOG.info("client: {}", client);
if (Objects.nonNull(client)) {
String token = client.getAccessToken().getTokenValue();
String refreshtoken = client.getRefreshToken().getTokenValue();
LOG.info("token: {} {}", token, refreshtoken);
}
return principal.getClaims();
}

Storing JWT tokens on OAuth2 web client using Spring Security

I'm implementing an OAuth2 web application Client using Spring Boot 2.1.3 and Spring Security 5.1.3 that is obtaining JWT tokens from an authorization server through authorization code grant type and calls a protected resource server.
This is how the implementation looks up till now:
Security configuration and a restTemplate bean used to call the protected resource:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.and()
.oauth2Client()
.and().logout().logoutSuccessUrl("/");
}
#Bean
public RestTemplate restTemplate(OAuth2AuthorizedClientService clientService) {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new AuthorizationHeaderInterceptor(clientService));
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
The interceptor that adds the authorization header (from the framework's InMemoryOAuth2AuthorizedClientService) in the restTemplate:
public class AuthorizationHeaderInterceptor implements ClientHttpRequestInterceptor {
private OAuth2AuthorizedClientService clientService;
public AuthorizationHeaderInterceptor(OAuth2AuthorizedClientService clientService) {
this.clientService = clientService;
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String accessToken = null;
if (authentication != null && authentication.getClass().isAssignableFrom(OAuth2AuthenticationToken.class)) {
OAuth2AuthenticationToken auth = (OAuth2AuthenticationToken) authentication;
String clientRegistrationId = auth.getAuthorizedClientRegistrationId();
OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(clientRegistrationId, auth.getName());
accessToken = client.getAccessToken().getTokenValue();
request.getHeaders().add("Authorization", "Bearer " + accessToken);
}
return execution.execute(request, bytes);
}
}
And the controller that calls the protected resource server:
#Controller
#RequestMapping("/profile")
public class ProfileController {
#Autowired
private RestTemplate restTemplate;
#Value("${oauth.resourceServerBase}")
private String resourceServerBase;
#GetMapping
public String getProfile(Model model) {
Profile profile = restTemplate.getForEntity(resourceServerBase + "/api/profile/", Profile.class).getBody();
model.addAttribute("profile", profile);
return "profile";
}
}
The OAuth2 client configuration is directly in the application.yml:
spring:
security:
oauth2:
client:
registration:
auth-server:
client-id: webClient
client-secret: clientSecret
scope: read,write
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8081/client/login/oauth2/code/auth-server
provider:
auth-server:
authorization-uri: http://localhost:8080/auth-server/oauth/authorize
token-uri: http://localhost:8080/auth-server/oauth/token
user-info-uri: http://localhost:8082/resource-server/users/info
user-name-attribute: user_name
After doing some debugging I've observed that at the end of a successful authentication flow through OAuth2LoginAuthtenticationFilter the framework is storing the obtained access and refresh JWT tokens under OAuth2AuthorizedClient model in memory through the provided InMemoryOAuth2AuthorizedClientService.
I am trying to find out how to override this behaviour so that the tokens can remain available after a server restart. And also keep the user logged in based on this.
Should I just provide a custom OAuth2AuthorizedClientService implementation? How could I configure Spring Security to use it? And should this custom implementation store the tokens in a cookie?
Should I just provide a custom OAuth2AuthorizedClientService
implementation?
I think yes, for solving your use case
How could I configure Spring Security to use it?
From spring doc:
If you would like to provide a custom implementation of
AuthorizationRequestRepository that stores the attributes of
OAuth2AuthorizationRequest in a Cookie, you may configure it as shown
in the following example:
#EnableWebSecurity
public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Client()
.authorizationCodeGrant()
.authorizationRequestRepository(this.cookieAuthorizationRequestRepository())
...
}
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> cookieAuthorizationRequestRepository() {
return new HttpCookieOAuth2AuthorizationRequestRepository();
}
}

Add optional Google Sign In in my Spring Boot + Security + web Application

I am working on a Spring boot web application. I have now working a registration and login system using Spring Security with a custom userDetailService.
Now I want add a register-login system using Google Accounts. I created my Google API keys and added them to the application.properties. I think is not necessary use .yml propertie files here:
# ===============================
# = OAUTH2
# ===============================
security.oauth2.client.client-id=clientId Here
security.oauth2.client.client-secret=clientSecret here
security.oauth2.client.access-token-uri=https://www.googleapis.com/oauth2/v3/token
security.oauth2.client.user-authorization-uri=https://accounts.google.com/o/oauth2/auth
security.oauth2.client.token-name=oauth_token
security.oauth2.client.authentication-scheme=query
security.oauth2.client.client-authentication-scheme=form
security.oauth2.client.scope=profile
security.oauth2.resource.user-info-uri=https://www.googleapis.com/userinfo/v2/me
security.oauth2.resource.prefer-token-info=false
I added OAuth2 support to my Spring Boot application on this way:
#SpringBootApplication
#EnableOAuth2Sso
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
Now I want keep the posibility to login using Google or login using a website account, but I only found manuals about unique login or multiple providers login (Facebook, Google, Twitter..)
In my SpringSecurity configuration class I have this. I think that I have to create a authenticationProvider for Google and link it to the google access url in my app, but I am so confused yet about this:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
/**
* Obtenemos informaciĆ³n de persistencia
*/
// #formatter:off
auth
//.authenticationProvider(googleOauth2AuthProvider())
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
// #formatter:on
}
...
#Override
protected void configure(HttpSecurity http) throws Exception {
String[] anonymousRequest = { urls};
http
.authorizeRequests()
//..other rules
You have to use a composite filter in which you configure your desired authentication providers, for example:
private Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List<Filter> filters = new ArrayList<>();
filters.add(ssoFilter(facebook(), "/login/facebook"));
filters.add(ssoFilter(google(), "/login/google"));
filter.setFilters(filters);
return filter;
}
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationFilter = new OAuth2ClientAuthenticationProcessingFilter(
path);
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
oAuth2ClientAuthenticationFilter.setRestTemplate(oAuth2RestTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(client.getResource().getUserInfoUri(),
client.getClient().getClientId());
tokenServices.setRestTemplate(oAuth2RestTemplate);
oAuth2ClientAuthenticationFilter.setTokenServices(tokenServices);
return oAuth2ClientAuthenticationFilter;
}
where:
#Bean
#ConfigurationProperties("google")
public ClientResources google() {
return new ClientResources();
}
#Bean
#ConfigurationProperties("facebook")
public ClientResources facebook() {
return new ClientResources();
}
and:
class ClientResources {
#NestedConfigurationProperty
private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();
#NestedConfigurationProperty
private ResourceServerProperties resource = new ResourceServerProperties();
public AuthorizationCodeResourceDetails getClient() {
return client;
}
public ResourceServerProperties getResource() {
return resource;
}
}
finally, add the filter before the BasicAuthenticationFilter in your HTTP security config:
#Override
protected void configure(HttpSecurity http) throws Exception {
String[] anonymousRequest = { urls};
http
.authorizeRequests()
//..other rules
addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
Ps: your configuration properties has to start with the value specified in the #ConfigurationProperties("facebook"):
facebook:
client:
clientId: yourCliendId
clientSecret: yourClientSecret
accessTokenUri: https://graph.facebook.com/oauth/access_token
userAuthorizationUri: https://www.facebook.com/dialog/oauth
tokenName: oauth_token
authenticationScheme: query
registeredRedirectUri: http://localhost:8083/app.html
preEstablishedRedirectUri: http://localhost:8083/app.html
clientAuthenticationScheme: form
resource:
userInfoUri: https://graph.facebook.com/me
This is inspired from the example presented here: https://github.com/spring-guides/tut-spring-boot-oauth2/tree/master/github
You can achieve this using Spring Social or OAUTH2
If you want to do using spring social be aware that Google is not supported by default in spring boot social so you have to do a couple of extra steps.
Add Maven Dependencies
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-google</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
Add a GoogleAutoConfiguration Class
Do Ctrl+Shift+T in your IDE(eclipse) and look for FacebookAutoConfiguration class you should be able to find it either in org.springframework.boot.autoconfigure.social package in spring-autoconfigure.jar. Copy this File and replace Facebook with Google.
3.Add GoogleProperties
In the same package add the below class
#ConfigurationProperties(prefix = "spring.social.google")
public class GoogleProperties extends SocialProperties{
Update the application.properties with your google API key
Follow this link for complete description and step by step instruction
Hope It helps !!
If you want to do using OAUTH2 here is a working example

Spring OAuth2 ResourceServer external AuthorizationServer

How do you setup a separate Spring OAuth2 ResourceServer only, that uses and 3rd party AuthorizationServer
All examples I see always implement the ResourceServer and AuthorizationServer in same application.
I don't want to implement the AuthorizationServer as someone else is going to provide this.
Have tried with no luck
#Configuration
#EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter
And application.yml includes
security:
oauth2:
resource:
userInfoUri: https://...../userinfo
Adding to my question some further details::
In my understanding - with OAuth there are 4 players:
resource owner: a person
resource server: server exposing a protected API (protected by the authentication server)
authentication server: the server that handles issuing access tokens to clients
client: an application (say website) accessing the resource server API's after resource owner have given consent
I have tried various tutorials, but all seem to implement their own Authorisation server
http://www.swisspush.org/security/2016/10/17/oauth2-in-depth-introduction-for-enterprises
https://gigsterous.github.io/engineering/2017/03/01/spring-boot-4.html
or are examples of implementing the client player
http://www.baeldung.com/spring-security-openid-connect
https://spring.io/guides/tutorials/spring-boot-oauth2/
My Question is:
How do I implement just the Resource Server which secures my REST API, via a 3rd party authentication server, nothing more.
I have work this out - all you need is:
#SpringBootApplication
#EnableResourceServer
public class ResourceServer {
public static void main(String[] args) {
SpringApplication.run(ResourceServer.class, args);
}
}
With the application.yml as posted in the original question of:
security:
oauth2:
resource:
userInfoUri: https://........userinfo
I've created two sample separate applications, one of them acting as oauth client, and another one acting as a resource server, and both of them are using an external authentication server (which is facebook in this example).
The scenario in the example is as follows, the user opens app1 (oauth client) and gets redirected to first page, and once he clicks login, he'll be redirected to facebook login, and after a successful login, he will get back to the first page. If he clicked on the first button, a call to an api within the same application will be made, and will appear beside message 1 label, and if he clicked on the second button, a call to an api within app2 (resource server) will be made, and the message will be displayed beside message 2 label.
If you checked the logs, you will find the api call going from app1 to app2 containing the access token in the request parameters.
Logs for app1 calling app2
Please find the source code on the git repository here
This is the configuration for app1 (oauth client)
app1 web security config
#Configuration
#EnableOAuth2Sso
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**", "/error**").permitAll()
.anyRequest().authenticated().and().logout().logoutSuccessUrl("/").permitAll().and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("PUT");
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
app1 application properties
security:
oauth2:
client:
clientId: <your client id>
clientSecret: <your client secret>
accessTokenUri: https://graph.facebook.com/oauth/access_token
userAuthorizationUri: https://www.facebook.com/dialog/oauth?redirect_url=https://localhost:8443/
tokenName: access_token
authenticationScheme: query
clientAuthenticationScheme: form
registered-redirect-uri: https://localhost:8443/
pre-established-redirect-uri: https://localhost:8443/
resource:
userInfoUri: https://graph.facebook.com/me
logging:
level:
org.springframework.security: DEBUG
This is the configuration for app2 (resource server)
app2 resource server config
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
String[] ignoredPaths = new String[] { "/error", "/login", "/doLogut", "/home", "/pageNotFound", "/css/**",
"/js/**", "/fonts/**", "/img/**" };
#Value("${security.oauth2.resource.user-info-uri}")
private String userInfoUri;
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(ignoredPaths).permitAll().anyRequest().authenticated();
}
#Primary
#Bean
public UserInfoTokenServices tokenService() {
final UserInfoTokenServices tokenService = new UserInfoTokenServices(userInfoUri, clientId);
return tokenService;
}
}
app2 application properties
security:
oauth2:
resource:
userInfoUri: https://graph.facebook.com/me
client:
client-id: <your client id>
logging:
level:
org.springframework.security: DEBUG
This is where app1 controller calls an api on app2 (hi2 api)
#RestController
#CrossOrigin(origins = "*", allowedHeaders = "*")
public class UserController {
#Autowired
OAuth2RestTemplate restTemplate;
#RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
#RequestMapping("/hi")
public String hi(Principal principal) {
return "Hi, " + principal.getName();
}
#RequestMapping("/hi2")
public String hi2(Principal principal) {
final String greeting = restTemplate.getForObject("http://127.0.0.1:8082/api/hello", String.class);
System.out.println(greeting);
return greeting;
}
}

Resources