spring boot / spring security is ignoring /login call from javascript - javascript

I am using Spring-Boot 1.1.7, with spring security, html (no thyme-leaf) and javascript. I am unable to get my login to work correctly when I use javascript to submit my login. When I use html with a form, spring-security picks up the requests, authenticates and proceeds happily. But with Javascript, I get a 302 redirect and no authentication.
Here is my configuration:
#ComponentScan
#EnableAutoConfiguration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class MyApplication {
public static void main(String[] args) {
ApplicationContext edm = SpringApplication.run( MyApplication.class, args );
}
}
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/menu").permitAll()
.antMatchers("/error").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.antMatchers("/libs/**").permitAll();
http
.formLogin().failureUrl("/login?error")
.defaultSuccessUrl("/")
.permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
.permitAll();
http
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true)
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/");
http
.authorizeRequests().anyRequest().authenticated();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService( customUserDetailsService ).passwordEncoder( encoder );
}
#Override
public void configure(WebSecurity security){
security.ignoring().antMatchers("/css/**","/fonts/**","/libs/**");
}
}
And I have my own UserDetailsService
#Component
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserService userService;
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userService.findByUsername( userName );
if ( user == null ) {
throw new UsernameNotFoundException( "UserName " + userName + " not found" );
}
return user;
}
}
And finally, here is the javascript that submits the post:
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$("#login").click(function(){
username=$("#username").val();
password=$("#password").val();
$.ajax({
type: "POST",
url: "/login",
beforeSend: function(xhr){
xhr.setRequestHeader(header, token);
},
data: "username="+username+"&password="+password,
success: function(html){
alert("logged in");
}
});
return false;
});
If I make this call from the url /login, I get the spring provided login form and all works perfectly. but I have a need to use javascript so I am wondering if there is something different I need to do to tell spring to look for it?

Here is the answer/solution that worked for me. Based on this article (http://patrickgrimard.com/2014/01/03/spring-security-csrf-protection-in-a-backbone-single-page-app/), adding a CSRFTokenGeneratorFilter extends OncePerRequestFilter and wiring it up to my security config, allowed my javascript provided parameters to be used.
public final class CSRFTokenGeneratorFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
response.setHeader("X-CSRF-HEADER", token.getHeaderName());
response.setHeader("X-CSRF-PARAM", token.getParameterName());
response.setHeader("X-CSRF-TOKEN", token.getToken());
filterChain.doFilter(request, response);
}
}
with wiring as:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new CSRFTokenGeneratorFilter(), CsrfFilter.class)
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
..}
}
I am not sure why the filter is needed, but I guess spring-boot/security doesn't use that as a default.

302 probably means your request either has invalid credentials or you are hitting filter protected area of the app. Are you sure that /login is the url spring filter listens to? I can't see where you set your request path for spring to listen to.. The default if I recall correctly is /j_spring_security_check with j_username and j_password.
Try to override login processing url.
Taking config from our prod as an example:
http
.formLogin()
.loginProcessingUrl("/app/authentication")
.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
Hope this helps.

Related

react-stomp client keeps updating after auth token has expired

I'm using react-stomp as follows:
import React from 'react';
import SockJsClient from 'react-stomp';
render() {
return (
<div>
<SockJsClient url={MY_WS_ENDPOINT + this.wsCredentials}
topics={['/topics/all']}
onMessage={(msg) => { console.log(msg); }}
ref={ (client) => { this.clientRef = client }} />
</div>
);
}
}
The server is built using Springboot, and the configuration is:
security configuration:
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers(MY_WS_ENDPOINT + "/**").authenticated()
.anyMessage().authenticated();
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
WebSocket configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint(MY_WS_ENDPOINT)
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(final MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/")
.enableSimpleBroker(WS_SIMPLE_BROKER.toArray(new String[0]));
}
}
After a while, the token expires, and the client continue trying to update status from server getting 401 errors for ever. Is there any approach where I can redirect to login page once the first 401 error is received?
Thanks (sorry, quite noob with this technology).

WebSockets fails to connect because of lost connection

