I'm using Spring security and database login in my application (in the future I'll have to implement LDAP authentication).
Through web all work right, but now when I call web services from external (I have some web service for internal javascript and some for external calls) I receive the HTML code of login page. It's correct, but now how can I make REST call?
I have to protect them, I thought to use a token or username and password for each web services call, but how can I set username and password in REST call?
For example with postman. Then I will set the credentials also in
restTemplate.setRequestFactory(requestFactory);
responseEntity = restTemplate.getForEntity(serverIp + "ATS/client/file/?filePath={filePath}", byte[].class, filePath);
and in
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
ContentBody cbFile = new FileBody(file);
ContentBody cbPath= new StringBody(toStorePath,ContentType.TEXT_PLAIN);
builder.addPart("file", cbFile);
builder.addPart("toStorePath",cbPath);
httppost.setEntity(builder.build());
CloseableHttpResponse httpResponse = httpClient.execute(httppost);
HttpEntity resEntity = httpResponse.getEntity();
On the web I have even the roles for the user, maybe I'll have to use them also for the web services.
Thanks for the advices. Regards
UPDATE:
As #Gergely Bacso advices me, I have updated my code, but now I have the opposite problems: When I call web services they return all the information without username and password.
This is security config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/client/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests() //Authorize Request Configuration
//.antMatchers("/", "/register").permitAll()
// .antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login").permitAll();
}
}
}
There was a similar question asked only a few days ago:
Securing REST service with Spring Security
The important part is that:
In case you want to secure something that is accessed programatically (for example a REST service being called by another program) then you should not use form-based authentication.
What you need is something much more suitable for the job. Like an HTTP-basic auth. Form-based login methods are more suited to use cases where users can enter their username/password.
Related
I have a simple Spring boot application that has a POST rest api method to register users. This works perfectly when I test it through Postman. But when I test it from my Mobile application, this always throws a 403. This fails in the Options level as I don't see my backend logging an attempted request.
The usual solution given everywhere for this problem is to disable csrf in the spring security configuration. The funny thing is, I have this disabled and still getting a 403. I have searched as much as I could but cannot find a solution as to how this is still failing. Appreciate any help anyone could provide.
Here is the security configuration.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// Some beans that are not relevant
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/register").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
I think it's also worth mentioning that I also tried adding cors disable option as advised in many other threads, to no avail.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/register").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
This is how my controller look like
#RestController
public class AuthenticationResource {
// Wiring and other methods
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> registerNewUser(#Valid #RequestBody UserRegistrationRequest request) {
if (!request.getConfirmPassword().equals(request.getPassword())) {
throw new MyException(ErrorUtil.generateErrorFieldsValue("password", "confirmPassword"),
"Passwords do not match!", HttpStatus.BAD_REQUEST);
}
UserAccountResponse savedUserAccount = userAccountService.save(request);
return ResponseEntity.ok(savedUserAccount);
}
}
Let me know if any other details are required.
From what I know this is related to CORS as you suggested, but by using the http.cors() you tell spring to look for a bean named corsFilter(). In my other projects this is how I solved it.
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource configSource = new
UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// GET , POST, HEAD
config.applyPermitDefaultValues();
config.addAllowedMethod(HttpMethod.PUT);
config.addAllowedMethod(HttpMethod.DELETE);
config.addAllowedMethod(HttpMethod.OPTIONS);
configSource.registerCorsConfiguration("/**", config);
return new CorsFilter(configSource);
}
So I have this project, which really all I want to do is be able to have a user log in and get access to a specific page:
Security
#Configuration
#EnableWebSecurity
public class MainSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource
private UserDetailsServiceImpl userDetailsService;
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable();
http
.authorizeRequests()
.antMatchers("/", "/**", "/login/**", "/index.html", "/login.html", "/components/**", "/css/**", "/js/**", "/fonts/**", "/images/**", "/.sass-cache/**", "/services.html").permitAll()
.anyRequest().authenticated();
http.formLogin()
.loginPage("/login")
.failureForwardUrl("/login.html")
.usernameParameter("user")
.passwordParameter("password");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
When I sent the /login request from angularjs as a POST I was hitting the UserDetailsServiceImpl which is good, but the username was coming in empty.
UserDetailsServiceImpl
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Resource
private HttpSession httpSession;
#Resource
private UserDao userDao;
#Override
public UserDetails loadUserByUsername(String user) throws UsernameNotFoundException {
User userByEmail = userDao.findUserByEmail(user);
UserDetailsImpl userDetails = new UserDetailsImpl(userByEmail, httpSession.getId());
return userDetails;
}
}
So I did some googling and it said that the /login request has to be GET, which in itself confused me, should we really be plonking the username and password into the url? Or am I thinking about this wrong. Anyway, here's the angularJS code:
$scope.loginUser = function () {
$scope.user.user = $scope.email;
$scope.user.password = $scope.password;
$http.get("/login", { params: {username: $scope.user.user, password: $scope.user.password}});
I no longer hit the breakpoints now within UserDetailsServiceImpl and rather I am getting a 404.
UPDATE
After updating the processing url, I now post it but the username that get's passed server-side is empty
$scope.loginUser = function () {
$scope.user.username = $scope.email;
$scope.user.password = $scope.password;
$http.post("/api/authentication", $scope.user);
Everything up to here is fine, it's just when java handles it
If you are using Angular you have not a loginPage because you are writing a SPA and page navigation is managed by Angular itself.
You should use loginProcessingUrl that defines only the login submission url
.and()
.formLogin()
.loginProcessingUrl("/api/authentication")
.usernameParameter("username")
.passwordParameter("password")
To submit you login you need to do a POST not a GET. Probably links mean url to access a login page not to submit.
In the example above you have to do a POST using url /api/authentication with a body containing username and password
Also if i've seen you already found the solution, i've published a project based on Spring Boot 2.0 and angular 6 (angularjs is quite outdated) with spring security and a stateful authentication (the same you were searching for)
https://github.com/ValerioMC/vforge-stateful-auth
It's just a starting point.
The issue was with my AngularJS $http.post request, I solved it by adding headers for 'application/x-www-form-urlendcoded' as it is not the default header:
$http({
method: 'POST',
url: '/api/authentication',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: 'username=' + $scope.email + '&password=' + $scope.password
});
This question already has answers here:
When to use Spring Security`s antMatcher()?
(2 answers)
Closed 5 years ago.
I am trying to get Spring Security's basic authentication to work side by side with JWT token authentication with no success. I have implemented basic authentication for my web console and JWT to secure a number of API endpoints. Here's my config:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MultiHttpSecurityConfig {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(bCryptPasswordEncoder());
}
#Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
*
* API Security configuration
*
*/
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter{
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/api/**","/refresh/**").authenticated()
.antMatchers("/auth/**").permitAll().anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
/**
*
* Form login security configuration
*
*/
#Configuration
public static class FormLoginWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ConsoleAuthenticationEntryPoint consoleAuthenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().exceptionHandling().authenticationEntryPoint(
consoleAuthenticationEntryPoint).and()
.authorizeRequests().antMatchers("/console/**").authenticated()
.antMatchers(HttpMethod.GET,
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().defaultSuccessUrl("/console/home")
.loginPage("/console/login")
.permitAll()
.and()
.logout()
.permitAll();
http.csrf().disable();
}
}
}
I have noticed that the configuration I annotate with Order(1) is the one that is picked by Spring Security and the other is completely ignored. Like in the above config, I get 401 error if I try to access /console/login.
Any help would be much appreciated.
The reason why is because neither ApiWebSecurityConfigurationAdapter nor FormLoginWebSecurityConfig uses the antMatcher(). This means that both security configurations will handle all paths, even though you're using antMatchers() afterwards. Due to this, the configuration with the lowest order (#Order(1)) will handle everything, while the other one will do nothing.
This is also mentioned in the docs:
The http.antMatcher states that this HttpSecurity will only be applicable to URLs that start with /api/
So, to fix this problem, you have to povide an antMatcher to one of your configurations (or both). For example, if the form login should only be applied to /console/login and /console/home, you could change the configuration to:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/console/**") // Add this
.httpBasic().and()
.exceptionHandling().authenticationEntryPoint(consoleAuthenticationEntryPoint).and()
.authorizeRequests().antMatchers("/console/**").authenticated()
.antMatchers(HttpMethod.GET,
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js").permitAll()
.anyRequest().authenticated().and()
.formLogin().defaultSuccessUrl("/console/home")
.loginPage("/console/login").permitAll().and()
.logout().permitAll().and() // Make sure to use .and() to add the .csrf()
.csrf().disable();
}
Another good read about this topic is this question: When to use Spring Security`s antMatcher()?
Please note that you shouldn't use the http builder twice like you did to add the .csrf().disable(), add it to the other builder like I did in the code above.
Also be aware that you'll likely have to change the order. You should put the order on the configuration with the most detailed antMatcher(), in this case FormLoginWebSecurityConfig.
I know almost nothing about LDAP and even less about spring security but I am trying to configure a spring boot app to authenticate against an ldap instance and am stuck.
I was given the ldap server name at adldap.company.com and base dn of dc=ad,dc=company,dc=com
I have some python code that does a simple bind and works.
LDAP_USERNAME = 'username#ad.company.com'
LDAP_PASSWORD = 'password'
base_dn = 'dc=ad,dc=company,dc=com' # not used for bind I guess, only search
try:
ldap_client = ldap.initialize('ldap://adldap.company.com')
ldap_client.set_option(ldap.OPT_REFERRALS,0)
ldap_client.simple_bind_s(LDAP_USERNAME, LDAP_PASSWORD)
except ldap.INVALID_CREDENTIALS as e:
ldap_client.unbind()
return 'Wrong username and password: %s' % e
except ldap.SERVER_DOWN:
return 'AD server not available'
If I run this code, it seems to successfully bind as "username#ad.company.com" with password "password".
I also have a WebSecurityConfig class that I think should be handling auth:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secure")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0}")
.contextSource()
.url("ldap://adldap.company.com");
//.url("ldap://adldap.company.com/dc=ad,dc=company,dc=com");
}
}
When I go to /secure in the app, I get a basic auth pop up but then anything I try entering gets me a 401 Unauthorized. I have tried "username#ad.company.com", without the domain, putting that stuff in the userDnPatterns like {0}#adldap.company.com and a bunch of other things. I have tried using different URLs with the base dn in it or not. Nothing seems to work. What am I missing?
Also, is this the right way to auth users? I've read about both bind authentication and something about binding and searching but the server doesn't allow anonyous binds so I guess I would need some kind of "app user" that could bind and do the searches, right? Is that "better"?
Active Directory has its own non-standard syntax for user authentication, different from the usual LDAP DN binding.
Spring Security provides a specialized AuthenticationProvider for Active Directory.
Try this :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secure")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
#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("adldap.company.com", "ldap://adldap.company.com");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
Long story short, the problem is that Microsoft Active Directory LDAP is not "Vanilla" LDAP and thus you need to connect to it differently.
The working solution is here: https://medium.com/#dmarko484/spring-boot-active-directory-authentication-5ea04969f220
I am getting a 404 after I loggin in a very simple Spring Boot Application. It happen's since I added the password encoder stuff into my configureAuth method. Can someone help me?
Here ist my security configuration code:
#Configuration
#EnableGlobalAuthentication
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
public void configureAuth(final AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().passwordEncoder(passwordEncoder()).dataSource(dataSource).withDefaultSchema()
.withUser("admin").password(passwordEncoder().encode("admin123")).roles("USER", "ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic().and().csrf().disable()
.headers().frameOptions().disable();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
There is no exception or other error. A simple whitelabel error page with 404 is showing up.
EDIT: The login form is coming up, but I think there is something wrong with the authentication.
Thank you,
Christian
You have to configure requests to the login form I believe. Reference.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login");
}
From what it looks like, it's important to specify the .loginPage. I'm using the following config for my project.
http.
.authorizeRequests().antMathcers("/login-page", "/login", "/successful-login", "/error-login").anonymous().and()
.formLogin()
.loginPage("/login-page")
.defaultSuccessUrl("/successful-login")
.loginProcessingUrl("/login")
.failureUrl("/error-login")
.permitAll()
The .loginProcessingUrl is I believe the URL to handle the login POST request.
I'm also using the #EnableWebSecurity annotation on my SecurityConfig class,
My case...
It worked properly
antMatchers("/admin/**")
It was failed after I changed like this
antMatchers("/admin/xxx", "/admin/yyyy", "/admin/zzz")
Solution is to add the loginProc URL like this
antMatchers("/admin/xxx", "/admin/yyyy", "/admin/zzz", "/admin/loginProc")