Spring boot unauthorized response with XMLHttpRequest - spring

I have spring boot + spring security application and I discovered strange behavior. When I send request with wrong authorization (basic), I receive normal error message in body:
{
"timestamp": "2018-01-15T11:59:31.837+0000",
"status": 401,
"error": "Unauthorized",
"message": "Bad credentials",
"path": "/karbonator/api/v1/wallet"
}
But if I put X-Requested-With: XMLHttpRequest in header - I will receive only status 401 without body.
Is it normal? How can I fix it and receive error message in body with header XMLHttpRequest?
Spring boot version is 1.5.9.RELEASE
I do not use any custom header filters. Security configuration is simple:
SecurityBeanConfiguration
#Configuration
public class SecurityBeanConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
#Bean
UserDetailsService userDetailsService() {
return new UserDetailsService() {
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userRepository.get(s);
if (Objects.nonNull(user)) {
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
true,
true,
true,
true,
AuthorityUtils.createAuthorityList(""));
} else {
throw new UsernameNotFoundException("Could not find the user " + s);
}
}
};
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
return provider;
}
}
SecurityConfiguration
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DaoAuthenticationProvider authenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and().httpBasic()
.and().csrf().disable()
;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
}
All project available here: https://github.com/rublin/XMLHttpRequest
P.S. It looks like the Spring Security issue

Related

How to map 403 error to user friendly error?

I'm new to building rest APi using spring boot.
Here is my controller snippet
#PreAuthorize("hasRole('ADMIN')")
#PostMapping(value = "/api/post/posts", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PostDto> createPost(#Valid #RequestBody PostDto postDto) {
System.out.println("postDto : " + postDto.getId());
return new ResponseEntity<>(postService.createPost(postDto), HttpStatus.CREATED);
}
this is my Security config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true) //give method level security
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.GET, "/api/**").permitAll().anyRequest()
.authenticated().and().httpBasic();
}
#Override
#Bean
protected UserDetailsService userDetailsService() {
// In Memory Users
UserDetails ashish = User.builder().username("oxana").password(getPasswordEncoder().encode("password")).roles("USER").build();
UserDetails admin = User.builder().username("admin").password(getPasswordEncoder().encode("admin")).roles("ADMIN").build();
return new InMemoryUserDetailsManager(ashish, admin);
}
#Bean
PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
I'm trying land above exception here
#ExceptionHandler(Exception.class)
public ResponseEntity<Errors> handleGlobalException(Exception exception,
WebRequest webRequest){
Error errorDetails = new Error();
errorDetails.setErrorDesc(exception.getMessage());
errorDetails.setErrorCode(Error.ErrorCodeEnum.BAD_REQUEST);
Errors errors = new Errors();
errors.addErrorsItem(errorDetails);
return new ResponseEntity<>(errors, HttpStatus.INTERNAL_SERVER_ERROR);
}
but its not coming and giving a big mess of error, like this
"timestamp": "2022-02-21T11:39:28.797+00:00",
"status": 403,
"error": "Forbidden",
"trace": "org.springframework.security.access.AccessDeniedException: Access is denied\r\n\tat org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73)\r\n\tat org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:238)\r\n\tat org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:208)\r\n\tat org.springframework.security.access.intercept.aopalliance.
Can anyone please suggest me, How can I handle or catch this exception to customize error, where user has no access to do something ?
Thanks
Update
Implemented AccessDeniedHandler in below way
#ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Dont have sufficient priviliges to perform this action")
public class AccessDeniedError implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exec)
throws IOException, ServletException {
response.sendRedirect("Dont have sufficient priviliges to perform this action");
}
}
And now able to get message like this
{
"timestamp": "2022-02-21T13:29:08.377+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/api/post/Dont%20have%20sufficient%20priviliges%20to%20perform%20this%20action"
}
Its somewhat better, but how can I take control of these variables ("error", "message", "status") values from above response, so that I could add mine custom values in it ?
The AccessDeniedException is handled by the ExceptionTranslationFilter which then delegates to the AccessDeniedHandler to write to corresponding response to the client.
If you want to customize this behavior, then you can implement a AccessDeniedHandler and then set your implementation to the HttpSecurity object.
MyAccessDeniedHandler.java
public class MyAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
writeCustomResponse(response);
}
private void writeCustomResponse(HttpServletResponse response) {
if (!response.isCommitted()) {
try {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write("{ \"error\": \"User is not authorized.\"}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
EDIT
Since WebSecurityConfigurerAdapter was deprecated in Spring Security 5.7.0-M2, the following shows how to configure it by registering a SecurityFilterChain bean.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler())
return http.build();
}
OLD WAY
SecurityConfiguration.java
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// set the customized AccessDeniedHandler to the HttpSecurity object
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
}
}