I'm using spring boot in my backend and vuejs in my front-end . I've defined everything in the guide yet I still don't get my project to work.
FrontEnd
connectToWS() {
this.socket = new Sock("http://localhost:8754/stomp-endpoint");
this.stompClient = Stomp.over(this.socket);
this.stompClient.connect(
{},
frame => {
this.connected = true;
console.log(frame);
this.stompClient.subscribe("/topic/greetings", tick => {
console.log(tick);
this.received_messages.push(JSON.parse(tick.body).content);
});
},
error => {
console.log(error);
this.connected = false;
}
);
},
Backend
WebSocketConfiguration Class :
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfiguration implements
WebSocketMessageBrokerConfigurer
{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry)
{
// with sockjs
registry.addEndpoint("/stomp-endpoint")
.setAllowedOrigins("http://localhost:8081")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/topic");
}
}
WebSecurityConfigClass :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/api/v*/registration/**").permitAll()
.antMatchers("/topic**").permitAll()
.antMatchers("/stomp-endpoint").permitAll()
UPDATE :
The error is actually coming out from the browser . It actually changes the route of the api because it has sockjs integrated into it. I couldn't figure out why the API always gets a /info?t=RandomNumber after it. I need the route to remain the same as I typed it in the front-end.
How can I stop the browser from updating my path. Thanks for helping !

CORS error with Spring boot, javascript and web socket

I'm working on a little test project with a simple html page (using javascript) as Frontend and a Spring Boot application for my apis as Backend. I use websocket with stomp and sockJS to keep alive my connection between front and back. My problem is the following : when I test my connection with Postman i have no problems but when I call the api from my javascript I have the following error :
I tried every solutions I've found on internet and now i'm just stuck
Here is my Spring boot App_controller :
#RestController
#CrossOrigin(origins = "*")
public class App_Controller {
int id = 0;
ArrayList<Player> players = new ArrayList<Player>();
public void addTab(Player p){
players.add(p);
}
#MessageMapping("/batch-socket")
#SendTo("/topic/messages")
public String send(String message) throws Exception {
String time = new SimpleDateFormat("HH:mm").format(new Date());
return (message + " : " + time);
}
#RequestMapping (value = "/error", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public String error(){
return "erreur";
}
}
Here is my corsConfiguration :
#Configuration
public class CorsConfig {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**")
.allowCredentials(true)
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.maxAge(3600);
}
};
}
}
Here is my WebSocketConfiguration :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/topic")
.enableSimpleBroker("/app");
}
#Override public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/batch-socket");
registry.addEndpoint("/batch-socket")
.setAllowedOrigins("*")
.withSockJS();
}
}
And finally here is my function to connect to this WebSocket :
function connect(){
var socket = new SockJS("http://localhost:8080/batch-socket");
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame){
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/messages', function(msgOutput){
console.log(msgOutput);
})
})
}
I tried to just add the #CrossOrigin(origins="*") and #CrossOrigins(origins="http://localhost:63342") and I test multiple possibilities with my CorsConfig.
Thank you for your help in advance
(sorry for my english)
You didn't say how your HTML page with JS is loaded. Is it the same localhost:8080 or maybe local file?
For testing you can tell Chrome to ignore cors restrictions:
chrome --disable-web-security --user-data-dir={some dir}
In your code you are doing registry.addEndpoint() twice, remove one and replace "*" with "http://localhost:63342". Remove all other CORS related code.
I think that you don’t need to create the #bean WebMvcConfigurer corsConfigurer, if it is for a simple test. Could you please do a test removing the registration of that bean and change the order of the annotations?
#CrossOrigin(origins = "http://localhost:63342")
#RestController

Spring STOMP - No matching message handler

My goal is to notify, if a player joins a team in real-time.
Since the player will be created in the controller, i want to notify the team about this change via socket.
This has been my first attempt in doing that:
#RestController
#RequestMapping("/api/secure/players/")
public class PlayerController {
#Autowired
private SimpMessagingTemplate simpMessageTemplate;
#PostMapping
public ResponseEntity<PlayerDTO> createPlayer() {
// ...
// data is being processed
// notify team about the change
simpMessageTemplate.convertAndSend("/teams/" + selectedTeam.getId() + "/", player);
return ...
}
}
#Controller
public class TeamSocket {
private static final Logger logger = LoggerFactory.getLogger(TeamSocket.class);
#MessageMapping("/teams/{team}")
#SendTo("/teams/{team}")
public void teamEvent(#DestinationVariable String team) {
logger.debug("Received message -> {}", team);
}
}
#Configuration
#ComponentScan
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// I still dont fully understand this. Do i have to enable it? (It does not fix the issue).
// config.enableSimpleBroker("/teams/");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/api/secure/stomp/").withSockJS();
}
}
However, Spring tells me, that it was unable to map the request. What am i doing wrong?
DEBUG 29487 --- [nboundChannel-4] .WebSocketAnnotationMethodMessageHandler : Searching methods to handle SUBSCRIBE /teams/16 id=sub-0 session=0nkqv1km, lookupDestination='/teams/16'
DEBUG 29487 --- [nboundChannel-4] .WebSocketAnnotationMethodMessageHandler : No matching message handler methods.
DEBUG 29487 --- [nboundChannel-2] org.springframework.web.SimpLogging : Processing SUBSCRIBE /teams/16 id=sub-0 session=0nkqv1km
const client = new SockJS(`${REST_API}/api/secure/stomp/`);
const stompClient = Stomp.over(client);
stompClient.connect({}, (frame) => {
stompClient.subscribe(`/teams/${teamId}`, (message) => {
...
});
});
I see you are using path "/teams/" + selectedTeam.getId() + "/" in your convertAndSend method while you are subscribing to /teams/${teamId}. Do you need to add additional slash while subscribing or you may need to remove additional slash from convertAndSend?

Notify registered clients using Websockets with AngularJS (angular-websocket-service) and Spring Boot

I am new in AngularJS as well as FullStack development. The architecture of my current app is already set up and should not change preferably (for security reasons). So far, I can emit messages to the server using angular-websocket-service. Here is the code snippet of the service from the front-end:
proxiMiamApp.service('WebSocketService', function ($websocket) {
var wsEndpoint = {};
this.openWsEndpoint = function () {
wsEndpoint = $websocket.connect("ws://localhost:9000/proximiamHandler");
console.log(wsEndpoint);
return wsEndpoint;
}
this.sendMessage = function(){
if($.isEmptyObject(this.wsEndpoint)){
this.openWsEndpoint();
}
eventUser = {
idEvent : '1',
idUser : '49'
};
wsEndpoint.register('/eventUser', function(){
console.log('Register OK!');
});
console.log('Ready!');
wsEndpoint.emit('/eventUser',eventUser);
}});
As for the back-end, I am using an implementation of the WebSocketHandler interface:
#Controller
public class ProximiamHandler implements WebSocketHandler {
#Override
public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
System.out.println("afterConntectionEstablished called");
}
#Override
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
System.out.println("handleMessage called");
// My code here...
}
#Override
public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
System.out.println("handleTransportError called");
}
#Override
public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
System.out.println("afterConnectionClosed called");
}
#Override
public boolean supportsPartialMessages() {
return true;
}}
The Implementation of the WebSocketHandler is called via Spring WebSocketConfigurer
#Configuration
#EnableWebSocket
#Controller
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/proximiamHandler").setAllowedOrigins("*");
}
#Bean
public WebSocketHandler myHandler() {
return new ProximiamHandler();
}}
My questions are:
Can I notify subscribed clients using this architecture?
If yes, how can I do it?
Is there a way to return something to subscribed clients from the server? (an Object or a String for instance)
Thanks in advance for your help
Can I notify subscribed clients using this architecture?
=> Yes.
If yes, how can I do it?
=> Based on the Spring web socket APIs, you have to retain the ' WebSocketSession' passed to you via "afterConnectionEstablished" callback.
Use the sendMessage() API of Web socket session to send notifications to client.
Is there a way to return something to subscribed clients from the server? (an Object or a String for instance)
=> You can format your data in either JSON or XML & wrap it using "WebSocketMessage" and pass it to client.
I never worked on spring, however, I am answering this based on my knowledge on web socket. See if it helps.

Categories

Resources