Cors Error with Post mapping in Spring boot - java

I have a Spring boot application with a rest controller and an Angular application as frontend.
For the moment they are both running in localhost and SpringSecurity is enabled is Spring.
Originally I was unable the make a getRequest from Angular to Spring because of Cors. I added #CrossOrigin to my restContoller and now I'm able to do a Get request from angular to Spring.
Now I have the same problem with post request. I want to send some form data from angular to Spring but I always get an error in Chrome. I added #CrossOrigin here as well but I still have the problem.
If I try a post request with postmen it's working just fine
zone.js:3243 Access to XMLHttpRequest at 'localhost:8080/rest/contact' from origin 'http://localhost:4200' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
contact.component.ts:51 HttpErrorResponse {headers: HttpHeaders, status: 0, statusText: "Unknown Error", url: "localhost:8080/rest/contact", ok: false, …}
This is my security configuration:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void configure (AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
http
.authorizeRequests()
.antMatchers("/admin/**").authenticated()//.hasAnyRole("ADMIN","USER")
.and().formLogin().loginPage("/login").permitAll()
.and().logout();
http.csrf().disable();
//http.headers().frameOptions().disable();
}
private PasswordEncoder getPasswordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return encode(charSequence).equals(s);
}
};
}
}
My Cors configuration:
#Configuration
public class CorsConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
};
}
}
My rest controller:
#RestController()
#CrossOrigin(origins = "http://localhost:4200/**", maxAge = 3600)
public class GymRestController {
private final GymRepository gymRepository;
GymRestController (GymRepository gymRepository) {
this.gymRepository = gymRepository;
}
#GetMapping("/rest/gyms")
public List<Gym> findAll() {
return gymRepository.findAll();
}
#PostMapping ("/rest/contact")
public void submitContact(#RequestBody ContactForm contactForm) {
System.out.println(contactForm);
}
}
and my on submit method in angular
onSubmit() {
this.submitted = true;
if (this.messageForm.invalid) {
return;
}
this.success = true;
this.contactModel.fromName = this.messageForm.get('name').value;
this.contactModel.fromMail = this.messageForm.get('email').value;
this.contactModel.subject = this.messageForm.get('subject').value;
this.contactModel.message = this.messageForm.get('message').value;
let url = "http://localhost:8080/rest/contact";
// let url = "https://cors.io/?localhost:8080/rest/contact"
this.http.post(url, this.contactModel).subscribe(
res => console.log("success"),
error => console.log(error),
() => console.log("complete")
);
}
I've been trying for 3 days to get this working without any luck
Any help would be appreciated

I finally found the solution. I had to enable cors in Spring Security and disable csrf
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.authorizeRequests()
.antMatchers("/admin/**").authenticated()//.hasAnyRole("ADMIN","USER")
.and().formLogin().loginPage("/login").permitAll()
.and().logout();
http.csrf().disable();
http.headers().frameOptions().disable();
}
I had to removed #CrossOrigin from the controller and I added the following configuration:
#Configuration
public class CorsConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")
.allowedOrigins("http://localhost:4200");
}
};
}
}

Following on Spring io link :
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
If you are using Spring Boot, it is recommended to just declare a WebMvcConfigurer bean as following:
#Configuration
public class MyConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
};
}
}
You can easily change any properties, as well as only apply this CORS configuration to a specific path pattern:
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE","POST")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
Above you can replace http://domain2.com with your localhost or required host/url.

Related

Using tls with jwt

I want to use tls with my sharing of token and login process. I implemented it and it works fine. I create a new port for tls that is https://localhost:8443 but there is a security leak in my app. When i post https://localhost:8443/login and take the jwt and use it with GET https://localhost:8443/welcome everything great if i use http instead of https bad request is returned. But if i change my url with http://localhost:8080/welcome and gives the token that is taken before the app must return bad request but it returns succesful open. My server and security config is below. What am i doing wrong?
Server Config:
#Configuration
public class ServerConfig {
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(getHttpConnector());
return tomcat;
}
private Connector getHttpConnector() {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}
}
Security Config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenFilter jwtTokenFilter;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configurePasswordEncoder(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(getBCryptPasswordEncoder());
}
#Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Auth Controller:
#RestController
#RequestMapping("/login")
public class AuthConroller {
#Autowired
private TokenManager tokenManager;
#Autowired
private AuthenticationManager authenticationManager;
#PostMapping
public ResponseEntity<String> login(#RequestBody LoginRequest loginRequest) {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
return ResponseEntity.ok(tokenManager.generateToken(loginRequest.getUsername()));
} catch (Exception e) {
throw e;
}
}
}
Message Controller:
#RestController
#RequestMapping("/message")
public class MessageController {
#GetMapping
public ResponseEntity<String> getMessage() {
return ResponseEntity.ok("JWT demo");
}
}