keycloak Spring Security : Unable to login with Bearer token as null

I have integrated the Keylock with Spring boot using #KeycloakConfiguration in SecurityConfig Class,
#KeycloakConfiguration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
// configureGlobal() tasks the SimpleAuthorityMapper to make sure roles are not
// prefixed with ROLE_.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
// keycloakConfigResolver defines that we want to use the Spring Boot properties
// file support instead of the default keycloak.json.
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
// we are permitting all here but we are gonna have method level
// pre-authorization
#Override protected void configure(HttpSecurity http) throws Exception {
super.configure(http); http.cors().and().csrf().disable()
.authorizeRequests().antMatchers("/admin**").hasAnyRole("admin")
.anyRequest().permitAll();
}
// we configure to accepts CORS requests from all and any domains
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST", "PUT", "DELETE");
}
};
}
#PostMapping("/login")
public #ResponseBody AuthToken login(#RequestBody LoginForm form) {
Collection<SimpleGrantedAuthority> authorities =
(Collection<SimpleGrantedAuthority>) SecurityContextHolder
.getContext().getAuthentication().getAuthorities();
AuthToken authToken = authService.login(form);
authToken.setAuthorities(authorities);
return authToken;
}
and I am able to log in without a Bearer token and with an empty Bearer token.
I have created a login page in angular,
and from that, I am passing the bearer token is null.
I am getting
status": 401,
“error”: “Unauthorized”
and there are no security logs on eclipse.
Thanks and Regards
Put this into SecurityConfig
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
// All of Spring Security will ignore the requests
.antMatchers("/login");
}

How do I implement conditional authentication in spring security based on user selection between LDAP and JDBC?

Hope you all are doing good and safe. I have an application which will allow a user to login either by using his LDAP credentials or using his credentials stored on database. I was able to configure separate jdbc and ldap authenticator but how can I implement it conditionally? Say, a user selects LDAP radio button on login page then it should trigger LDAP authentication and if user selects database authentication, then it should trigger jdbc authentication. Any help would be highly appreciated.
JDBC authenticator:-
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.authorizeRequests()
.antMatchers("/admin").hasAnyRole("ADMIN")
.antMatchers("/user").hasAnyRole("ADMIN", "USER")
.antMatchers("/").permitAll()
.and().formLogin();
}
#Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
**LDAP authenticator:-**
#EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private UserDetailsService userDetailsService;
#Autowired
AuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
#Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new AuthenticationFailureHandler() {
String message = "";
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception.getClass().isAssignableFrom(UsernameNotFoundException.class)) {
message = "User Not Found";
} else if (exception.getClass().isAssignableFrom(DisabledException.class)) {
message = "Account Disabled";
} else if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
message = "Bad Credentials";
}
else if (exception.getClass().isAssignableFrom(LockedException.class)) {
message = "Account Locked";
}
else {
message = "Internal Server Error";
}
response.sendRedirect("/WisoKeyinPortal/login?error=" + message);
}
};
}
#Bean
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
#Bean
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
return authenticationProvider;
}
/*---------------------------For QA comment from here-------------------------------*/
#Bean public AuthenticationManager authenticationManager() { return new
ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean public AuthenticationProvider
activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new
ActiveDirectoryLdapAuthenticationProvider("xyz.com", "ldap://xyz.com");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setSearchFilter("sAMAccountName={1}"); return provider; }
/*----------------------------For QA comment ends here-----------------------------*/
#Override
protected void configure(HttpSecurity http) throws Exception {
String[] staticResources = { "/css/**", "/images/**", "/fonts/**", "/scripts/**", };
http.csrf().disable().authorizeRequests().antMatchers("/login**").permitAll().antMatchers(staticResources)
.permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll()
.successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler).and()
.logout().invalidateHttpSession(true).clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login").permitAll();
//.and().sessionManagement().invalidSessionUrl("/login?error=session");
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
//authManagerBuilder.inMemoryAuthentication().withUser("admin").password("pass").roles("ADMIN");
/*---------------------------For QA comment from here-------------------------------*/
authManagerBuilder.authenticationProvider(
activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService());
/*---------------------------For QA comment from here-------------------------------*/
}
}

