Spring OAuth2 ResourceServer external AuthorizationServer - spring

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;
}
}

Related

OAuth2 with Google and Spring Boot - I can't log out

I've been trying to get a successful Oauth2 login with Google and Spring Boot for a while now. This only works partially. Why partly - because I can't manage the logout or when I pressed the logout button I see an empty, white browser page with my URL (http://localhost:8181/ben/"). After a refresh of the page I get error from google, but if I open a new tab, enter my url, I'm still logged in to google, because I can see my user, which I'm outputting to my react application.
#SpringBootApplication
#EnableOAuth2Sso
#RestController
#CrossOrigin
public class SocialApplication extends WebSecurityConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(SocialApplication.class, args);
}
#RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
#RequestMapping("/logout")
public String fetchSignoutSite(HttpServletRequest request, HttpServletResponse response) {
Cookie rememberMeCookie = new Cookie("JSESSIONID", "");
rememberMeCookie.setMaxAge(0);
response.addCookie(rememberMeCookie);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
auth.getPrincipal();
return "redirect:/ben/login";
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/ben/*").permitAll().anyRequest().authenticated().and()
.logout().logoutSuccessUrl("http://localhost:8181/ben/login").invalidateHttpSession(true)
.clearAuthentication(true).deleteCookies("JSESSIONID");
}
My application.yml file looks like this:
# Spring Boot configuration
spring:
profiles:
active: google
# Spring Security configuration
security:
oauth2:
client:
clientId: 415772070383-3sapp4flauo6iqsq8eag7knpcii50v9k.apps.googleusercontent.com
clientSecret: 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
accessTokenUri: https://www.googleapis.com/oauth2/v4/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
clientAuthenticationScheme: form
scope:
- email
- profile
resource:
userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
preferTokenInfo: true
# Server configuration
server:
port: 8181
servlet:
context-path: /ben
That fetchSignoutSite only emptying the JsessionId and logging out from Spring Security context. So you would still need to add part where you go to google and sign out from there which I have no experience on implementation.

How to configure multiple OAuth2RestTemplates for different services?

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.

Service-to-service communication with feign in spring security 5

in fact the spring-oauth project turn into maintenance mode we trying migrate our application into pure spring security 5 which support resource server configuration as well.
Our actual resource server configuration looks like this:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
authorizeRequests -> {
authorizeRequests.antMatchers("/api/unsecured/**").permitAll();
authorizeRequests.anyRequest().authenticated();
}
);
}
#Bean
#ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
#Bean
public TokenStore jwkTokenStore() {
return new JwkTokenStore("http://localhost:8080/...", new JwtAccessTokenConverter());
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(){
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());
}
#Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
}
and these properties:
security:
oauth2:
client:
client-id: service-id
client-secret: secret
access-token-uri: http://localhost:8081/oauth/token
This resource server is configured to work with jwt token. To verify token uses rsa public key from link passes to jwkstore. It is also able call another resource server with Feign.
And this is new configuration:
#Configuration
static class OAuth2ResourceServerConfig extends WebSecurityConfigurerAdapter {
private final JwtDecoder jwtDecoder;
ResourceServerConfiguration(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/public/unsecured/**").permitAll()
.anyRequest().authenticated())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
.jwt(jwtConfigurer -> {
jwtConfigurer.decoder(NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/...").build());
jwtConfigurer.jwtAuthenticationConverter(tokenExtractor());
})
);
}
This configuration works fine to decode and verify tokens, but Feign doesn't work. Previous configuration with spring oauth2 supports Oauth2 feign interceptor which call authorization server to get its own access token. But I don't know how to configure this in spring security 5. This is flow which I need:
frontend client call spring resource server A with token
resource server A need data from resource server B
resource server A call authorization server to get access token with client_credentials grant type
resource server A call resource server B with its access token set to request header by feign
resource server A return all data to frontend client
Can you tell me how to configure 3. and 4. step in spring security 5 without spring's oauth project? Thank you.

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();
}
}

Spring Zuul Eureka Security Authentication get user info from Zuul

I'm using #EnableOAuth2Sso to authenticate a user with a third party authentication server on the Zuul server. I need to pass user info from Zuul to the routed servers. I've set up the request endpoint /userinfo to return a jsonified representation of a flattened version of the userinfo from the third party. How do I get this userinfo to one of the resource servers?
What I've tried so far:
I've tried making a request using the #LoadBalanced #Bean RestTemplate been. However, I get redirected to the third party for authorization. The sensitive-headers is set to none. I've checked which headers were going through:
["upgrade-insecure-requests","user-agent","accept","accept-language","cookie",
"authorization","x-forwarded-host","x-forwarded-proto",
"x-forwarded-prefix","x-forwarded-port","x-forwarded-for","accept-encoding",
"content-length", "host","connection"]
So, then I tried using #LoadBalanced #Bean OAuth2RestTemplate. I had to set the config security.basic.enabled=false to prevent the Authentication User Login Prompt from appearing. This produces UserRedirectRequiredException
Resource Server
#RequestMapping(value = "/test", method = RequestMethod.GET)
public ResponseEntity<String> test3() {
return restTemplate.getForEntity("http://zuul-server/userinfo", String.class);
}
Zuul Server
#RequestMapping(value = "/userinfo", produces = MediaType.APPLICATION_JSON_VALUE)
public User getInfo(OAuth2Authentication auth) {
return service.getUser(auth); // Returns User Object
}
Additional Notes
The Resource Server has not been annotated with #EnableResourceServer. If it was, a forwarded request will result in Invalid access token error message.
This is what I have working on our system for passing Oauth2 JWT tokens.
#Configuration
#EnableResourceServer
public class JwtSecurityConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/**").hasAuthority("ROLE_API")
.and()
.csrf().disable();
}
}
And the config portion you might need.
services:
path: /services/**
serviceId: services
stripPrefix: false
sensitiveHeaders: true
auth:
path: /oauth/**
serviceId: saapi-auth-server
stripPrefix: false
sensitiveHeaders: true
There was very little documentation on this. So it was really just hacking away at it until I could get it to pass tokens on.

Resources