I developed a mobile chat application with springboot,spring security,spring websockets ,stomp and sockjs. Server running at port 8082 and client running at 8100 port.
I am using angular and sockjs client to send requests.when I try to connect to STOMP end point its giving the error at developer console(front end) and back end console.Below is the track trace,
From Developer console:
Opening Web Socket...
stomp.min.js (line 8)
GET http://localhost:8082/ws/info?t=1451547968350
200 OK
565ms
sockjs-1.0.1.js (line 1610)
Web Socket Opened...
stomp.min.js (line 8)
CONNECT
login:test
passcode:test
accept-version:1.1,1.0
heart-beat:10000,10000
**<<< ERROR message:Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is org.springframework.security.access.AccessDeniedException\c Access is denied
content-length:0*
Back end Error **:**
org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:127) ~[spring-messaging-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:104) ~[spring-messaging-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.web.socket.messaging.StompSubProtocolHandler.handleMessageFromClient(StompSubProtocolHandler.java:280) ~[spring-websocket-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.handleMessage(SubProtocolWebSocketHandler.java:317) [spring-websocket-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75) [spring-websocket-4.2.3.RELEASE.jar:4.2.3.RELEASE
**Angular and sockjs code files:**
chatController.js**
angular.module('letsCatchApp').controller(
'chatController',
function($scope, $location, $interval, toaster, chatSocket,$state) {
var typing = undefined;
$scope.username = '';
$scope.sendTo = 'everyone';
$scope.participants = [];
$scope.messages = [];
$scope.newMessage = '';
$scope.sendMessage = function() {
console.log('22222');
var destination = "http://localhost:8082/app/chat.message";
if($scope.sendTo != "everyone") {
destination = "http://localhost:8082/app/chat.private." + $scope.sendTo;
$scope.messages.unshift({message: $scope.newMessage, username: 'you', priv: true, to: $scope.sendTo});
}
chatSocket.send(destination, {}, JSON.stringify({message: $scope.newMessage}));
$scope.newMessage = '';
};
$scope.startTyping = function() {
// Don't send notification if we are still typing or we are typing a private message
if (angular.isDefined(typing) || $scope.sendTo != "everyone") return;
typing = $interval(function() {
$scope.stopTyping();
}, 500);
chatSocket.send("http://localhost:8082/topic/chat.typing", {}, JSON.stringify({username: $scope.username, typing: true}));
};
$scope.stopTyping = function() {
console.log('44444');
if (angular.isDefined(typing)) {
$interval.cancel(typing);
typing = undefined;
chatSocket.send("http://localhost:8082/topic/chat.typing", {}, JSON.stringify({username: $scope.username, typing: false}));
}
};
$scope.privateSending = function(username) {
console.log('55555');
$scope.sendTo = (username != $scope.sendTo) ? username : 'everyone';
};
$scope.initStompClient = function() {
console.log('66666');
chatSocket.init('http://localhost:8082/ws');
console.log('66666rrrrrrrrrrrrrrrrr');
chatSocket.connect(function(frame) {
console.log('101010101010101');
$scope.username = frame.headers['user-name'];
chatSocket.subscribe("http://localhost:8082/app/chat.participants", function(message) {
console.log('7777');
$scope.participants = JSON.parse(message.body);
});
chatSocket.subscribe("http://localhost:8082/topic/chat.login", function(message) {
console.log('888888');
$scope.participants.unshift({username: JSON.parse(message.body).username, typing : false});
});
chatSocket.subscribe("http://localhost:8082/topic/chat.logout", function(message) {
console.log('99999');
var username = JSON.parse(message.body).username;
for(var index in $scope.participants) {
if($scope.participants[index].username == username) {
$scope.participants.splice(index, 1);
}
}
});
chatSocket.subscribe("http://localhost:8082/topic/chat.typing", function(message) {
var parsed = JSON.parse(message.body);
if(parsed.username == $scope.username) return;
for(var index in $scope.participants) {
var participant = $scope.participants[index];
if(participant.username == parsed.username) {
$scope.participants[index].typing = parsed.typing;
}
}
});
chatSocket.subscribe("http://localhost:8082/topic/chat.message", function(message) {
console.log('88888');
$scope.messages.unshift(JSON.parse(message.body));
});
chatSocket.subscribe("http://localhost:8082/user/exchange/amq.direct/chat.message", function(message) {
console.log('9999');
var parsed = JSON.parse(message.body);
parsed.priv = true;
$scope.messages.unshift(parsed);
});
chatSocket.subscribe("http://localhost:8082/user/exchange/amq.direct/errors", function(message) {
toaster.pop('error', "Error", message.body);
});
}, function(error) {
console.log("errrooooooo======= "+error);
toaster.pop('error', 'Error', 'Connection error ' + error);
});
};
//initStompClient();
});
chatService.js
angular.module('letsCatchApp').service('chatSocket',
function($rootScope,$http) {
console.log('Chat service is called.... ');
var stompClient;
this.test = function() {
console.log('Chat service test... ');
return $http.get('http://localhost:8082/app/')
}
this.init = function(url) {
console.log('Chat service init ... '+url);
stompClient = Stomp.over(new SockJS(url));
console.log('Chat service init ...end---- '+url);
}
this.connect = function(successCallback, errorCallback) {
console.log('Chat service init ... connect');
stompClient.connect({'login': 'test',
'passcode': 'test'}, function(frame) {
$rootScope.$apply(function() {
return successCallback(frame);
});
}, function(error) {
$rootScope.$apply(function(){
return errorCallback(error);
});
});
}
this.subscribe = function(destination, callback) {
console.log('Chat service init ... subscribe');
stompClient.subscribe(destination, function(message) {
$rootScope.$apply(function(){
return callback(message);
});
});
}
this.send = function(destination, headers, object) {
return stompClient.send(destination, headers, object);
}
});
**WebsocketConfig files:**
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{
//AbstractSessionWebSocketMessageBrokerConfigurer {
public static final String IP_ADDRESS = "IP_ADDRESS";
// public static final String IP_ADDRESS = "IP_ADDRESS";
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
egistry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
registry.addEndpoint("/ws").setAllowedOrigins("*")
.setHandshakeHandler(new DefaultHandshakeHandler() {
@Override
protected Principal determineUser(
ServerHttpRequest request,
WebSocketHandler wsHandler,
Map<String, Object> attributes) {
Principal principal = request.getPrincipal();
if (principal == null) {
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(
"ANONYMOUS"));
principal = new AnonymousAuthenticationToken(
"WebsocketConfiguration", "anonymous",
authorities);
}
return principal;
}
}).withSockJS()
.setInterceptors(httpSessionHandshakeInterceptor());
}
@Bean
public HandshakeInterceptor httpSessionHandshakeInterceptor() {
return new HandshakeInterceptor() {
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
attributes.put(IP_ADDRESS,
servletRequest.getRemoteAddress());
attributes.put("Access-Control-Allow-Origin",
"*");
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
}
};
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
System.out
.println("registering the endpoint ==of ===/queue/===========");
registry.enableSimpleBroker("/queue/", "/topic/", "/exchange/");
// registry.enableStompBrokerRelay("/queue/", "/topic/", "/exchange/");
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> arg0) {
}
@Override
public void addReturnValueHandlers(
List<HandlerMethodReturnValueHandler> arg0) {
// TODO Auto-generated method stub
}
@Override
public void configureClientInboundChannel(ChannelRegistration arg0) {
// TODO Auto-generated method stub
}
@Override
public void configureClientOutboundChannel(ChannelRegistration arg0) {
// TODO Auto-generated method stub
}
@Override
public boolean configureMessageConverters(List<MessageConverter> arg0) {
return false;
}
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration arg0) {
// TODO Auto-generated method
}
HI Artem Bilan,Thanks for your reply.As per your comments,I am sharing the security config.
**WebsecurityConfig.java**
@Configuration
@EnableWebSecurity
@EnableRedisHttpSession
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
private RoleService roleService;
/**
* This section defines the user accounts which can be used for
* authentication as well as the roles each user has.
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.POST, "/person/**")
//.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers(HttpMethod.GET, "/lookup/**")
.antMatchers(HttpMethod.POST, "/upload/image/**")
.antMatchers(HttpMethod.GET, "/ws/**")
.antMatchers(HttpMethod.GET, "/topic/**")
.antMatchers(HttpMethod.GET, "/app/**");
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
Iterable<com.sergialmar.wschat.model.Role> roleIterable = roleService.getRoles();
List<Role> roleList = new ArrayList<Role>();
Iterator<Role> iterator = roleIterable.iterator();
while (iterator.hasNext()) {
Role role = iterator.next();
roleList.add(role);
System.out.println("Roles : " + role.getName());
}
String[] roles = new String[roleList.size()];
for (int roleIndex = 0; roleIndex < roleList.size(); roleIndex++) {
roles[roleIndex] = roleList.get(roleIndex).getName();
}
http.httpBasic().and().authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").hasAnyAuthority(roles)
.antMatchers(HttpMethod.POST, "/**").hasAnyAuthority(roles)
.antMatchers(HttpMethod.PUT, "/**").hasAnyAuthority(roles)
.antMatchers(HttpMethod.DELETE, "/**").hasAnyAuthority(roles)
.antMatchers(HttpMethod.OPTIONS, "/**").hasAnyAuthority(roles)
.and().csrf().disable();
http.headers().frameOptions().disable();
}
}
JS是否在头中添加身份验证令牌?
var headers = {};
var csrf = self.csrfToken();
headers[csrf.headerName] = csrf.token;
headers['X-AUTH-TOKEN'] = AUTH_TOKEN;
stompClient.connect(headers, function(frame) );
暂时禁用与安全相关的配置,例如:AbstractSecurityWebSocketMessageBrokerConfigurer:
@Override
protected boolean sameOriginDisabled() {
return true;
}
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpTypeMatchers(SimpMessageType.CONNECT,
SimpMessageType.HEARTBEAT,
SimpMessageType.UNSUBSCRIBE,
SimpMessageType.DISCONNECT)
.permitAll()
.anyMessage().authenticated() //or permitAll
.simpDestMatchers("/**").authenticated();//or permitAll
}
WebSecurityConfigurerAdapter:
http.csrf().disable();
http.headers().frameOptions().disable()
.httpStrictTransportSecurity().disable();
问题内容: 我只能在用户的套接字ID直接存储在io.sockets.on(’connect’)函数中时向用户发出消息。我不知道为什么在登录后尝试存储其套接字ID时为什么不起作用。 加工: 无法运作: JavaScript客户端代码段 解决方案:感谢@alessioalex, 我不得不从登录页面中删除对socket.io的引用,并将以下内容添加到io.sockets.on(’connection’)
问题内容: 所以现在,我正在制作一个基于客户端服务器应用程序的多线程。在服务器端,我为接受的每个连接创建了一个线程。 在线程类中,我创建了一种将命令发送到客户端的方法。我只想要的是如何将参数发送到所有正在运行的客户端?为简单起见,我只想使此服务器向所有连接的客户端发送消息。 我已经阅读了这篇文章,并从此链接中找到方法。但是,当我尝试使用自己的代码时,中没有类似的方法。 好的,这是我的服务器和线程示
当我提交表单时,我在浏览器控制台中看到“emit”消息,所以我知道表单提交事件正在触发,但我没有在服务器上收到测试消息。客户端或服务器端似乎什么也没发生,“socket.emit”函数似乎什么也没做。 我做错了什么?
我尝试将Spring与websocket一起使用。我从本教程开始调查。 在我的侧客户端,我有类似的东西来初始化到服务器的连接: 它工作得很好,在我的控制器中,我可以在下面的类中执行我的过程: 现在我想做的是让一个线程向监听“/主题/问候”的客户端发送消息。我这样写Runnable类: 这样完成了我的控制器: 该方法采用光电控制器。fireGreeting按我的要求调用,但客户端没有发生任何事情。有
我后来理解对了。实际上,我需要一条来自android客户端的MQTT消息发送到所有其他客户端,所以我想在消息正文中包含publish关键字,这是非常错误的。MQTT本身将接收到的消息发送给所有提供的客户端,如果客户端订阅了该主题的话。
主要内容:一、通知,二、源码分析,三、总结一、通知 在Redis中,既可以实现消息的发送又可以实现订阅,也就是常说的发布/订阅。它的主要逻辑实现在nofigy.c和module.c、pubsub.c中,其实通知做为一种辅助功能,是易用性的一种良好的接口。Redis的通知功能是一种即时在线通知功能,如果CS端断线后,相关的消息就无法再次通知,也就是说,消息机制没有保存和重发功能。这也是为了实现设计上的简单和功能实现的鲁棒性的考虑,至于以后会