CORS policy conflict in Spring boot

I'm new to spring boot and I implemented some basic REST apis using spring boot. And when I tried to call those api using react I've got an error calling some CORS policies. Then I found a solution to that problem, And all my api end point working correctly except the login api call. I get the same error calling:
Here is my web security java class.
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private final UserService userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL)
.permitAll().anyRequest().authenticated().and().cors().and().addFilter(getAuthenticationFilter())
.addFilter(new AuthorizationFilter(authenticationManager())).sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
public AuthenticationFilter getAuthenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl("/users/login");
return filter;
}
}
And here is my example controller class.
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
UserService userService;
#CrossOrigin
#GetMapping(path = "/{id}",
produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
public UserRest getUser(#PathVariable String id) {
UserRest returnValue = new UserRest();
UserDto userDto = userService.getUserByUserId(id);
BeanUtils.copyProperties(userDto, returnValue);
return returnValue;
}
#CrossOrigin
#PostMapping(
consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE },
produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
public UserRest createUser(#RequestBody UserDetailsRequestModel userDetails) {
UserRest returnValue = new UserRest();
UserDto userDto = new UserDto();
BeanUtils.copyProperties(userDetails, userDto);
UserDto createUser = userService.createUser(userDto);
BeanUtils.copyProperties(createUser, returnValue);
return returnValue;
}
}
I'm stuck with this problem and I really need some help. I've tried some answers given in stackoverflow as similar to my problem. But that didn't solve my problem. Mt spring boot version is 2.1.4
You are facing the issue because,you have allowed cors on your backend from port 8080, but your react is up in port 3000 on local.So , springboot will not accept the request as it is coming from a different port address.
Method 1 :
You could get away with this by using the annotation like :
#CrossOrigin(origins = "http://localhost:3000", maxAge = 3600)
Since you are using springboot , you can also use the following global config to define which all domains have access to your backend.
#Configuration
public class MyConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("PUT", "DELETE", "GET", "POST") //or allow all as you like
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
};
}
}
and if you are still having issues , it might be because the spring security is not adding the CORS headers like Access-Control-Allow-Origin in your response header. In that case , you can define a custom CORS filter and add it to the spring security so that the CORS response headers are set for all spring security endpoints . You can create a filter bean like :
public class CorsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request= (HttpServletRequest) servletRequest;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", true);
response.setHeader("Access-Control-Max-Age", 180);
filterChain.doFilter(servletRequest, servletResponse);
}
#Override
public void destroy() {
}
}
Method 2:
since you are using spring security you could also add the CORS configuration along with spring security like :
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
//other configurations that you want
}
#Bean
CorsConfigurationSource corsConfigurationSource()
{
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
//or any domain that you want to restrict to
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
//Add the method support as you like
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Spring security will take up the CORS configuration even if you provide it like in method 1 as per my knowledge.But , in spring documentation they have provided this way of registering CORS with spring security as well.
In your console log that you have attached it is said that the pre flight check failed because of http status returned is not OK .So , can you try registering cors like method 2 and have a filter added so that the headers are added correctly in response. Springboot is blocking the pre flight check when the login page is accessed. You need to get your cors setup properly with the spring security to allow login page to be accessed from a dfferent port.
Confirm that your login page /login mapping have CORS allowed
Official Doc
Reference Doc
Try this,
#Configuration
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*");
}
}
Update
you could also do the below,
#Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOrigins(Collections.singletonList("*"));
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> filterRegistration = new FilterRegistrationBean<>(new CorsFilter(source));
filterRegistration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterRegistration;
}

Set basic auth on server side

