1. 后台注册一个WebSocket
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
// 添加一个名为 "/endpointChat" 的基于STOMP子协议的节点(即服务器端socket)
stompEndpointRegistry.addEndpoint("/endpointChat").withSockJS();
}
}
使用的是WebSocket的子协议STOMP
2. 自定义数据库认证方式
@Service
public class CustomUserService implements UserDetailsService {
private static final Log log = LogFactory.getLog(CustomUserService.class);
@Autowired
private UserMapper userMapper;
/**
* 重写 loadUserByUsername 方法获取 userDetails 类型用户
* SysUser 已经实现 UserDetails
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userMapper.findByUsername(username);
if (user != null) {
log.info("username存在");
log.info("打印: " + user);
// 用户权限
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
//用于添加用户的权限。只要把用户权限添加到authorities[本项目固定是用户角色]
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), authorities);
} else {
throw new UsernameNotFoundException("admin: " + username + " do not exist!");
}
}
}
- 认证方式有很多,可以直接使用内存存储用户,这里是使用通用的数据库存储用户信息。
- 直接实现 org.springframework.security.core.userdetails.UserDetailsService接口的loadUserByUsername方法即可。这个方法返回的是一个org.springframework.security.core.userdetails.UserDetails对象
- 作用是根据参数用户名,查找用户信息,然后交由Spring Security判断密码是否正确(Spring Security会对前端的密码做BCrypt加密然后和得到的用户信息的密码作比较,BCrypt是多对多)
- 重要的还有用户的角色,根据不同角色可以编写不同的功能,这也是Spring Security的一个类的必须属性。企业用户角色,这里暂时都采用普通用户类型,还有ROLE_ADMIN等等。
3. 再WebSecurityConfig里面配置加入上面的数据库认证方式
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
UserDetailsService myCustomUserService() {
return new CustomUserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myCustomUserService());
}
....
}
认证方式对应的是这个 protected void configure(AuthenticationManagerBuilder auth) throws Exception {…} 方法。
里面可以配置各种认证方式,比如提到过的内存验证。自定义数据库验证的就得实现那个接口。如上这种方式配置。
4. 客户端订阅
var sock = new SockJS("/chatroom/endpointChat");
var stomp = Stomp.over(sock);
stomp.connect('guest', 'guest', function (frame) {
stomp.subscribe("/user/notification", showGetMsg);
});
- /endpointChat就是后台创建的WebSocket,前面的/chatroom只是个人项目的应用上下文。这里可以使用 "./endpointChat"替换。
- 这里已客户身份登录,stomp.subscribe("/user/notification", showGetMsg);是订阅服务器端的通知。user就是通用的普通用户角色,服务器可以根据订阅了/notification的相应客户端自由转发消息。 showGetMeg是绑定的自定义回调函数,当服务器转发消息来的时候,会自动调用这个函数。参数是message。如下
/**
* 显示收到的信息
* @param message
*/
function showGetMsg(message) {
console.log("收到服务器转发的的信息(别人发来的):" + message);
// 这个API时间格式化函数别人写的是,这里用hh ...
var now = new Date().Format("yyyy-MM-dd hh:mm:ss");
// 这里显示还没有完善(此外:异步回调的message是一个http数据包,数据在body)
$('.active-chat')[0].innerHTML += '<div class="bubble you">'.concat(message.body).concat('</div>');
}
5. 客户端发送消息
stomp.send("/chat", {}, JSON.stringify({"text": text, "receiver": receiver}));
=="/chat"==是消息请求路径,类似@RequestMapping。这里是@MessageMapping
(org.springframework.messaging.handler.annotation.MessageMapping)
最后一个参数是自定义消息主体。这里我使用JSON,对应后台的参数是Message。
6. 后台转发消息
@MessageMapping(value = "/chat")
public void handleChat(Principal principal, Message message) {
if (message.getReceiver().equals("")) {
log.info("不填接收者,这是广播");
simpMessagingTemplate.convertAndSendToUser("", "/notification",
"@广播(" + principal.getName() + "): " + message.getText());
} else {
simpMessagingTemplate.convertAndSendToUser(message.getReceiver(), "/notification",
principal.getName() + ": " + message.getText());
}
}
a. 消息请求映射。这里要注意即便应用上下文不是"/", 以上那个前端发送那里也不能多填上下文URL,如"/chatroom/chat"。因为这个是@MessageMapping,不是@RequestMapping
b. Message参数就是如上的JSON消息主体。这里我用一个POJO做映射@Component public class Message { private String text; private String receiver; /** getter and setter **/ }
c. Principal参数来自java.security.Principal,并不是Spring的。但是是JDK默认提供给企业用户验证的一个类。里面主要保持了发送者的信息。这里可以获取发送者的用户名。而且这个参数是必须的。
d. simpMessagingTemplate.convertAndSendToUser(接收对象, 订阅名称, 信息);
因为一个用户是可以订阅很多功能的。这里只是用了一个。订阅的参数值这里是 “/notification”,前端多出来的那个 "/user"就是填充了用户的角色类型了。