【问题标题】:spring-security block websocket (sockjs)弹簧安全块 websocket (sockjs)
【发布时间】:2017-08-23 11:57:18
【问题描述】:

在我的一个项目中,我同时配置了 rest 服务和 websocket,它们都通过了检查 JWT 的 spring 安全过滤器。对于客户端的 websockets,应用程序使用 sockjs & stomp(在 Angular2 上)和服务器端的 Spring websockets(Tomcat 8)。当我打开启用了 Spring 安全性的连接时,在它打开两秒钟后我得到以下错误。但是,当我打开没有启用弹簧安全的连接时,连接不会被丢弃。

angular2 connect()/subscribe()/send() - 都使用 JWT 令牌

public connect() : void {
        let sockjs = new SockJS('/rest/add?jwt=' + this.authService.getToken());
        let headers : any = this.authService.getAuthHeader();
        this.stompClient = Stomp.over(sockjs);
        this.stompClient.connect(this.token, (frame) => {
            this.log.d("frame", "My Frame: " + frame);
            this.log.d("connected()", "connected to /add");
            this.stompClient.subscribe('/topic/addMessage', this.authService.getAuthHeader(), (stompResponse) => {
                // this.stompSubject.next(JSON.parse(stompResponse.body));
                this.log.d("result of WS call: ", JSON.parse(stompResponse.body).message);
            }, (error) => {
                this.log.d(error);
            });
        });
    }

    public send(payload: string) {
        this.stompClient.send("/app/add", this.token, JSON.stringify({'message': payload}));
    }

JwtAuthenticationFilter.java

public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public JwtAuthenticationFilter() {
        super("/rest/**");
    }

    @Override
    protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
        return true;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String token = null;

        String param = request.getParameter("jwt");
        if(param == null) {
            String header = request.getHeader("Authorization");
            if (header == null || !header.startsWith("Bearer ")) {
                throw new JwtAuthenticationException("No JWT token found in request headers");
            }
            token = header.substring(7);
        } else {
            token = param;
        }
        JwtAuthenticationToken authRequest = new JwtAuthenticationToken(token);

        return getAuthenticationManager().authenticate(authRequest);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        super.successfulAuthentication(request, response, chain, authResult);

        // As this authentication is in HTTP header, after success we need to continue the request normally
        // and return the response as if the resource was not secured at all
        chain.doFilter(request, response);
    }
}

JwtAuthenticationProvider.java

@Service
public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    private SecurityService securityService;

    @Override
    public boolean supports(Class<?> authentication) {
        return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    }

    @Override
    @Transactional(readOnly=true)
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
        String token = jwtAuthenticationToken.getToken();

        User user = securityService.parseToken(token);

        if (user == null) {
            throw new JwtAuthenticationException("JWT token is not valid");
        }

        return new AuthenticatedUser(user);
    }
}

JwtAuthenticationSuccessHandler.java

@Service
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        // We do not need to do anything extra on REST authentication success, because there is no page to redirect to
    }

}

RestAuthenticationEntryPoint.java

@Service
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        // This is invoked when user tries to access a secured REST resource without supplying any credentials
        // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

Weboscket 配置:

<websocket:message-broker
    application-destination-prefix="/app">
    <websocket:stomp-endpoint path="/add">
        <websocket:sockjs />
    </websocket:stomp-endpoint>
    <websocket:simple-broker prefix="/topic, /queue" />
</websocket:message-broker>

还有我的春季安全

<context:component-scan base-package="com.myapp.ws.security"/>

<sec:global-method-security pre-post-annotations="enabled" />

<!-- everyone can try to login -->
<sec:http pattern="/rest/login/" security="none" />
<!--<sec:http pattern="/rest/add/**" security="none" />-->

<!-- only users with valid JWT can access protected resources -->
<sec:http pattern="/rest/**" entry-point-ref="restAuthenticationEntryPoint" create-session="stateless">
    <!-- JWT is used to disabled-->
    <sec:csrf disabled="true" />
    <!-- don't redirect to UI login form -->
    <sec:custom-filter before="FORM_LOGIN_FILTER" ref="jwtAuthenticationFilter" />
</sec:http>