I configured resource and authorization servers in one application. I use spring-security oauth2, with Resource Owner Password Credentials. Can I set up basic auth on the server side? I don't want to do it on the front-end.
I don't know what a part of the code I need to show...
When I want to receive a token I need to enter this in postman:
Can I configure it on the server side?
Authorization Server:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private TokenStore tokenStore;
#Autowired
private JwtAccessTokenConverter jwtTokenEnhancer;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).tokenEnhancer(jwtTokenEnhancer).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager)
.pathMapping("/oauth/token", "/login");
}
}
Resource Server:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource_id";
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/swagger-ui.html#").permitAll()
.antMatchers("/").authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
Security config:
#Configuration
#EnableWebSecurity(debug = true)
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtTokenEnhancer());
}
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
converter.setSigningKey("Demo-Key-1");
return converter;
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore) {
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
#Override
#Order(Ordered.HIGHEST_PRECEDENCE)
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
.and().cors().and().csrf().disable();
}
}
This answer is accompanied by a complete and working sample.
Maybe you are biting off more than you can chew here?
For example:
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
The /oauth/token endpoint must remain protected. This is the endpoint on the authorization server that issues tokens to authenticated clients. The system will probably fail with NullpointerException or other exceptions if you open it, however, the above configuration option indicate that maybe you're a bit confused about how OAuth2 works.
What I would recommend is to first fully understand authorization server versus resource server. You can definitely combine the two, but they would have very different endpoints.
Authorization Server - typical endpoints
/oauth/token - issues tokens
/oauth/authorize - issues authorization codes
/introspect - validates a token and returns token claims in a known format
Resource Server - These would be your application endpoints, requiring Bearer tokens, for example
/account/123/debit
and these endpoints expect a stateless request that has an authorization header
Authorization: Bearer <token value here>
A controller for a resource server would look like this:
#PreAuthorize("hasRole('your-scope-role')")
#RequestMapping(value = "/hello")
#ResponseBody
public String hello(Principal principal) {
return "Hello to " + principal.getName();
}
Feel free to review the simple project that I have created for you.
In addition to that, I also recommend this video on OAuth2 and OpenID Connect
In my sample, I have configured the clients like this:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
InMemoryClientDetailsService clientDetails = new InMemoryClientDetailsService();
BaseClientDetails client = new BaseClientDetails(
"testclient",
null,
"testscope,USER,ADMIN",
"password",
null
);
client.setClientSecret(passwordEncoder.encode("secret"));
clientDetails.setClientDetailsStore(
Collections.singletonMap(
client.getClientId(),
client
)
);
clients.withClientDetails(clientDetails);
}
Take a look at this simple test case, the client is using http-basic authentication:
mvc.perform(
post("/oauth/token")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.param("username", "admin")
.param("password", "password")
.param("grant_type", "password")
.param("response_type", "token")
.param("client_id", "testclient")
.header("Authorization", "Basic " + Base64.encodeBase64String("testclient:secret".getBytes()))
This is client authentication, using the http-basic method:
.header("Authorization", "Basic " + Base64.encodeBase64String("testclient:secret".getBytes()))

How do you make a secure websocket conneciton with Java on the client side?

There doesn't seem to a clean and simple example of creating a secure websocket connection anywhere on the interwebs, nor instructions to set one up... any ideas?
I would provide some guidelines for websocket authentication. Since websocket is upgraded from http, the authentication is based on http too. You can equip the http connection with ssl or basic or digest auth.
I had worked with spring websocket auth before, ssl is just to upgrade http to https. I would post digest auth for spring websocket here.
1.Configure the server to user digest auth, spring security can get it:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public final static String REALM="MY_REALM";
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN")
.and().withUser("test").password("test").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().exceptionHandling().authenticationEntryPoint(getDigestEntryPoint())
.and().addFilter(getDigestAuthenticationFilter(getDigestEntryPoint()));
}
#Bean
public MyDigestAuthenticationEntryPoint getDigestEntryPoint() {
MyDigestAuthenticationEntryPoint digestAuthenticationEntryPoint = new MyDigestAuthenticationEntryPoint();
digestAuthenticationEntryPoint.setKey("mykey");
digestAuthenticationEntryPoint.setNonceValiditySeconds(120);
digestAuthenticationEntryPoint.setRealmName(REALM);
return digestAuthenticationEntryPoint;
}
public DigestAuthenticationFilter getDigestAuthenticationFilter(
MyDigestAuthenticationEntryPoint digestAuthenticationEntryPoint) throws Exception {
DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter();
digestAuthenticationFilter.setAuthenticationEntryPoint(digestAuthenticationEntryPoint);
digestAuthenticationFilter.setUserDetailsService(userDetailsServiceBean());
return digestAuthenticationFilter;
}
#Override
#Bean
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
}
public class MyDigestAuthenticationEntryPoint extends DigestAuthenticationEntryPoint {
#Override
public void afterPropertiesSet() throws Exception{
super.afterPropertiesSet();
setRealmName(WebSecurityConfig.REALM);
}
}
2.Extend from AbstractSecurityWebSocketMessageBrokerConfigurer:
#Configuration
#EnableWebSocketMessageBroker
public class WssBrokerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated()
.simpSubscribeDestMatchers("/topic/notification").permitAll()
.simpDestMatchers("/**").authenticated()
.anyMessage().denyAll();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/ws");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hpdm-ws").setAllowedOrigins("*").withSockJS();
}
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(mapper);
return converter;
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
3.Digest auth for client refer to this post:
spring websocket with digest authentication

