I am currently working on building an Angular2-Application accessing a Spring Boot backend server for authentication and editing database data via REST-APIs. My current basic configuration to authenticate against our Active Directory looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailsService());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(AD_DOMAIN, "ldap://" + AD_URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
This works so that I can access the basic Spring Boot Actuator-APIs after logging in using the default Boot Login Form.
The next step would be intertwining the activeDirectoryLdapAuthenticationProvider with a token-based authentication solution that can be accessed by Angular2 - for that, i found an example repository on gitHub. It implements HMAC-based authentication using JWT tokens with a custom service structure.
My issue now comes in understanding how the components of Spring Security work together in the background. As i understand the example, it implements a customised version of UserDetailsService and its environment using the UserDTO and LoginDTO. These access a MockUser-Service for example user data - which is what I want to avoid, instead accessing our Active Directory to authenticate users.
I am still digging through the repository trying to understand every customised class implementation and if I can customise the AuthenticationService to fit my needs, but I feel relatively lost.
What I am looking for are experiences in combining these three components, or with Spring Security and how exactly the default UserDetailsService and AuthenticationProviders interact, so I can try and fit the ActiveDirectory-logic into the token solution.
Alternatively, is there a way to combine the default-LDAP/AD-Configuration with a default JWT-Solution such as this?
Related
I am trying to implement LDAP authentication in a Sprint Boot application. In the test environment I have installed an Active Directory LDP service with which to authenticate. I have created a user within the AD instance, enabled the account and set a password. I am then trying to authenticate using this account from the Spring login form.
When I try to log in using AD I get an error message:
Your login attempt was not successful, try again.
Reason: Bad credentials
As I am new to both AD and Spring it is quite possible I have mis-configured either (or both!).
Do you have any suggestions as to how I can further diagnose this problem or is there anything obvious I may have missed?
My Spring Boot code (I have tried a number of different variations on this code, this is one example):
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider =
new ActiveDirectoryLdapAuthenticationProvider("foo.bar", "ldap://servername:389");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
It turns out that there was nothing wrong with my Java implementation. The issue appears to be with the AD LDP configuration. I tried connecting to another, known good instance of AD LDP and authentication worked first time.
I am going to mark this as the answer as I am no longer interested in a solution to this question and wish to close it down...
I just apply Spring Boot and Spring Cloud to build a microservice system. And I also apply Spring Oauth to it. Honestly, everything is perfect. Spring does a great job in it.
In this system, I have a microservice project does the job of an OAuth server, using JDBC datasource, and I using Permission based for UserDetails authorities (1 User has several Permissions). There are several microservice project does the jobs of Resource server (expose Rest api using Jersey), access security is based on Permissions of Authentication of OAuth bearer token.
Resource Server OAuth config class is something like this
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/restservice/object/list")
.hasAuthority("PERMISSION_VIEW_OBJECT_LIST");
// ...
}
#Override
public void configure(ResourceServerSecurityConfigurer resources)
throws Exception {
resources.resourceId("abc-resource-id")
.tokenStore(new JdbcTokenStore(dataSource()));
}
#Bean
#ConfigurationProperties(prefix = "oauth2.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
Everything is great! But I encounter 2 problems:
If I add a new microservice project as a new resourceId, and I append resourceId value to RESOURCE_IDS in table OAUTH_CLIENT_DETAILS of the OAuth client, all requests to Rest API of new resource service return error something like this
{"error":"access_denied","error_description":"Invalid token does not contain resource id (xyz-resource-id)"}
This happens even when user logout and re-login to obtain new access token. It only works if I go to delete records of the Access token and Refresh token int table OAUTH_ACCESS_TOKEN and OAUTH_REFRESH_TOKEN in database.
If at runtime, Permission of a User is changed, the authorities of authentication is not reloaded, I see that AUTHENTICATION value of the Access Token in table OAUTH_ACCESS_TOKEN still contains old Authorities before Permission is changed. In this case, User must logout and re-login to obtain new Access Token with changed authorities.
So, are there any ways to fix these 2 problems.
I'm using Spring Cloud Brixton.SR4 and Spring Boot 1.3.5.RELEASE.
If you are using the default Spring JdbcTokenStore, then the users authentication is serialised and stored with the access/refresh token when the user authenticates and retrieves their token for the first time.
Each time the token is used to authenticate, it is this stored authentication that is loaded which is why changes to the user permissions or the addition of extra resources is not reflected in the users permissions.
In order to add in some checking on this, you can extend DefaultTokenServices and override the loadAuthentication(String accessTokenValue) method to perform your own checks once the users authentication is loaded from the token store.
This may not be the ideal way of doing this, but it is the only way we've found of doing it so far.
To override DefaultTokenServices, add the follwoing bean method to you AuthorizationServerConfigurerAdapter config class:
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Bean
public AuthorizationServerTokenServices authorizationServerTokenServices() throws Exception {
// Where YourTokenServices extends DefaultTokenServices
YourTokenServices tokenServices = new YourTokenServices();
tokenServices.setTokenStore(tokenStore);
tokenServices.setClientDetailsService(clientDetailsService);
return tokenServices;
}
}
I resolved reload problem this way.
#Bean
public ClientDetailsService jdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
SUMMARY: In this Spring Security program I can login with (sam/abc125) and (neal/$ENCRYPTED_abc125). Shouldn't the (sam/abc125) login fail?
DETAILS:
In the process of learning something new I copied a tutorial Spring Security project from Websystique Spring Security 4 Hibernate Role Based Login Example . Works fine. In particular there are login/password combos of (sam/abc125) and (kenny/abc127). These logins work fine and the web page directs you to the proper place.
I wanted to add Bcrypt password encryption (from Websystique Spring Security 4 Hibernate Password Encoder Bcrypt Example) to this example -- adding it to the role based example seems preferable to adding role based stuff to the encrypted project version. So I carefully copied the code from the encryption-enabled project to my role-based project.
I made slight adjustments to some JSP pages -- missing some DIVs needed for formatting. I then added one new user record (neal/$ENCRYPTED_abc125). The $ENCRYPTED_abc125 is "abc125" properly encrypted with BCryptPasswordEncoder. I checked the "quick encoder" program results with what the web page provided.
The web site is accepting the (sam/abc125) login, even though the password "abc125" is stored in the clear in the user table. It is also accepting the (neal/$ENCRYPTED_abc125) login, even though the password $ENCRYPTED_abc125 is stored encrypted in the user table. It also accepts (kenny/abc127).
Since all of the authentication is hidden within Spring Security I can't debug my way through it. I'm left with expert knowledge for my question, which is:
Is there something within Spring Security that detects that the user table has an unencrypted password and encrypts it before comparing it with the user-supplied password text?
Note that the clear-text passwords predate the conversion to an encrypted storage and wouldn't be there for a live version. My question here is in trying to figure if something is broken or if it is working as designed.
Here is the security configuration from the project.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
#Autowired
CustomSuccessHandler customSuccessHandler;
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public PasswordEncoder psswordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//.antMatchers("/", "/home").permitAll()
.antMatchers("/", "/home").access("hasRole('USER')")
.antMatchers("/admin/**", "/newuser").access("hasRole('ADMIN')")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
//.and().formLogin().loginPage("/login")
.and().formLogin().loginPage("/login").successHandler(customSuccessHandler)
.usernameParameter("ssoId").passwordParameter("password")
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/Access_Denied");
}
}
My problem is that I would like to have two Authentication providers
BEFORE:
I have my UserDetailServiceImpl and I validated the users against DB(not sure what provider is it) The user got role according to the data in DB
NOW:
I have used ActiveDirectoryLdapAuthentication provider as follows
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailService);
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
MyActiveDirectoryLdapAuthenticationProvider provider = new MyActiveDirectoryLdapAuthenticationProvider(domain, url, rootDN);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
I made it work, so I'm able to authenticate.
THE PROBLEM:
I'm unable now to login with the database users anymore, only LDAP now.
The UserDetailsService is not used, so which role the user has?
Is there way how to add some default role to LDAP authenticated user?
So how to enable both providers?
How to add roles to user, authenticated through LDAP auth.provider?
I would also really appreciate "big picture" description of what is going on here(under the hood). What is the role of each of the classes used here, it is really unclear how does it work(AuthenticationManager, AuthenticationManagerBuilder, AuthenticationProvider etc.) Maybe it is just hidden by autoconfiguration and so, but even looking to Spring Security directly does not make me any wiser.
Thanks for any hints
UPDATE
This code seems to be working properly
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailService);
}
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
MyActiveDirectoryLdapAuthenticationProvider provider = new MyActiveDirectoryLdapAuthenticationProvider(domain, url, rootDN);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setAuthoritiesMapper(new SimpleAuthorityMapper());
return provider;
}
Too many questions!
Both providers are enabled since you add them both to the AuthenticationManagerBuilder. But you add them both to the same filter chain and both accept the same kind of Authentication as inputs, so one of them always masks the other.
The standard LdapAuthenticationProvider has an authoritiesMapper that you can use to map authorities from the directory entries to your users (normally it is done out of the box using directory groups, e.g. see sample). I suppose if your directory doesn't contain groups you could make all users have the same authorities or something with a custom mapper.
Your #Beans of type AuthenticationManager and AuthenticationProvider look suspiciously redundant (and possibly harmful since they are global, and you are configuring the AuthenticationManagerBuilder for a single filter chain). I doubt you need that AuthenticationManager method at all and the other one doesn't need to be public or a #Bean (probably).
I'm trying to setup a resource server to work with separate authorization server using spring security oauth. I'm using RemoteTokenServices which requires /check_token endpoint.
I could see that /oauth/check_token endpoint is enabled by default when #EnableAuthorizationServer is used. However the endpoint is not accessible by default.
Should the following entry be added manually to whitelist this endpoint?
http.authorizeRequests().antMatchers("/oauth/check_token").permitAll();
This will make this endpoint accessible to all, is this the desired behavior? Or am I missing something.
Thanks in advance,
You have to
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception
{
oauthServer.checkTokenAccess("permitAll()");
}
For more information on this ::
How to use RemoteTokenService?
Just to clarify a couple of points, and to add some more information to the answer provided by Pratik Shah (and by Alex in the related thread):
1- The configure method mentioned is overridden by creating a class that extends AuthorizationServerConfigurerAdapter:
#EnableAuthorizationServer
#Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("ger-client-id")
.secret("ger-secret")
.authorizedGrantTypes("password")
.scopes("read", "write");
}
}
2- I suggest reading this Spring guide explaining the automatic configuration carried out by Spring Boot when we include the #EnableAuthorizationServer annotation, including an AuthorizationServerConfigurer bean. If you create a configuration bean extending the AuthorizationServerConfigurerAdapter as I did above, then that whole automatic configuration is disabled.
3- If the automatic configuration suits you just well, and you JUST want to manipulate the access to the /oauth/check_token endpoint, you can still do so without creating an AuthorizationServerConfigurer bean (and therefore without having to configure everything programmatically).
You'll have to add the security.oauth2.authorization.check-token-access property to the application.properties file, for example:
security.oauth2.client.client-id=ger-client-id
security.oauth2.client.client-secret=ger-secret
security.oauth2.client.scope=read,write
security.oauth2.authorization.check-token-access=permitAll()
Of course, you can give it an isAuthenticated() value if you prefer.
You can set the log level to DEBUG to check that everything is being configured as expected:
16:16:42.763 [main] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression 'permitAll()', for Ant [pattern='/oauth/check_token']
There is no much documentation about these properties, but you can figure them out from this autoconfiguration class.
One last thing worth mentioning, even though it seems to be fixed in latest Spring versions, I just submitted an issue in the spring-security-oauth project; it seems that the token_check functionality is enabled by default if you add a trailing slash to the request:
$ curl localhost:8080/oauth/check_token/?token=fc9e4ad4-d6e8-4f57-b67e-c0285dcdeb58
{"scope":["read","write"],"active":true,"exp":1544940147,"authorities":["ROLE_USER"],"client_id":"ger-client-id"}
There are three POST parameters, namely client_id (user name), client_secret (password corresponding to the user name), token (token applied for), client_id, client_secret are different from the parameters in the /oauth/token interface
enter image description here
First, config token access expression:
#Override
public void configure(AuthorizationServerSecurityConfigurer securityConfigurer) throws Exception {
securityConfigurer
.allowFormAuthenticationForClients()
.checkTokenAccess("isAuthenticated()")
.addTokenEndpointAuthenticationFilter(checkTokenEndpointFilter());
}
Then, we need define a filter to process client authentication:
#Bean
public ClientCredentialsTokenEndpointFilter checkTokenEndpointFilter() {
ClientCredentialsTokenEndpointFilter filter = new ClientCredentialsTokenEndpointFilter("/oauth/check_token");
filter.setAuthenticationManager(authenticationManager);
filter.setAllowOnlyPost(true);
return filter;
}