Get 403 status when using Spring Websocket and SockJS in AngularJS - angularjs

I got this error and my web socket cannot run:
403
Here is my configure for websocket:
#Configuration
#EnableWebSocketMessageBroker
public class SpringWebSocketConfig extends
AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setUserDestinationPrefix("/ws-secured/user/");
config.setApplicationDestinationPrefixes("/ws");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-secured/init-api").withSockJS();
registry.setErrorHandler(new ApplicationStompSubProtocolErrorHandler());
}
}
and websocket security:
#Configuration
public class SpringWebSocketSecurityConfig extends
AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry
messages) {
messages.simpTypeMatchers(
SimpMessageType.CONNECT,
SimpMessageType.MESSAGE,
SimpMessageType.SUBSCRIBE).authenticated()
.simpTypeMatchers(
SimpMessageType.UNSUBSCRIBE,
SimpMessageType.DISCONNECT).permitAll()
.anyMessage().denyAll();
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
For more information, here is my csrf configure in Spring Sec:
http.headers().frameOptions().sameOrigin().and().authorizeRequests();
http
.csrf()
.ignoringAntMatchers("/ws-secured/**")
.and()
.headers()
.frameOptions().sameOrigin();
I have also pass my csrf token to the header:
$stomp.connect('/ws-secured/init-api', {'X-CSRF-TOKEN' : csrf_token}, function(data) {
I have searched through the Internet but cannot find the answer. I am using Tomcat 7. Do you have any ideas?

Related

How do I implement Github OAuth2 login with Spring Boot using only REST api?

I am trying to implement Signup with Github in a sample project, where front-end code is made up of ReactJS and Backend API is made up of Spring Boot.
I am not using Server Template Engines such as ThymeLeaf or Mustache, but only creating RESTful APIs.
Below is my Spring Security Configuration.
#RequiredArgsConstructor
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final GithubOAuth2UserService githubOAuth2UserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/health-check")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(githubOAuth2UserService);
}
}
Below is my code for GithubOAuth2UserService
#RequiredArgsConstructor
#Service
public class GithubOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
System.out.println(registrationId);
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint()
.getUserNameAttributeName();
System.out.println(userNameAttributeName);
OAuthAttributes attributes = OAuthAttributes.ofGithub(userNameAttributeName, oAuth2User.getAttributes());
User user = saveUser(attributes);
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(user.getRole().name())),
attributes.getAttributes(),
attributes.getNameAttributeKey()
);
}
#Transactional
protected User saveUser(OAuthAttributes attributes) {
System.out.println("SAVE USER!");
if(userRepository.findByEmail(attributes.getEmail()).isPresent()) {
throw new UserEmailConflictException();
} else {
User user = attributes.toEntity();
return userRepository.save(user);
}
}
}
Finally, OAuthAttributes is as below.
#Getter
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
#Builder
public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name, String email) {
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
}
public static OAuthAttributes ofGithub(String userNameAttributeName, Map<String, Object> attributes) {
return OAuthAttributes.builder()
.name((String) attributes.get("name"))
.email((String) attributes.get("email"))
.attributes(attributes)
.nameAttributeKey(userNameAttributeName)
.build();
}
public User toEntity() {
return User.builder()
.name(name)
.email(email)
.build();
}
}
Via PostMan, I can see the API result as below, but my ReactJS code doesn't show anything, and just refreshes the page.
On ReactJS, it calls this API like below.
// OnClick function
const result = await axios.get("http://localhost:8080/oauth2/authorization/github");
console.log(result);
UPDATE
I am getting CORS policy violation, and I configured this using a configuration class below. Is this related to SecurityConfig class ?
#Configuration
#EnableWebMvc
public class CORSConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("*");
}
}
Thank you so much in advance. If any code needs to be shown, I will happily edit this post.

SpringBoot AngularJS webSocket integration

