Is There an Easier Way to Load Spring OAuth Client Configuration - spring-security

I'm working on a little proof of concept for a set of endpoints that need to be able to call each other passing tokens which are obtained via an OAuth 2 client credentials flow. I'm using Spring Boot and related projects to build these endpoints, and I'm confused as to why the framework appears to be very opinionated about the following code:
package com.example.client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#Configuration
#EnableAutoConfiguration
#EnableOAuth2Client
#RestController
public class StuffClient {
#Value("${security.oauth2.client.access-token-uri}")
private String tokenUrl;
#Value("${security.oauth2.client.id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Value("${security.oauth2.client.grant-type}")
private String grantType;
#Autowired
private OAuth2RestOperations restTemplate;
private String uri = "http://localhost:8082/stuff/";
#RequestMapping(value = "/client/{stuffName}", method = RequestMethod.GET)
public String client(#PathVariable("stuffName") String stuffName) {
String request = uri + stuffName;
return restTemplate.getForObject(request, String.class);
}
#Bean
public OAuth2RestOperations restTemplate(OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(resource(), clientContext);
}
#Bean
protected OAuth2ProtectedResourceDetails resource() {
ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails();
resource.setAccessTokenUri(tokenUrl);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setGrantType(grantType);
return resource;
}
}
And the accompanying configuration file:
server:
port: 8081
security:
basic:
enabled: false
oauth2:
client:
id: test-client
client-secret: test-secret
access-token-uri: http://localhost:8080/uaa/oauth/token
grant-type: client_credentials
The above works exactly as expected. If I change security.oauth2.client.id to security.oauth2.client.client-id (in both the Java code and the YAML), I get a 500 error, the first line of which is:
org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException: Unable to obtain a new access token for resource 'null'. The provider manager is not configured to support it.
The code also works fine if I hard code values for all of the instance variables. It seems to work fine, in fact, in every permutation of populating those instance variables except the one where I use #Value to populate clientId with the value of security.oauth2.client.client-id
So my main question is: is the framework actually opinionated in this very specific way? And if so, why? And, can I leverage this opinionated-ness to simplify my code?

I am not sure which spring-boot version you are using. I am using spring-boot version 1.5.4.RELEASED and to simplify your codes,
you can inject OAuth2ProtectedResourceDetails like
#Autowired
private OAuth2ProtectedResourceDetails resource;
and create OAuth2RestTemplate as
#Bean
#Primary
public OAuth2RestOperations restTemplate(OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(resource, clientContext);
}
sample yaml ..
### OAuth2 settings ###
security:
user:
password: none
oauth2:
client:
accessTokenUri: ${auth-server}/oauth/token
userAuthorizationUri: ${auth-server}/oauth/authorize
clientId: myclient
clientSecret: secret
resource:
user-info-uri: ${auth-server}/sso/user
jwt:
keyValue: |
-----BEGIN PUBLIC KEY-----
your public key
-----END PUBLIC KEY-----
And then,use restTemplate instance in controllers as
#Autowired
private OAuth2RestOperations restTemplate;
I hope some helps for you.

Related

Simple Spring Boot LDAP authentication example does not work with ActiveDirectory

