I am making an API with Spring boot + Spring Security. I am having issues with Spring Security "letting" any request go through.
What is happening is that I have two endpoints:
users/register/app
users/recoverpasswordbyemail
And I am testing them with Postman, the thing is that if I call one of those endpoints for the first time after the app started without an Authoritation header or a wrong one, it won't let me in and gives a 401 error. However, If I call the other method or the same method again with a correct Authoritation header and then call one of them without the Header again, it'll let me pass. I do not think it's suppossed to work without an auth header.
I am not sure if it is because I have already done some kind of "log in" when I put the correct auth header or if it's a Postman's issue. But I want that every time a call to one of those endpoints a check is done for the user.
This is my Spring config file:
#Configuration #EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserServiceImpl userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/users/register/app", "/users/recoverpasswordbyemail")
.hasAnyRole("ADMIN", "SADMIN")
.and().httpBasic().authenticationEntryPoint(new NotAuthorizedExceptionMapper())
.and().sessionManagement().disable();
}
#Override
public void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userService);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Also every time this happens, the user doesn't get first if exists (however it does when I have a wrong auth header) or whatever as I have in my UsersService:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("Hi");
User user = userRepository.findById(username).get();
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), user.getRole());
}
This is the URL I use to call the endpoints:
http://localhost:8680/ulises/usersserver/users/register/app
where /ulises/usersserver/ is the servlet context
Has anyone a clue of why this can be? I researched quite a lot but saw nothing that could solve it.
Thank.
Okay, it turns out that a session was being created despite of sessions being disabled. I don't understand why this is happening but using
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
solved my problem.
Related
So I am working on my first full-stack application (spring boot rest API and Vue.js frontend) and I came across a problem by using sonarqube.
My sonarqube gives the following warning:
Make sure disabling Spring Security's CSRF protection is safe here.
and it is coming from this file:
#Configuration
#AllArgsConstructor
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class
WebSecurityConfig extends WebSecurityConfigurerAdapter {//provides security for endpoints
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
private final AccountService accountService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()/*.disable()*/.and()//So we can send post requests without being rejected(if we using form based indication we want to enable this)
.authorizeRequests()
.antMatchers("/login", "/authenticate","/register", "/register/**")
.permitAll()//any request that goes trough that end point we want to allow.
.anyRequest()
.authenticated().and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
http.cors();
http.logout().permitAll();
http.logout().logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider =
new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(jwtUserDetailsService);
return provider;
}
}
More specifically this piece of code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()/*.disable()*/.and()//So we can send post requests without being rejected(if we using form based indication we want to enable this)
.authorizeRequests()
.antMatchers("/login", "/authenticate","/register", "/register/**")
.permitAll()//any request that goes trough that end point we want to allow.
.anyRequest()
.authenticated().and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
http.cors();
http.logout().permitAll();
http.logout().logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)));
When I remove the first .and() and use disable (which is commented out now) my program works, but I want to find a solution where I can .csrf() let be enabled (I know it is standard enabled) and where my login stops giving me a 401 error.
Thanks in advance!
Apparently, you are using JWTs for authenticating requests. This typically does not involve cookies (tokens are usually sent as request headers). If this is the case for you (JWT is received in a header) you can disregard the Sonarqube warning, you don't need CSRF protection.
The reason for this is CSRF is an attack where the attacker exploits the existing session of a victim user, when the victim visits a malicious website. This is based on cookies being sent with a request by the browser only depend on the destination, and not the origin (ie. a javascript on attacker.com can make a request to victim.com, and a user's cookies for victim.com will be sent automatically). If request headers are used to transmit a token, that cannot be accessed by an attacker on their malicious domain.
If you still wanted to make it work (because for example your JWTs are indeed received from a cookie), you would have to send the correct CSRF token from your frontend (VueJS) with every request that's not a get, so it's a change for your frontend, not your backend.
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...
My company has Active Directory set up. I am making an application whose login system should be linked to that AD, so I set up Spring LDAP authentication to implement it.
But when I try to log in to the application, I get
Failed to locate directory entry for authenticated user: my.name
javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of:
'DC=dev,DC=company,DC=corp'
My Spring Security code looks like this:
#Configuration
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()).userDetailsService(userDetailsService());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("company.hr", "ldap://10.23.1.1:389/dc=dev,dc=company,dc=corp");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
One of the problems is that our AD structure is really branched out, and each of those organizational units has further subdivision:
Reading up on LDAP, I thought giving it only the path to the common parent folder should do recursive search through the tree and find the matching user, no matter what OU they are in, and indeed, if I do a dsquery from command prompt it does return the queried users, no matter where they are. However, that doesn't happen in Spring. I've read just about everything there is to find on the topic, and debugged for hours - but it seems my parameters are set correctly, yet I still get thrown the Exception.
My question would be - how to make Spring see that the user is, in fact, in Active Directory, without specifying the complete URL to that specific user (since there are dozens of different OUs)?
Found the solution. Sometimes, rereading the documentation for Xth time actually helps.
I changed the constructor call of the ActiveDirectoryLdapAuthenticationProvider from this
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("company.hr", "ldap://10.23.1.1:389/dc=dev,dc=company,dc=corp");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
to this
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("company.hr", "ldap://10.23.1.1:389", "dc=dev,dc=company,dc=corp");
The third parameter in the constrcutor is in fact "rootDn", so that's the place where it should be passed, not as an extension of the URL.
I am trying to add basic authentication for my APIs where the users will be authenticated based on their credential stored in MongoDB. I want to use java config instead of XML based config. So far what I have learnt is I have to create #Configuration by extending WebSecurityConfigurerAdapter and override configure method. In that I can add a custom filter by addFilterBefore().
But how can I get a Authentication header information in the filter, how to proceed if the user is authenticated. I have been googling a lot but didn't find any good example that will help a novice like me whose been into spring just for 1 week.
Does any one have a good tutorial or sample that can help me get started with this? Thanks in advance.
As example, you can use next solution.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(getBasicAuthenticationEntryPoint());
}
#Bean
public BasicAuthenticationEntryPoint getBasicAuthenticationEntryPoint(){
BasicAuthenticationEntryPoint basicAuthenticationEntryPoint = new BasicAuthenticationEntryPoint();
basicAuthenticationEntryPoint.setRealmName("Basic Authentication");
return basicAuthenticationEntryPoint;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
It works for me. But you need to implement UserDetailsService interface.
Spring automatically checks is user authenticated and tries to proceed authentication if not.
I'm looking for a way to implement a Maintenance Mode in my Spring app.
While the app is in Maintenance Mode only users role = MAINTENANCE should be allowed to log in. Everyone else gets redirected to login page.
Right now I just built a Filter:
#Component
public class MaintenanceFilter extends GenericFilterBean {
#Autowired SettingStore settings;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if(settingStore.get(MaintenanceMode.KEY).isEnabled()) {
HttpServletResponse res = (HttpServletResponse) response;
res.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
} else {
chain.doFilter(request, response);
}
}
}
And added it using:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// omitted other stuff
.addFilterAfter(maintenanceFilter, SwitchUserFilter.class);
}
Because as far as I figured out SwitchUserFilter should be the last filter in Spring Security's filter chain.
Now every request gets canceled with a 503 response. Though there's no way to access the login page.
If I add a redirect to the Filter, this will result in an infinite loop, because access to login page is also denied.
Additionally I can't figure out a nice way to get the current users roles. Or should I just go with SecurityContextHolder ?
I'm looking for a way to redirect every user to the login page (maybe with a query param ?maintenance=true) and every user with role = MAINTENANCE can use the application.
So the Filter / Interceptor should behave like:
if(maintenance.isEnabled()) {
if(currentUser.hasRole(MAINTENANCE)) {
// this filter does nothing
} else {
redirectTo(loginPage?maintenance=true);
}
}
I now found two similar solutions which work, but the place where I inject the code doesn't look that nice.
For both I add a custom RequestMatcher, which get's #Autowired and checks if Maintenance Mode is enabled or not.
Solution 1:
#Component
public class MaintenanceRequestMatcher implements RequestMatcher {
#Autowired SettingStore settingStore;
#Override
public boolean matches(HttpServletRequest request) {
return settingStore.get(MaintenanceMode.KEY).isEnabled()
}
}
And in my Security Config:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired MaintenanceRequestMatcher maintenanceRequestMatcher;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.requestMatchers(maintenanceRequestMatcher).hasAuthority("MY_ROLE")
.anyRequest().authenticated()
// ...
}
Solution 2:
Very similar, but uses HttpServletRequest.isUserInRole(...):
#Component
public class MaintenanceRequestMatcher implements RequestMatcher {
#Autowired SettingStore settingStore;
#Override
public boolean matches(HttpServletRequest request) {
return settingStore.get(MaintenanceMode.KEY).isEnabled() && !request.isUserInRole("MY_ROLE");
}
}
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired MaintenanceRequestMatcher maintenanceRequestMatcher;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.requestMatchers(maintenanceRequestMatcher).denyAll()
.anyRequest().authenticated()
// ...
}
This will perform a denyAll() if Maintenance Mode is enabled and the current user does not have MY_ROLE.
The only disadvantage is, that I cannot set a custom response. I'd prefer to return a 503 Service Unavailable. Maybe someone can figure out how to do this.
It's kinda of a chicken or egg dilemma, you want to show unauthorized users a "we're in maintenance mode ..." message, while allow authorized users to login, but you don't know if they are authorized until they log in. Ideally it would be nice to have this in some sort of filter, but I found in practice it was easier for me to solve a similar issue by putting the logic after login, like in the UserDetailsService.
Here's how I solved it on a project. When I'm in maintenance mode, I set a flag for the view to show the "we're in maintenance mode .." message, in a global header or on the login page. So users, regardless of who they are know it's maintenance mode. Login should work as normal.
After user is authenticated, and in my custom UserDetailsService, where their user details are loaded with their roles, I do the following:
// if we're in maintenance mode and does not have correct role
if(maintenance.isEnabled() && !loadedUser.hasRole(MAINTENANCE)) {
throw new UnauthorizedException(..)
}
// else continue as normal
It's not pretty, but it was simple to understand (which I think is good for security configuration stuff) and it works.
Update:
With you solution I'd have to destroy everyone's session, else a user
which was logged in before maintenance mode was enabled, is still able
to work with the system
On our project we don't allow any users to be logged in while in maintenance mode. An admin, kicks off a task which enables "maintenance..." msg, with a count down, then at the end, we expire everyone's session using SessionRegistry.
I was a similar situation and found this answer is helpful. I followed the second approach and also managed to return custom response.
Here is what I have done to return the custom response.
1- Define a controller method that returns the needed custom response.
#RestController
public class CustomAccessDeniedController {
#GetMapping("/access-denied")
public String getAccessDeniedResponse() {
return "my-access-denied-page";
}
}
2- In your security context, you should permit this URL to be accessible.
http.authorizeRequests().antMatchers("/access-denied").permitAll()
3- Create a custom access denied exception handler
#Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
#Autowired
private SettingStore settingStore;
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
if(settingStore.get(MaintenanceMode.KEY).isEnabled()) {
response.sendRedirect(request.getContextPath() + "/access-denied");
}
}
}
4- Register the custom access denier exception handler in the security config
#Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);