I'm trying to create webSocket on springBoot application.
this is config class:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/sub");
config.setApplicationDestinationPrefixes("/send");
}
#Override
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
}
#Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}
#Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
}
#Override
public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
return true;
}
}
Now from AngularJS I'm trying to connect to websocket using SockJS and Stomp
var socket = new SockJS('/api/ws');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function (frames) {
stompClient.subscribe('/subs/hello', function (data) {
console.log(data);
});
});
When I open console I get this message:
admin-components.js:112173 WebSocket connection to 'wss://proxy.beta.corp.payment21.com/api/ws/135/hwq2yv3q/websocket' failed: Error during WebSocket handshake: Unexpected response code: 502
After 30 seconds I get this:
VM333 sockjs.min.js:2 Uncaught Error: Incompatibile SockJS! Main site uses: "1.4.0", the iframe: "1.0.0".
at s (VM333 sockjs.min.js:2)
And after 30 more seconds:
And it is working...
When I go to network to see details in the frames it says
(Opcode -1)
What is the problem here? Is it the spring configuration or SockJS?
Based on the Spring documentation the supported client SockJS version is 1.0.x
On the browser side, applications can use the sockjs-client (version 1.0.x). It emulates the W3C WebSocket API and communicates with the server to select the best transport option, depending on the browser in which it runs.

Annotation CrossOrigin not working in Spring boot

I have a Spring Boot application that exposes some endpoints. From a React app I want to make requests to these endpoints, but it keeps giving me CORS problem:
access to XMLHttpRequest at
'localhost:9090/helios-admin/api/dashboard/clients?page=0&size=30'
from origin 'http://localhost:3000' has been blocked by CORS policy:
Cross origin requests are only supported for protocol schemes: http,
data, chrome, chrome-extension, https.
So I've tried using #CrossOrigin annotation for all my methods and also for the Controller class, but the error is the same.
The get request in my react app looks like this:
constructor() {
this.url = 'localhost:9090/helios-admin/api/dashboard/clients?page=0&size=30';
}
getProjectStatusById(projectId) {
Axios.get(this.url).then(res=>{
console.log(res.data);
})
}
What's missing?
EDIT
In my spring boot app I have only this class to configure security:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
SysdataUserDetailsService sysdataUserDetailsService;
#Autowired
private JwtAuthEntryPoint jwtAuthEntryPoint;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(sysdataUserDetailsService)
.passwordEncoder(encoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(PathConstants.USER_AUTH +"/**", PathConstants.HELIOS+"/dashboard/**").permitAll()
.antMatchers(HttpMethod.GET, "/"+PathConstants.PROCESS_DEFINITION+"/**").permitAll()
.antMatchers(HttpMethod.POST, "/"+PathConstants.PROCESS_DEFINITION+"/**").permitAll()
.antMatchers(HttpMethod.GET, "/"+PathConstants.PROCESS_INSTANCE+"/**").permitAll()
//.anyRequest().authenticated()
.anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// custom jwt filter.
http.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
You can add it by overriding addCorsMappings of WebMvcConfigurerAdapter, so either create a class that extends WebMvcConfigurerAdapter or define a bean in your configuration class like this:
#Bean
public WebMvcConfigurer corsConfigurer () {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain1.com", "http://domain2.com")
.allowedMethods("GET", "OPTIONS")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
}
}
Edit
As of 5.0 WebMvcConfigurerAdapter is deprecated and hence you could acheive the same thing by implementing WebMvcConfigurer interface (added default methods, thanks java 8 ! and can be implemented directly without the need for this adapter)
#Configuration
#EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain1.com", "http://domain2.com")
.allowedMethods("GET", "OPTIONS")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
}
You can create separate CORS configuration class as follows. This class will handle the CORS configurations for all requests throughout your application and you need not annotate each controller separately with #CrossOrigin.
#Configuration
public class CorsConfig {
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*"); //'*' allows all endpoints, Provide your URL/endpoint, if any.
config.addAllowedHeader("*");
config.addAllowedMethod("POST"); //add the methods you want to allow like 'GET', 'PUT',etc. using similar statements.
config.addAllowedMethod("GET");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
You can add a global configuration like
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000");
}
};
}
Just use this to add a global corrs configuration that will affect all the endpoints.Try this if the annotation doesn't work.

Spring Oauth2 + User Registration