I found a very simple example for LDAP authentication, which works just fine using an embedded LDAP server: https://github.com/asbnotebook/spring-boot/tree/master/spring-security-embedded-ldap-example . It is exactly what I need - one config class added and now all users are required to log in before accessing the application.
Since our AD (local server, not the Azure AD) requires userDN and password for access, I added this to the example code, also modified url, base dn etc.
When I attempt to log in, I always get the "Bad credentials" error message. I then stepped through the code and found that the Spring LDAP code successfully retrieves some user data from AD (I found the user email address in the "userDetails" object which is known only in the AD), however the "password" field is set to null. This null value is then compared to the password entered by the user which fails and a BadCredentialsException is thrown in function org.springframework.security.authentication.dao.additionalAuthenticationChecks().
So now I have two questions:
why is the "password" attribute set to null? My understanding is that it should contain the password hash. I checked the AD response with ldapsearch but I don't see anything looking like a password hash. However the userDN does work with other applications so it is probably not a problem with the userDN AD account. Please advise how to properly retrieve the password information.
I believe that the example does not handle password hashes. The LDIF file to preload the embedded LDAP server of the example application simply contains clear text passwords for the userPassword attribute. Also the passwordEncoder in the example code looks like a No Op Encoder. How should I change this to make it work with the AD?
Here is my code:
package com.asbnotebook.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.userdetails.LdapUserDetailsManager;
#Configuration
public class LdapSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public UserDetailsService userDetailsService() {
var cs = new DefaultSpringSecurityContextSource("ldaps://ad.company.local/dc=company,dc=local");
cs.setUserDn("cn=robot1,ou=robots");
cs.setPassword("secret");
cs.afterPropertiesSet();
var manager = new LdapUserDetailsManager(cs);
manager.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=company_user", "cn"));
manager.setGroupSearchBase("ou=company_groups");
return manager;
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
After considering Gabriel Luci's comment, I have now found a simple way to authenticate with our ActiveDirectory:
package com.asbnotebook.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
#Configuration
public class LdapSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
ActiveDirectoryLdapAuthenticationProvider adProvider =
new ActiveDirectoryLdapAuthenticationProvider(
"company.de","ldaps://ad.company.local","dc=company,dc=local");
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
auth.authenticationProvider(adProvider);
auth.eraseCredentials(false);
}
}
Login is possible using either the email address or sAMAccountName.

Spring Boot as a resource server (JWK validation) & Angular/Cordova as front end - OAuth2 for social Login (Facebook & Google) support

I am stuck with the implementation of spring boot as a resource server for multiple authorization servers for validating the access/id tokens provided by authorization servers (such as google, facebook via front end libraries). Here is the architecture I am looking for the below desired flow as a working model.
Desired Architecture Image - Click here
what I implemented so far: I used a library angularx-social-login on angular 8 to get the required tokens google. I was able to pass the token back to the backend resource server to validate the token with google and authorize. Below is the code snippets.
Property File:
google:
client-id: xyz
iss: accounts.google.com
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://accounts.google.com
jwk-set-uri: https://accounts.google.com/.well-known/openid-configuration
Security Config Snippet
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
private String jwkSetUri;
#Value("${google.client-id}")
private String clientId;
#Value("${google.iss}")
private String iss;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated().and()
.oauth2ResourceServer()
.jwt().decoder(jwtDecoder());
}
#Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(clientId);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(iss);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>
(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
The above snippet works for one authorization server (google in this case) but
below are my issues
How do Intercept the code to validate if the user exists in our DB first?
How do I add support for another authorization server like facebook?

Spring data rest shows id as path variable in swagger for POST method in mongo repository?

I am just using MongoRepository. Below is my entity class:
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = "product")
#Data
#ToString
#EqualsAndHashCode
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Product {
#Id private String id;
#Indexed(unique = true)
private String name;
private boolean displayAds;
}
When I view the resource in Swagger the POST method appears as follows:
This is my repository:
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ProductRepository extends MongoRepository<Product, String> {
}
Why does this happen? Is there a fix? I'm using springfox-swagger2 and springfox-swagger-ui.
Anyway it can post via Postman without adding such path variable.
Updating to 2.8.0 should resolve the issue.
This seems to have been a bug in Springfox version 2.7.0. Should be fixed in 2.8.0 as highlighted here.

is it possible to use Spring MVC Test directly in grails, and how?