<bean id="jwtAuthenticationFilter" class="com.myapp.ws.security.JwtAuthenticationFilter">
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="authenticationSuccessHandler" ref="jwtAuthenticationSuccessHandler" />
</bean>

<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider ref="jwtAuthenticationProvider" />
</sec:authentication-manager>

【问题讨论】:

  • 我只是查看错误屏幕,似乎您尝试通过 xdr_streaming 连接,而不是通过 websockets。
  • 但是当 websockets "" 禁用安全性时,一切正常,消息被传递到服务器并稍后发送传递给所有订阅用户。 @user1516873 你可以结帐和构建,它是 maven,将在 5 分钟内立即构建
  • 看起来 websockets 部分在启用 spring 安全性的情况下可以正常工作,至少在我的环境中是这样。检查日志pastebin.com/128L4rkz可能是代理问题或客户端浏览器不支持websockets?
  • @user1516873 如果您在日志中看到最后一行“很好,我收到 ...(截断)”,它应该是“很好,我收到 ...111111111”,因为您发送的消息是 111111111 .浏览器是兼容的,我和你的问题一模一样。当我检查浏览器日志(见附图)时,它说“headers is null”,这告诉我当你成功握手时,Spring security 没有附加一些它应该在响应中的标题。我检查了一些浏览器,并且在禁用安全性时它们都可以工作,而在启用安全性时它们都不能工作。
  • 请在 pom.xml 中将这一行“npm build prod”替换为“”这一行“npm build”,您将看到登录您的网络浏览器

标签: spring angular spring-security websocket spring-websocket


【解决方案1】:

您的问题与安全性无关。您只是在 Stomp 连接和订阅函数中传递了错误的参数。

如果需要,connect() 方法还接受其他两种变体 传递额外的标题:

client.connect(headers, connectCallback);
client.connect(headers, connectCallback, errorCallback);

header 是一个映射,connectCallback 和 errorCallback 是 功能。

this.stompClient.connect(this.token, (frame) => {

应该是

this.stompClient.connect({}, (frame) => {

您可以使用 subscribe() 方法订阅目的地。这 方法接受 2 个强制参数:destination,一个 String 对应目的地和回调,一个函数有一个 message 参数和一个可选参数 headers,一个 JavaScript 对象 额外的标题。

var subscription = client.subscribe("/queue/test", callback);

this.stompClient.subscribe('/topic/addMessage', this.authService.getAuthHeader(), (stompResponse) => {

应该是

this.stompClient.subscribe('/topic/addMessage', (stompResponse) => {

文档http://jmesnil.net/stomp-websocket/doc/

【讨论】:

  • 很遗憾不是这样,请查看 github 上的最新代码,我根据您的建议进行了更改,但没有帮助。如果您能看到我的第二篇文章以及带有请求的 JSON 文件的链接,再次非常感谢
【解决方案2】:

@user1516873 终于搞定了:

  • 将正确的参数传递给 STOMP 解决了一个问题
  • 添加 {transports: ["websocket"]} 没有必要(没有它也可以工作)

问题是我在端口 4200 上使用 angular-cli 服务器,代理文件如下:

{ “/休息”: { "目标": "http://localhost:8080", “安全”:假 } }

但应该是这样的:

{ “/休息”: { "目标": "http://localhost:8080", “安全”:错误, “ws”:是的, “日志级别”:“调试” } }

所以通过所有配置组合,我总是通过 4200 代理服务器检查,很少直接通过本机 8080。我只是不知道应用spring security时angular-cli代理不支持。我会接受你的回答,因为你帮了很多忙!

【讨论】:

  • 我很高兴你成功了。 {transports: ["websocket"]} 实际上 restrict SocketJs 使用 websocket 协议以外的任何协议,因此如果它无法通过 websoket 连接,它会立即失败,并且不会尝试使用不同的协议(如 xhr_streaming)进行故障转移。
  • 这基本上也是很有希望的,所以如果你不需要,建议不要使用{transports: ["websocket"]},因为使用它你会失去对其他协议的故障转移支持
猜你喜欢
  • 1970-01-01
  • 2023-01-03
  • 2016-07-14
  • 2018-09-08
  • 2017-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多