I have problem with Spring Oauth2 again. I know this topic is not easy to suggest sth or check the codes because we have too much configuration.
My project has 3 different servers, Authentication server, resource server and front-end server. I want to put register.html to user's registration in front-end project(under Angularjs files) but when I make request to the related url (http://localhost:7080/app/#register) its redirecting to the login page (http://localhost:9080/auth-service/login) only for a second i can see my register.html content but after that its going to login page.
The question is, where should i put this register.html, it should be under front-end project or authentication server ?
My authentication server and front-end server codes are;
#Configuration
public class AuthServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.parentAuthenticationManager(authenticationManager);
auth.authenticationProvider(userAuthProviderService());
}
private CsrfMatcher csrfRequestMatcher = new CsrfMatcher();
#Override
protected void configure(final HttpSecurity http) throws Exception {
/*http.csrf().disable();*/
http.csrf().requireCsrfProtectionMatcher(csrfRequestMatcher);
http
.formLogin().loginPage("/login").defaultSuccessUrl("/")
/*.failureUrl("")*/.successHandler(new AuthSuccessHandler()).permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access","/register")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Bean
public UserAuthProviderService userAuthProviderService(){
return new UserAuthProviderService();
}
private class CsrfMatcher implements RequestMatcher {
#Override
public boolean matches(HttpServletRequest request) {
return false;
}
}
}
#Configuration
#EnableAutoConfiguration
#RestController
#EnableZuulProxy
#EnableOAuth2Sso
#EnableOAuth2Client
public class UIServiceMain {
public static void main(String[] args) {
SpringApplication.run(UIServiceMain.class, args);
}
#Configuration
protected static class SecurityConfiguration extends OAuth2SsoConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.logout().and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/index.html", "/home.html", "/", "/login","/register.html").permitAll().anyRequest()
.authenticated().and().csrf().disable();
http.headers().frameOptions().disable(); //FOR EMBED MAP
}
//unused
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
//unused
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
}
in your UI server try to create websecurity with /register.hml enabled, something like this
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/register.html")
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
edit:
or maybe in your current configuration remove .antMatcher("/**").authorizeRequests() and add and() .authorizeRequests().anyRequest().authenticated()
so finally it could be something like this:
#Override
public void configure(HttpSecurity http) throws Exception {
http.logout().and()
.antMatchers("/index.html", "/home.html", "/", "/login","/register.html").permitAll().anyRequest()
.authenticated()
.and().csrf().disable();
http.headers().frameOptions().disable() //FOR EMBED MAP
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
Couple of things:
I can't think of a good reason not to put your *.html anywhere other than front end server.
Also, in general, you should permit access to your static UI components publically, like #bilak mentioned:
.antMatchers("/index.html", "/home.html", "/", "/login","/register.html").permitAll()
If you are able to see register.html page at all (assuming unauthenticated user) then it is public already
Perhaps, there is a webservice call on register.html's load event that is behind Spring security that is triggering the auth flow.

Spring Security: Still can't disable redirect with logoutSuccessHandler

I am using Spring Security with Oauth2 to secure RESTful API. My WebSecurityConfig class looks like this:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RestAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(
SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/logout").permitAll()
.antMatchers("/ristore/**").authenticated()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(new SimpleUrlAuthenticationFailureHandler());
http.logout().permitAll();
http.logout().logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)));
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public RestAuthenticationSuccessHandler mySuccessHandler(){
return new RestAuthenticationSuccessHandler();
}
#Bean
public SimpleUrlAuthenticationFailureHandler myFailureHandler(){
return new SimpleUrlAuthenticationFailureHandler();
}
#Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://ldap.mdanderson.edu:389/dc=mdanderson,dc=edu");
contextSource.setUserDn("cn=ris_flow,ou=service accounts,ou=institution,ou=service accounts,dc=mdanderson,dc=edu");
contextSource.setPassword("!BMpl#tform2O15");
contextSource.setReferral("follow");
contextSource.afterPropertiesSet();
LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthenticationProviderConfigurer = auth.ldapAuthentication();
ldapAuthenticationProviderConfigurer
.userDnPatterns("cn={0},ou=institution,ou=people")
.userSearchBase("")
.contextSource(contextSource);
}
}
}
In order to disable redirect for logout, I added the logoutSuceessHandler.
At the front end, I wrote the following function to handle logout event with AngularJS:
$scope.logout = function() {
$http.post(SERVER + '/logout', {}).success(function() {
$rootScope.authenticated = false;
$window.localStorage.removeItem("access_token");
$location.path("/");
}).error(function(data) {
console.log("Logout failed")
$rootScope.authenticated = false;
});
};
However, I still got the following error at logout:
XMLHttpRequest cannot load http://localhost:8080/logout. The request was redirected to 'http://localhost:8080/login?logout', which is disallowed for cross-origin requests that require preflight.
I tried every solution in the following similar posts and nothing seems to stop the redirect. spring security /logout not working cross origin requests and Spring security - Disable logout redirect.
What am I missing?
EDIT
After I turned on debug mode in logging spring security, here is the output for logout request. Why is it trying match /logout request to /oauth/token?

Resources