We think the Spring Rest Doc is great for documenting rest api. But it is based on Spring MVC Test,and we can't figure out how to use Spring MVC Test in my grails apps(Grails 3.0.5).
I tried to use a config class (with #Configuration and#ComponentScan) to scan grails components into the test context, but it seems like nothing had been loaded (when performing a http request to the mockmvc, it got 404).
I also tried to configure the grails controller directly, and got a run time error.
Could not autowire field: private reactor.bus.EventBus
I also tried to add #Integration (from grails) on the test class but recieved the same error.
Please help.
Here are some code samples.
what i tried was adding config class or class locations or grails controller to ContextConfiguration of the test class below. And the test class itself is basically following spring rest doc reference.
import org.junit.Before;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.restdocs.RestDocumentation;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration
//TODO how to scan Grails components into the test context
public class QuestionRestSpec {
#Rule
public final RestDocumentation restDocumentation = new RestDocumentation("build/generated-snippets");
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
}
}
The config class(which has no use):
#Configuration
#EnableWebMvc
#ComponentScan
public class AskConfig {
}
Unfortunately it doesn't appear to be possible to use MockMvc with Grails. Calls to MockMvc.perform fail:
HandlerMapping requires a Grails web request. Stacktrace follows:
java.lang.IllegalArgumentException: HandlerMapping requires a Grails web request
at org.springframework.util.Assert.notNull(Assert.java:112) ~[spring-core-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.grails.web.mapping.mvc.UrlMappingsHandlerMapping.getHandlerInternal(UrlMappingsHandlerMapping.groovy:113) ~[grails-web-url-mappings-3.0.9.jar:3.0.9]
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:299) ~[spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1120) [spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.test.web.servlet.TestDispatcherServlet.getHandler(TestDispatcherServlet.java:90) [spring-test-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:932) [spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) [spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) [spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858) [spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) [tomcat-embed-core-8.0.26.jar:8.0.26]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) [spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65) [spring-test-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) [tomcat-embed-core-8.0.26.jar:8.0.26]
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167) [spring-test-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134) [spring-test-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:144) [spring-test-4.1.7.RELEASE.jar:4.1.7.RELEASE]
…
Spring REST Docs 1.1 will hopefully add support for REST Assured. This removes the need for MockMvc and, instead, will allow the API to be documented by making HTTP calls to a running server in a functional test.
For reference, here's the Spock specification that I used:
package com.example.notes
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import grails.test.mixin.integration.Integration
import grails.transaction.*
import org.junit.Rule;
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.restdocs.RestDocumentation;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext
import spock.lang.*
#Integration
#Rollback
class ApiDocumentationSpec extends Specification {
#Rule
public final RestDocumentation restDocumentation = new RestDocumentation("build/generated-snippets");
#Autowired
WebApplicationContext context
MockMvc mockMvc
def setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)).build();
}
def cleanup() {
}
void "list notes"() {
when:
MvcResult result = this.mockMvc.perform(get("/notes")).andReturn()
then:
result.andExpect(status().isOk())
.andDo(document("notes-list-example"));
}
}

Configuring Spring Boot Security to use BCrypt password encoding in Grails 3.0

In Grails 3.0, how do you specify that Spring Boot Security should use BCrypt for password encoding?
The following lines should provide a sense of what I think needs to be done (but I'm mostly just guessing):
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
PasswordEncoder passwordEncoder
passwordEncoder(BCryptPasswordEncoder)
My application loads spring-boot-starter-security as a dependency:
build.gradle
dependencies {
...
compile "org.springframework.boot:spring-boot-starter-security"
And I have a service wired up for userDetailsService using:
conf/spring/resources.groovy
import com.example.GormUserDetailsService
import com.example.SecurityConfig
beans = {
webSecurityConfiguration(SecurityConfig)
userDetailsService(GormUserDetailsService)
}
I have the following code in grails-app/conf/spring/resources.groovy
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
beans = {
bcryptEncoder(BCryptPasswordEncoder)
}
and I have a java file which does the configuration as described by spring-security. It should be possible to do it in groovy too, but I did it in java.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
BCryptPasswordEncoder bcryptEncoder;
#Autowired
UserDetailsService myDetailsService
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// userDetailsService should be changed to your user details service
// password encoder being the bean defined in grails-app/conf/spring/resources.groovy
auth.userDetailsService(myDetailsService)
.passwordEncoder(bcryptEncoder);
}
}

Resources