This question already has answers here:
CORS issue - No 'Access-Control-Allow-Origin' header is present on the requested resource
(8 answers)
Closed 20 days ago.
I have the following configurations:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor(onConstructor = #__({#Autowired}))
public class SecurityConfig {
private final JwtAuthFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
private static final String[] PUBLIC_MATCHERS = {"/v1/auth/**"};
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers(PUBLIC_MATCHERS)
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
#Configuration
public class CorsConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "TRACE", "CONNECT");
}
}
And the endpoint:
#RestController
#RequestMapping("/v1/clients")
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class ClientRestController {
private final ClientService service;
private final ClientMapper mapper;
#GetMapping
public ResponseEntity<Page<ClientResponse>> getByUser(
#RequestParam(defaultValue = "0") final int page) {
final var clients = service.findAllByUserLogged(PageRequest.of(page, 10));
return ResponseEntity.ok().body(clients.map(mapper::toResponse));
}
But when I send the requests from angular application I get the this:
Access to XMLHttpRequest at 'http://localhost:8080/v1/clients' from origin 'http://localhost:4200' has been 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'm sending the Authorization header, in the get, but my application are not sending the header in options request, I don't if it is that is causing this problem. I get error in the two requests.
I tried a request right after receiving a 200 from my authenticate endpoint.
Add http.cors() to your filterchain
http.cors().and().csrf().disable();
Then you can put this in your controller class. But seeing as you already have CorsConfiguration class, you might not need the below. Although the snippet below is a shorter way of doing it.
#CrossOrigin(origins = "http://localhost:4200")
Related
I'm trying to add security headers to my Spring Boot application.
It already had a Java class with multiple filters extending from WebSecurityConfigurerAdapter. But whenever I try to add the annotation #EnableWebSecurity to this class or even with a new custom one I always receive NullPointerException for the bean springSecurityFilterChain.
Changing the order to add some filters seems to solve this problem but whenever I try to enter the app I can't because it seems the HTTP Authorization header field is null (which I recover inside one of my custom filters).
Do any have a clue of what is happening?
EDIT: After some days of cheking this I noted that the Authorization header was not the problem as the code is built to let that call enter without it and before any change it was already sent without header.
Still with the same call and the changes I'm receiving a 403 FORBIDDEN (before any change this call was receiving 302 FOUND).
This happens before even reaching the controller and I can only get debugging until the filter.
As there were no other changes in the code except the #EnableWebSecurity and the way to add one filter I suspect the problem is around here but i can't find what is causing it exactly.
EDIT: I'm adding the code in case anyone need to see it.
This is the class that has the multiple filters:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity //ADDED THIS ONE
public class MultipleEntryPointsSecurityConfig {
#Configuration
#Order(1)
public class OauthSecurityAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2RestTemplate restTemplate;
#Bean
public CustomFilterOneFilter customFilterOneFilter() {
final CustomFilterOneFilter filter = new CustomFilterOneFilter ("/testLogin");
filter.setRestTemplate(restTemplate);
return filter;
}
#Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.antMatcher("/login")
.cors()
.and()
.csrf().disable()
//CHANGED THIS
// .addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
//FOR THESE TWO
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
.httpBasic()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/testLogin"))
.and()
.logout()
.logoutSuccessUrl("/logout")
.permitAll()
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated();
// #formatter:on
}
}
#Configuration
#Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public JwtSecurityFilter authenticationJwtTokenFilter() {
return new JwtSecurityFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated();
http
.addFilterAfter(new UsernamePasswordAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//CHANGED THE BELOW ONE FOR THE TWO ABOVE
//http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
#Configuration
#Order(3)
public static class PublicConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").permitAll()
.antMatchers("/api/v1/login/**").permitAll();
}
}
}
And this is the custom filter where I try to recover the Authorization header:
#Component
public class JwtSecurityFilter extends OncePerRequestFilter{
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
//FAILS HERE!
if(authHeader == null || !authHeader.startsWith("Bearer ")) {
SecurityContextHolder.getContext().setAuthentication(null);
chain.doFilter(request, response);
return;
}
...
}
}
When a POST request to a non-existing end-point in the application is sent, the server returns 405 instead of 404. A similar problem for Requests with an existing endpoint occurs, the status code returns 200 whenever everything goes right, but when an internal server error occurs (for example User not found), the http response becomes 405 (instead of 500). With GET requests everything works as it should.
The weird thing is, if I put on the debugger, and I follow the process of the error to be thrown, it is handling a 500 error. But apparently somewhere in the end something goes wrong and I get a 405 returned.
My web security config:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#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(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Value("${allowedOrigin}")
private String origin = "http://localhost:4200";
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//You can enforce the use of HTTPS when your app is running on Heroku by adding
// the following configuration to your Spring Boot app.
httpSecurity.requiresChannel()
.requestMatchers(r - > r.getHeader("X-Forwarded-Proto") != null)
.requiresSecure();
httpSecurity
.cors()
.and().csrf()
.disable()
// dont authenticate this particular request
.authorizeRequests()
.antMatchers("/api/authenticate")
.permitAll()
// all other requests for /api need to be authenticated
.antMatchers("/api/**", "/admin/**")
.authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
UPDATE:
I do not have any ControllerAdivce and there is no Global Exception handler written.
The "Allow" header in the 405 response reads "GET, HEAD", even when the POST request actually entered the POST endpoint.
Based on your HttpSecurity configuration you only allowing Post requests, you will get 405 (method not allowed) for other HTTP methods like GET, PUT....
antMatchers(HttpMethod.POST).permitAll();
It turned out an ErrorController with an unimplemented method for the "/error" path was causing the problem. Whenever an exception or an error was thrown it was resolved to "/error" and picked up by the ErrorController, which for some reason resolved it to a 405. After implementing the method the HTTP statuses are returned correctly.
#RestController
public class RoutingController implements ErrorController {
private static final String PATH = "/error";
#RequestMapping(value = PATH)
public String handleError(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
return String.format("<html><body><h2>Error Page</h2><div>Status code: <b>%s</b></div>" +
"<div>Exception Message: <b>%s</b></div><body></html>",
statusCode, exception == null ? "N/A" : exception.getMessage());
}
public String getErrorPath() {
return PATH;
}
}
I am currently working on a Spring project, where I got the Security already implemented.(University) But I occurred the following problem: If I want to register a new user to the system, I logically do not have a JWT to authenticate the new user. I just get Invalid authorization header or token back from Spring, if I try to register a new user. So, I think the Security is not right configured:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
private final RequestMatcher whiteListedRequests;
private final SecurityProperties securityProperties;
private final JwtTokenizer jwtTokenizer;
#Autowired
public SecurityConfig(UserService userService,
PasswordEncoder passwordEncoder,
SecurityProperties securityProperties, JwtTokenizer jwtTokenizer) {
this.userService = userService;
this.securityProperties = securityProperties;
this.passwordEncoder = passwordEncoder;
this.jwtTokenizer = jwtTokenizer;
this.whiteListedRequests = new OrRequestMatcher(securityProperties.getWhiteList().stream()
.map(AntPathRequestMatcher::new)
.collect(Collectors.toList()));
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.csrf()
.disable();
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/v1/users/sign-up")
.permitAll();
http.authorizeRequests().anyRequest()
.authenticated();
http.addFilter(new JwtAuthenticationFilter(authenticationManager(), securityProperties, jwtTokenizer));
http.addFilter(new JwtAuthorizationFilter(authenticationManager(), securityProperties));
}
#Override
public void configure(WebSecurity web) {
web.ignoring().requestMatchers(whiteListedRequests);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final List<String> permitAll = Collections.unmodifiableList(Collections.singletonList("*"));
final List<String> permitMethods = List.of(HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.PUT.name(),
HttpMethod.PATCH.name(), HttpMethod.DELETE.name(), HttpMethod.OPTIONS.name(), HttpMethod.HEAD.name(),
HttpMethod.TRACE.name());
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedHeaders(permitAll);
configuration.setAllowedOrigins(permitAll);
configuration.setAllowedMethods(permitMethods);
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Does anyone has a tip, how I can change the securityconfig, that a user can register himself against my Endpoint? (/api/v1/users/sign-up) I am kind of lost at this, and trying for several hours to manage the wanted behavior!
Thanks in Advance!
EDIT:
I just figured that my application.yml holds a whitelist:
security:
auth:
header: Authorization
prefix: "Bearer "
login-uri: /api/v1/authentication
white-list:
# Browser requests
- /
- /favicon.ico
- /csrf
- /v2/api-docs
- /swagger-resources/**
- /webjars/**
- /swagger-ui.html
# H2
- /h2-console/**
# Registration
- /api/v1/registrations/
If I add the URI here, it works properly. But is there a solution to just add it via Code?
the registration uri must not request a token, it must be allowed access from the outside.
it must be enabled at the antMatcher
Here is an exemple
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable();
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/regitration/**")
.permitAll();
http.authorizeRequests().anyRequest()
.authenticated();
http.addFilter(new JWTAuthenticationFilter(authenticationManager(),userRepository,iLdapService,cryptoHelper));
http.addFilterBefore(new JWTAutorizationFilter(),UsernamePasswordAuthenticationFilter.class);
}
my backend app in spring boot and secured with ssl. I used OAuth2 facebook login. Also the frontend app in Angular 7 and secured by ssl. My problem is sending requests Angular to my Spring boot App. All apps is https.
P.S. All works if i add url to webSecurity.ignoring(). and not secure my backend. i think some problem with security and HTTPS requests. THANKS FOR HELP.
BACKEND
SecurityConfig.java
#RestController
#CrossOrigin(origins = "https://192.168.1.106:4400")
#Configuration
#Order(1000)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserRepo userRepo;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/unauth/**").permitAll()
.antMatchers(HttpMethod.POST, "/unauth/upload").permitAll()
.antMatchers(HttpMethod.POST, "/api/**").authenticated()
.antMatchers(HttpMethod.PUT, "/api/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/api/**").authenticated()
.antMatchers(HttpMethod.GET, "/api/**").authenticated()
.anyRequest().permitAll()
.and().logout().logoutSuccessUrl("/").permitAll();
}
#Override
public void configure(WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers(HttpMethod.GET, "/unauth/**");
webSecurity.ignoring().antMatchers(HttpMethod.POST, "/unauth/**");
}
webSecurity.ignoring().antMatchers(HttpMethod.POST, "/unauth/**");
}
SomeRestController.java
#RestController
#CrossOrigin(origins = "https://192.168.1.106:4400")
#RequestMapping ("/api")
public class ProductService {
#Autowired
private ProductRepo productRepo;
#CrossOrigin(origins = "https://192.168.1.106:4400")
#GetMapping("/products")
public List<Product> getProducts(){
return productRepo.findAll();
}
SpringBootApplication.java
#SpringBootApplication
#EnableOAuth2Sso
#CrossOrigin(origins = {"https://192.168.1.106:4400"}, allowCredentials = "false")
public class MongoTestApplication {
public static void main(String[] args) {
SpringApplication.run(MongoTestApplication.class, args);
}
}
FRONTEND
SomeComponent.html
< button (click)="makeRequest()"> MAKE REQUEST < /button >
SomeComponent.ts
val:any = {};
makeRequest(){
this.http.get("https://localhost:8443/api/products").subscribe(value => {this.val = value; console.log(this.val.key)});
}
ERROR
error in browser
Access to XMLHttpRequest at 'https://localhost:8443/api/brands' from origin 'https://192.168.1.106:4400' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
core.js.pre-build-optimizer.js:15714 ERROR n {headers: t, status: 0, statusText: "Unknown Error", url: "https://localhost:8443/api/brands", ok: false, …}
Edit your main class as below and remove all #CrossOrigin from the controllers.
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#SpringBootApplication
#EnableOAuth2Sso
public class MongoTestApplication {
public static void main(String[] args) {
SpringApplication.run(MongoTestApplication.class, args);
}
#SuppressWarnings("deprecation")
#Bean
public WebMvcConfigurer corsConfigurer()
{
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "PUT", "POST", "DELETE", "OPTIONS");
}
};
}
}
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
});