WebSocket 若依鉴权
使用WebSocket实现实时通信,Ruoyi官方教程做法是在SecurityConfig中将ws的地址设为匿名访问: ("/websocket/**").permitAll()
这种方式虽然能够让WebSocket成功连接,但我想在大多数生产环境中,无需鉴权的情况极为少见。而网络上缺乏明确的Ruoyi解决方案,于是动拙笔浅析下我个人的解决方案。
WebSocket Header携带Token,修改Ruoyi鉴权拦截器
前端vue,将Token放置于创建WebSocket连接请求的protocols参数中:
ini
this.ws = new WebSocket(wsuri,[getToken()]);
则其请求头的Sec-Websocket-Protocol
参数会替换为Token,如下图所示:
JavaScript WebSocket无法自定义Header ,只有
Sec-Websocket-Protocol
内容能通过这种方式改变
在此基础上,我们需要让若依识别协议头Sec-Websocket-Protocol
中的Token
在若依的JWT鉴权请求过滤器JwtAuthenticationTokenFilter.java
中,其拦截逻辑是:
scss
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
//从request请求中解析出LoginUser
LoginUser loginUser = tokenService.getLoginUser(request);
//使用这个LoginUser 完成JWT鉴权流程
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
{
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}
进入这个getLoginUser函数,其内容如下:
ini
/**
* 获取用户身份信息
* @return 用户信息
*/
public LoginUser getLoginUser(HttpServletRequest request)
{
// **从request中获取携带的Token令牌**
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
LoginUser user = CacheUtils.get(CacheConstants.LOGIN_TOKEN_KEY, uuid, LoginUser.class);
return user;
}
catch (Exception e)
{
}
}
return null;
}
在第8行代码getToken中,进入即可看到获取Token的逻辑:
typescript
/**
* 获取请求token
* @param request
* @return token
*/
private String getToken(HttpServletRequest request)
{
String token = request.getHeader(header);
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
其中的
header
变量是ruoyi的全局参数,内容会被装载为在application.yml
中定义的Token协议头(默认是Authorization
)
TOKEN_PREFIX
为全局常量,默认为"Bearer ",标识Token开始前的前缀,我们没有前缀因此可忽略
我们的修改逻辑只是在getHeader为空时再次getHeader我们自己的协议头Sec-Websocket-Protocol
, 修改后内容为:
ini
private String getToken(HttpServletRequest request)
{
String token = request.getHeader(header);
if (StringUtils.isEmpty(token)){
//如果Authorization为空,那么尝试读取Sec-Websocket-Protocol的内容
token = request.getHeader("Sec-Websocket-Protocol");
}else if(token.startsWith(Constants.TOKEN_PREFIX)) {
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
"Sec-Websocket-Protocol"硬编码即可,无需提取为全局参数,因为WebSocket协议头名称永远无法改变
于是ruoyi便能够从WebSocket连接请求中得到Token并成功鉴权,无需配置匿名访问了。