trouble connecting Angular 10 with Spring security to use custom login page

I have been working on a web application using Spring boot and spring security with frontend controlled by angular 10. I have implemented backend for security and created a login page also. But, on running on local host it is throwing an error
blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I have been banging my head all day long to resolve this error but could not find the solution.
I have attached my code below for reference
Controller
#RestController
#RequestMapping("/user")
public class UserController {
#Autowired
AuthenticationManager authenticationManager;
#PostMapping("/login")
public boolean login(#RequestBody loginDetails data) {
try {
String username = data.getUsername();
System.out.println("Checking...");
System.out.println(data.getUsername());
System.out.println(data.getPassword());
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, data.getPassword()));
// String token = jwtTokenProvider.createToken(username,
// this.users.findByEmail(username).getRoles());
System.out.println("abcdefg");
Map<Object, Object> model = new HashMap<>();
model.put("username", username);
// model.put("token", token);
/* return true; */
} catch (AuthenticationException e) {
/*
* throw new BadCredentialsException("Invalid email/password supplied");
*/
return false;
}
return true;
}
WebSecurityConfiguration
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Qualifier("userDetailsService")
#Autowired
private UserDetailsService userDetailsService;
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/user/save","/user/login",
"/admin/**").permitAll().anyRequest().authenticated().and().csrf()
.disable().formLogin().permitAll().and().logout().permitAll();
http.cors();
}
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
AngularRequestcode
public doLogin(){
this.userLogin.username=this.loginForm.get("username").value;
this.userLogin.password=this.loginForm.get("password").value;
console.log(this.userLogin);
return this.http.post<any>("http://localhost:8080/user/login",this.userLogin).subscribe((response) => {
if (response.status === 200) {
console.log('login successfully');
} else {
console.log('galat');
}
}
);
}
First of all, try change to:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and()
.authorizeRequests().antMatchers("/user/save", "/user/login",
"/admin/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and().logout().permitAll();
}
CORS it's browser check if you have response with: Access-Control-Allow-Origin: http://localhost:4200 or no: No 'Access-Control-Allow-Origin' header is present on the requested resource.;
Change http://localhost:4200 to your front-end url;
And add to your WebSecurityConfig:
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("*");
}
and: implements WebMvcConfigurer
Response without error:
Response with error. No Access-Control-Allow-Origin:

How to permit endpoints in spring authorization server?

I have a spring boot oauth2 authorization server which will provide and authorize tokens. I also want to provide endpoints for user creation. Can you tell me how to permit these endpoints for non-authenticated users? I tried following configuration:
#Configuration
#EnableAuthorizationServer
#RequiredArgsConstructor
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
private final UserDetailsService userDetailsServiceImpl;
private final AuthenticationManager authenticationManager;
private final PasswordEncoder passwordEncoder;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// TODO persist clients details
clients.inMemory()
.withClient("browser")
.authorizedGrantTypes("refresh_token", "password")
.scopes("ui");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsServiceImpl);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(passwordEncoder);
}
}
and authorization server configuration:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsServiceImpl;
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> {
authorizeRequests
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated();
});
}
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsServiceImpl)
.passwordEncoder(passwordEncoder());
}
}
and here is endpoint which I want permit:
#RestController
#RequestMapping(path = "/user")
#RequiredArgsConstructor
public class UserController {
private final UserService userService;
#PostMapping
public UUID create(#RequestBody UserDto userDto) {
return userService.create(userDto);
}
}
With these configurations I always got response:
{
"timestamp": "2019-12-28T16:01:09.135+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/user"
}
I am using spring boot 2. Thank you in advice.
You need to disable CSRF in your AuthorizationConfig class. Try this configuration :
http.authorizeRequests(authorizeRequests -> {
authorizeRequests
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated();
}).csrf(csrf -> {
csrf.disable();
});
For more information about CSRF, check this website: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF).
Basically, you don't want to allow anybody to POST information on your website, so you allow to POST only if the user can provide a token indicating that he is using your website to POST (the token is provided by your server). In many web applications now, you can disable it, as you are POSTing from many locations... But don't forget the security of your website.

Resources