I am facing issue with Token Relay while communicating from one ResourceServer to another ResourceServer.
My AuthServer is based on Dave Sayer's sample and this is the application.yml for resource server1.
security:
user:
password: none
oauth2:
client:
accessTokenUri: http://localhost:9999/uaa/oauth/token
userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
clientId: trusted
clientSecret: secret
The config is very similar in resource server2, except that it is using a different clientId
Here is how i am creating the OAuth2RestTemplate in resource server1.
#LoadBalanced
#Bean
#Autowired
public OAuth2RestTemplate loadBalancedOauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
This call requires JWT OAuth2 Token Relay, but its not happening probably.
#GetMapping("/test-relay")
public String fetchMyProfile2() {
final ResponseEntity<String> forEntity = oauthRestTemplate.getForEntity("http://my-oauth/users/me", String.class);
final String body = forEntity.getBody();
System.out.println("body = " + body);
return body;
}
This is the exception i get while invoking this endpoint /test-relay from Postman RestClient. I am specifying JWT Token in Authorization Header while making the call.
org.springframework.security.oauth2.client.resource.UserRedirectRequiredException: A redirect is required to get the users approval
at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.getRedirectForAuthorization(AuthorizationCodeAccessTokenProvider.java:359)
at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:205)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:148)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:121)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:648)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128)
I am using Spring Boot 1.5.2/3. My Resource Server is a UI Server as well, and this call works fine if i use Web Browser to hit the url.
UPDATE-1
This issue only happens for Resource Server that is a UI server too i.e. with #EnableOAuth2Sso annotation present on it. For a pure Resource Server that does not have #EnableOAuth2Sso, token relay works perfectly fine.
You might be affected by the bug I reported https://github.com/spring-cloud/spring-cloud-security/issues/123. See if this workaround helps :
#Configuration
public class WorkaroundConfig extends WebMvcConfigurerAdapter {
#Autowired
#Qualifier("tokenRelayRequestInterceptor")
HandlerInterceptor handlerInterceptor;
#Override
public void addInterceptors (InterceptorRegistry registry) {
registry.addInterceptor(handlerInterceptor);
}
}
Related
I'm new to Spring boot and Spring Security. I have microservice project using Spring boot. And in my gateway app, I use OAuth2 for authentication. The authentication provider is from my organization and it is OIDC implementation.
I'm using oauth2 resource server to authenticate the bearer token, by configuring jwk-set-uri and jwk-set-uri properties.
spring-boot-starter-web => 2.6.7
spring-boot-starter-oauth2-resource-server => 2.6.7
spring-security => 5.6.3
application.properties
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://<org-auth-url>.com
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://<org-auth-url>/<jwk-uri>
With just above configuration, the authentication works fine. So I have not added any Security Config class also. But for authorization and other processing like to get user data in Controller, I need the user information and AD group details.
I have the user information endpoint URL. And when I test it in postman client, the response contains user information along with AD groups.
How to get the User details for Authorization?
Ok.
You've already added the required uri. Good.
Now you need to add some configuration:
#Configuration
#EnableWebSecurity
public class OAuth2ResourceServerSecurityConfiguration {
#Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
String jwkSetUri;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(HttpMethod.GET,
///// more your requestMatchers /////
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
}
}
Now you should be able to receive jwt claims in your controllers with #AuthenticationPrincipal annotation.
#RestController
public class YourController {
#GetMapping("/")
public String doAnything(#AuthenticationPrincipal Jwt jwt) {
return jwt.getSubject();
}
}
Please add more info and I'll try to explain it better :-)
==== UPD ====
Really useful official manual on this.
Official code samples
I am trying to familiarize myself with Spring Security, in particular migrating from Spring Security OAuth to Soring Security (as in the following example/guide https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide).
However, I am seeming to only get 403 Forbidden errors. I am accessing from Postman and am using my company's existing OAuth server. I am able to get a token from the auth server, so I know I have those credentials correct and I have verified what roles the OAuth user has.
I am using the following dependencies:
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
This is the simple endpoint I am attempting to access:
#RestController
public class AppController
{
#GetMapping("/hello")
public String hello()
{
return "hello";
}
}
This is my application.yml file:
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: <<company-website-here>>/uaa/oauth/token_keys
And this is my security configuration class:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/hello").hasRole("MY_ROLE")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
I can't seem to figure out why I seem to only get 403 errors. I have also tried adding #EnableWebSecurity to the security config class, but that didn't make a difference. Adding the auth server URL explicitly to the server and/or manually creating a JwtDecoder didn't do the trick either; it appears the url is being automatically picked up from the yml file, based on its property name.
I am trying to move away from using the org.springframework.security.oauth.boot dependency and ResourceServerConfigurerAdapter.
I had to add my own converter like so:
private static class JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken>
{
private final Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter;
public JwtAuthenticationConverter()
{
this.jwtGrantedAuthoritiesConverter = jwt -> jwt
.getClaimAsStringList("authorities")
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
#Override
public final AbstractAuthenticationToken convert(#NonNull Jwt jwt)
{
Collection<GrantedAuthority> authorities = jwtGrantedAuthoritiesConverter.convert(jwt);
return new JwtAuthenticationToken(jwt, authorities, jwt.getClaimAsString("client_id"));
}
}
Then had to add this to the main security config:
.jwtAuthenticationConverter(new JwtAuthenticationConverter());
There may be a couple of things happening.
As you're migrating to Spring Security 5, you may need to extract your authorities manually. Check this post and it's correct answer.
You are using hasRole function and this will append "ROLE_" before your authority/role. So if the role on your JWT token is not ROLE_JWT_ROLE you should use
hasTransaction.
I'm trying to implement login functionality using angular and spring boot.
Im following spring tutorial https://spring.io/guides/tutorials/spring-security-and-angular-js/
But in my case my angular project is hosted on localhost:4200 and spring on localhost:8080
Now im sending a '/user' request to spring server. My angular code look like:
const headers = new HttpHeaders(credentials ? {
authorization: 'Basic ' + btoa(credentials.username + ':' + credentials.password)
} : {});
this.http.get('http://localhost:8080'+'/user', { headers: headers }).subscribe(response => {
if (response['name']) {
this.authenticated = true;
} else {
this.authenticated = false;
}
return callback && callback();
});
Now because of CORS it sends OPTIONS request which is successful with status 200.
After this it is not sending actual GET request which should send credentials as Angular $http is sending OPTIONS instead of PUT/POST
My spring code looks like this:
#CrossOrigin(origins = "*", maxAge = 3600, allowedHeaders={"x-auth-token", "x-requested-with", "x-xsrf-token"})
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
#Configuration
#Order(SecurityProperties.DEFAULT_FILTER_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().cors().and().authorizeRequests().antMatchers("/index.html", "/", "/home", "/login").permitAll()
.anyRequest().authenticated().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
Also i have added Spring Security in pom file.
Can you please help to find out why GET request is not triggered after OPTIONS request.
Root Cause:
In your spring security, you allowed only three types of request headers, i.e. allowedHeaders={"x-auth-token", "x-requested-with", "x-xsrf-token"}
But, in your http.GET request you are doing the basic authentication using header name as authorization which is rejected by Spring Security. So, the spring responses the allowed headers to your browser (i.e. preflight request with OPTIONS method) and your browser sees that.. Ooops.. authorization header is not allowed so I can't proceed with original GET call.
Solution:
Allow authorization header also in your spring security. It should work.!!
We are trying to implement a SSO scheme using Azure AD and Spring security.
We found a few leads:
https://github.com/spring-guides/tut-spring-boot-oauth2
https://github.com/Pytry/azure-b2c-oauth2
But none of these tell the full story.
In fact we can't get passed the access token parsing, Spring has a different idea of what the JWT token should be.
Ideally we wouldn't want to write an SSO filter from scratch but override the Token Services to implement custom filtering for starters.
Has anyone successfully implemented this?
Any help would be appreciated.
Update: I found an easier method. Just add a resource parameter after the userAuthorizationUri.
security:
oauth2:
client:
...
userAuthorizationUri: https://login.microsoftonline.com/<<tenantId>>/oauth2/authorize?resource=https://graph.windows.net
...
https://stackoverflow.com/a/45828135/2231168
Original answer
At my office we found a foreign blog post which lead us to the final implementation http://statemachine.hatenablog.com/entry/2016/04/19/155920
As a workaround, you had to add two classes to capture OAuth2RestTemplate and request enhancers. It works with spring boot 1.3.8 which contains spring 4.2.8, we couldn't make it work with higher version.
application.yml:
azure:
resource: https://graph.windows.net
security:
oauth2:
client:
clientId: <<your client id>>
clientSecret: <<your client secret>>
accessTokenUri: https://login.microsoftonline.com/<<tenantId>>/oauth2/token
userAuthorizationUri: https://login.microsoftonline.com/<<tenantId>>/oauth2/authorize
clientAuthenticationScheme: form
scope: openid
resource:
userInfoUri: https://graph.windows.net/me?api-version=1.6
AzureRequestEnhancer:
#Component
public class AzureRequestEnhancer implements RequestEnhancer {
#Value("${azure.resource:null}")
private String aadResource;
#Override
public void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form, HttpHeaders headers) {
if (!StringUtils.isEmpty(resource)) {
form.set("resource", aadResource);
}
}
}
AzureRequestEnhancerCustomizer:
#Component
public class AzureRequestEnhancerCustomizer {
#Autowired
private OAuth2RestTemplate userInfoRestTemplate;
#Autowired
private AzureRequestEnhancer azureRequestEnhancer;
#PostConstruct
public void testWiring() {
AuthorizationCodeAccessTokenProvider authorizationCodeAccessTokenProvider = new AuthorizationCodeAccessTokenProvider();
authorizationCodeAccessTokenProvider.setTokenRequestEnhancer(azureRequestEnhancer);
userInfoRestTemplate.setAccessTokenProvider(authorizationCodeAccessTokenProvider);
}
}
The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.
I hope this helps you with the implementation.
I'm trying to use Spring Boot to create an OAuth2 authorization that only supports the client credentials flow. As I understand that flow, the client accesses the /oauth/token endpoint directly.
Is there a way to disable the /oauth/authorize endpoint in Spring Boot and allow direct access to /oauth/token without having to be fully authorized first?
#Configuration
#EnableAuthorizationServer
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// TODO: Is there something I can do here to disable /oauth/authorize?
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// client details configuration
}
}
I can't speak to disabling the authorize endpoint but you're right that you can go directly to the token endpoint with the client credentials flow. I'm probably restating something you already know but the credentials for a "client" (client_id/client_secret) are different from the credentials of a "user" (username/password). A "user" goes to the authorize endpoint so that the client can then get tokens from the token endpoint. A "client" (in the client credentials flow) provides the client credentials to the token endpoint directly. Do you need to disable the authorize endpoint?
So, for client_credentials flow, you don't need to go to authorize first (you don't need to disable it). Here's how you'd curl your token if your Spring Boot authorization server was on localhost:8080:
curl -H "Authorization: Basic d2VhcHA6" -X POST http://localhost:8080/oauth/token?grant_type=client_credentials
where d2VhcHA6 is the base64 encoding of your "client_id:client_secret"