Spring security logout not working because of CORS

Technologies: Spring Security, Spring Boot on backend and ReactJs and axios on front-end.
What I want to have: When hitting a logout button on my front-end I want to log out the user. In order to do so I make a call to backend using delete. Then I want my backend to log out.
My issue: When I call Spring Security logout endpoint from my front-end I receive the following message:
Failed to load http://localhost:8080/logout: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8888' is therefore not allowed access. The response had HTTP status code 403. I do understand why I have this error - backend is on localhost:8080 and front-end on localhost:8888. But what I don't understand is why my configuration doesn't work for logout, while it works perfectly fine for all other situations (e.g. calling spring security login endpoint or some of my custom endpoints).
How I make the call from the front-end
const endpoint = 'logout';
return axios.delete(
'http://localhost:8080/' + `${endpoint}`,
{withCredentials: true}
)
.then(response => {
let resp = {
httpCode: response.status,
data: response.data
};
return {response: resp};
})
.catch(error => {
let err = {
httpStatusCode: error.response.status,
message: `Error calling endpoint ${endpoint}`
};
return {error: err};
});
Enabling CORS from front-end
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8888");
}
};
}
SecurityConfig.java - here you might notice some parts are commented - they are other solutions I tried but they didn't work.
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#EnableJpaRepositories(basePackageClasses = UsersRepository.class)
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
private RESTAuthenticationSuccessHandler restAuthenticationSuccessHandler;
#Autowired
private RESTAuthenticationFailureHandler restAuthenticationFailureHandler;
#Bean
public CustomLogoutHandler customLogoutHandler(){
return new CustomLogoutHandler();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("**/anna/**").authenticated()
.anyRequest().permitAll()
.and()
.logout()
.addLogoutHandler(customLogoutHandler())
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);
http.formLogin().successHandler(restAuthenticationSuccessHandler);
http.formLogin().failureHandler(restAuthenticationFailureHandler);
// http
// .logout()
// .logoutUrl("/logout")
// .addLogoutHandler(new CustomLogoutHandler())
// .invalidateHttpSession(true);
// http
// .cors()
// .and()
// .csrf().disable()
// .logout()
// .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}
private PasswordEncoder getPasswordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
};
}
}
CustomLogoutHandler.java I read somewhere about the solution with setting the header here. I guess it's bad practice and would prefer not to do it, but basically I'd be happy to see the log working. Currently it's not logging anything so my guess it that it's not called on logout.
//#Slf4j
public class CustomLogoutHandler implements LogoutHandler{
#Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
response.setHeader("Access-Control-Allow-Origin", "*");
System.out.println("TodosLogoutHandler logging you out of the back-end app.");
}
}
I checked the source code of CorsRegistration,
public CorsConfiguration applyPermitDefaultValues() {
[...]
if (this.allowedMethods == null) {
this.setAllowedMethods(Arrays.asList(
HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));
}
[...]
return this;
}
As you can see, the Delete method is NOT ALLOWED by default, So you need add the delete method.
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8888")
.addAllowedMethod(HttpMethod.DELETE);
}
};
}

Categories

Resources