亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

websocket配合spring-security使用token認(rèn)證

CODING / 2442人閱讀

摘要:最后發(fā)現(xiàn)每一個(gè)在連接端點(diǎn)之前都會(huì)發(fā)送一個(gè)請(qǐng)求用于保證該服務(wù)是存在的。而該請(qǐng)求是程序自動(dòng)發(fā)送的自能自動(dòng)攜帶數(shù)據(jù),無(wú)法發(fā)送自定義。問(wèn)題是我們的是使用自定義實(shí)現(xiàn)的認(rèn)證。所以該方法不成立,所以只能讓自己認(rèn)證。

使用框架介紹

spring boot 1.4.3.RELEASE

spring websocket 4.3.5.RELEASE

spring security 4.1.3.RELEASE

sockjs-client 1.0.2

stompjs 2.3.3

項(xiàng)目介紹

由于公司需要使用websocket主動(dòng)給前端用戶(hù)推送消息,公司的項(xiàng)目是使用jhipster自動(dòng)生成的微服務(wù)項(xiàng)目,而spring boot本身就集成了websocket,這樣我們不用自己處理所有的網(wǎng)絡(luò)細(xì)節(jié)代碼。我們的項(xiàng)目主要為:
前端 - nodeJS代理 - 后端 - 計(jì)算系統(tǒng)(由于我們公司是做云計(jì)算的,計(jì)算系統(tǒng)是一個(gè)底層系統(tǒng))
項(xiàng)目的主要流程是:

遇到的問(wèn)題

由于我們使用的是spring security oauth2 來(lái)進(jìn)行認(rèn)證,而且我們需要吧websocket消息推送給指定用戶(hù),這樣為了保證websocket和http協(xié)議使用的同一套認(rèn)證系統(tǒng),我們就必須要把websocket認(rèn)證集成到spring security中。

第一個(gè)問(wèn)題認(rèn)證403錯(cuò)誤

首先貼出websocket的配置代碼

public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    private final static Logger LOG = LoggerFactory.getLogger(WebSocketConfig.class);

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/api/v1/socket/send");  // 推送消息前綴
        registry.setApplicationDestinationPrefixes("/api/v1/socket/req"); // 應(yīng)用請(qǐng)求前綴
        registry.setUserDestinationPrefix("/user");//推送用戶(hù)前綴
    }



    /**
     * 建立連接的端點(diǎn)
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/api/v1/socket/fallback").setAllowedOrigins("*").withSockJS().setInterceptors(httpSessionHandshakeInterceptor());
    }
}

第一次在打開(kāi)websocket的時(shí)候發(fā)現(xiàn)三次握手都沒(méi)有出現(xiàn)就直接報(bào)403,最后仔細(xì)看錯(cuò)誤信息發(fā)現(xiàn)錯(cuò)誤信息的鏈接為:/api/v1/socket/fallback/info,而且該請(qǐng)求使用的是http請(qǐng)求不是websocket請(qǐng)求。最后發(fā)現(xiàn)每一個(gè)websocket在連接端點(diǎn)之前都會(huì)發(fā)送一個(gè)http GET請(qǐng)求用于保證該服務(wù)是存在的。而該請(qǐng)求是程序自動(dòng)發(fā)送的自能自動(dòng)攜帶cookie數(shù)據(jù),無(wú)法發(fā)送自定義header。

spring boot自帶的認(rèn)證是:如果/api/v1/socket/fallback/info該請(qǐng)求通過(guò)認(rèn)證,那么websocket的所有請(qǐng)求以及發(fā)送全部自動(dòng)綁定該認(rèn)證用戶(hù)。如果我們想辦法讓/api/v1/socket/fallback/info請(qǐng)求通過(guò)認(rèn)證,那么接下來(lái)所有的問(wèn)題都將解決。問(wèn)題是我們的token是使用自定義header實(shí)現(xiàn)的認(rèn)證。所以該方法不成立,所以只能讓websocket自己認(rèn)證。

為了解決/api/v1/socket/fallback/info請(qǐng)求的403問(wèn)題我在安全配置中加入.authorizeRequests().antMatchers("/api/v1/socket/fallback/**").permitAll()這樣第一步判斷服務(wù)是否存在就解決了,這里離解決websocket的認(rèn)證問(wèn)題只是第一步。

第二個(gè)問(wèn)題:如果發(fā)送token給后端

stomp 客戶(hù)端可以直接在websocket請(qǐng)求中加入自定義header,如下:

let socket = new SockJS("/api/v1/socket/fallback")
let stompClient = Stomp.over(socket)
let token = localStorage.getItem("Auth-Token") // eslint-disable-line
stompClient.connect({"Auth-Token": token}, frame => {
  stompClient.subscribe("/user/api/v1/socket/send/greetings", data => {
    // TODO
  })
})
第三個(gè)問(wèn)題:后端如何認(rèn)證

我們?cè)趧?chuàng)建連接的時(shí)候前端需要將token發(fā)送到后端,現(xiàn)在我們已經(jīng)將token發(fā)送到后端了,但是后端如何接受并處理token得到認(rèn)證數(shù)據(jù)呢?帶著這個(gè)問(wèn)題開(kāi)始google吧!http://stackoverflow.com/questions/39422053/spring-4-x-token-based-websocket-sockjs-fallback-authentication這個(gè)鏈接正好解決了我的問(wèn)題,

UPDATE 2016-12-13 : the issue referenced below is now marked fixed, so the hack below is no longer necessary which Spring 4.3.5 or above. See https://github.com/spring-projects/spring-framework/blob/master/src/asciidoc/web-websocket.adoc#token-based-authentication.

原來(lái)這個(gè)問(wèn)題在4.3.5版本中已經(jīng)被繼承進(jìn)去了,查看自己的版本是4.3.4,不解釋直接升級(jí)版本到4.3.5,然后加如代碼

@EnableWebSocketMessageBroker
public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer {

  @Override
  public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.setInterceptors(new ChannelInterceptorAdapter() {

        @Override
        public Message preSend(Message message, MessageChannel channel) {

            StompHeaderAccessor accessor =
                MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

            if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                String jwtToken = accessor.getFirstNativeHeader("Auth-Token");
                    if (StringUtils.isNotEmpty(jwtToken)) {
                        UserAuthenticationToken authToken = tokenService.retrieveUserAuthToken(jwtToken);
                        SecurityContextHolder.getContext().setAuthentication(authToken);
                        accessor.setUser(authToken);
                    }
            }

            return message;
        }
    });
  }
}

開(kāi)始測(cè)試,發(fā)現(xiàn)還是報(bào)錯(cuò)MissingCsrfTokenException,然后開(kāi)始debug代碼,發(fā)現(xiàn)代碼錯(cuò)誤的代碼為:

public final class CsrfChannelInterceptor extends ChannelInterceptorAdapter {
    private final MessageMatcher matcher;

    public CsrfChannelInterceptor() {
        this.matcher = new SimpMessageTypeMatcher(SimpMessageType.CONNECT);
    }

    public Message preSend(Message message, MessageChannel channel) {
        if(!this.matcher.matches(message)) {
            return message;
        } else {
            Map sessionAttributes = SimpMessageHeaderAccessor.getSessionAttributes(message.getHeaders());
            CsrfToken expectedToken = sessionAttributes == null?null:(CsrfToken)sessionAttributes.get(CsrfToken.class.getName());
            if(expectedToken == null) {  // 在這里為null
                throw new MissingCsrfTokenException((String)null);  //報(bào)錯(cuò)
            } else {
                String actualTokenValue = SimpMessageHeaderAccessor.wrap(message).getFirstNativeHeader(expectedToken.getHeaderName());
                boolean csrfCheckPassed = expectedToken.getToken().equals(actualTokenValue);
                if(csrfCheckPassed) {
                    return message;
                } else {
                    throw new InvalidCsrfTokenException(expectedToken, actualTokenValue);
                }
            }
        }
    }
}

仔細(xì)查看里面的數(shù)據(jù),原來(lái)這里是需要在header中存放一些數(shù)據(jù),于是乎將configureClientInboundChannel方法修正為:

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.setInterceptors(new ChannelInterceptorAdapter() {
            @Override
            public Message preSend(Message message, MessageChannel channel) {
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    String jwtToken = accessor.getFirstNativeHeader("Auth-Token");
                    LOG.debug("webSocket token is {}", jwtToken);
                    if (StringUtils.isNotEmpty(jwtToken)) {
                        Map sessionAttributes = SimpMessageHeaderAccessor.getSessionAttributes(message.getHeaders());
                        sessionAttributes.put(CsrfToken.class.getName(), new DefaultCsrfToken("Auth-Token", "Auth-Token", jwtToken));
                        UserAuthenticationToken authToken = tokenService.retrieveUserAuthToken(jwtToken);
                        SecurityContextHolder.getContext().setAuthentication(authToken);
                        accessor.setUser(authToken);
                    }
                }
                return message;
            }
        });
    }

然后修改websocket安全配置為:

@Configuration
public class WebsocketSecurityConfiguration extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages.anyMessage().permitAll();
    }

    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}

這樣websocket 集成spring boot token的認(rèn)證就搞定了。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/66357.html

相關(guān)文章

  • 基于spring-security-oauth2實(shí)現(xiàn)單點(diǎn)登錄(持續(xù)更新)

    摘要:認(rèn)證服務(wù)器和瀏覽器控制臺(tái)也沒(méi)有報(bào)錯(cuò)信息。這里簡(jiǎn)單介紹下如何查閱源碼,首先全局搜索自己的配置因?yàn)檫@個(gè)地址是認(rèn)證服務(wù)器請(qǐng)求授權(quán)的,所以,請(qǐng)求認(rèn)證的過(guò)濾器肯定包含他。未完待續(xù),下一篇介紹資源服務(wù)器和認(rèn)證服務(wù)器的集成。 基于spring-security-oauth2-實(shí)現(xiàn)單點(diǎn)登錄 文章代碼地址:鏈接描述可以下載直接運(yùn)行,基于springboot2.1.5,springcloud Green...

    妤鋒シ 評(píng)論0 收藏0
  • 前后端分離項(xiàng)目 — 基于SpringSecurity OAuth2.0用戶(hù)認(rèn)證

    摘要:前言現(xiàn)在的好多項(xiàng)目都是基于移動(dòng)端以及前后端分離的項(xiàng)目,之前基于的前后端放到一起的項(xiàng)目已經(jīng)慢慢失寵并淡出我們視線,尤其是當(dāng)基于的微服務(wù)架構(gòu)以及單頁(yè)面應(yīng)用流行起來(lái)后,情況更甚。使用生成是什么請(qǐng)自行百度。 1、前言 現(xiàn)在的好多項(xiàng)目都是基于APP移動(dòng)端以及前后端分離的項(xiàng)目,之前基于Session的前后端放到一起的項(xiàng)目已經(jīng)慢慢失寵并淡出我們視線,尤其是當(dāng)基于SpringCloud的微服務(wù)架構(gòu)以及...

    QLQ 評(píng)論0 收藏0
  • [譯] Elixir、Phoenix、Absinthe、GraphQL、React 和 Apollo

    摘要:對(duì)于每個(gè)案例,我們插入所需要的測(cè)試數(shù)據(jù),調(diào)用需要測(cè)試的函數(shù)并對(duì)結(jié)果作出斷言。我們將這個(gè)套接字和用戶(hù)返回以供我們其他的測(cè)試使用。 原文地址:Elixir, Phoenix, Absinthe, GraphQL, React, and Apollo: an absurdly deep dive - Part 2 原文作者:Zach Schneider 譯文出自:掘金翻譯計(jì)劃 本文永久鏈接:gi...

    Cympros 評(píng)論0 收藏0
  • spring security登錄、登出、認(rèn)證異常返回值的自定義實(shí)現(xiàn)

    摘要:在整個(gè)學(xué)習(xí)過(guò)程中,我最關(guān)心的內(nèi)容有號(hào)幾點(diǎn),其中一點(diǎn)是前后端分離的情況下如何不跳轉(zhuǎn)頁(yè)面而是返回需要的返回值。登錄成功,不跳轉(zhuǎn)頁(yè)面,返回自定義返回值在官方文檔第節(jié),有這么一段描述要進(jìn)一步控制目標(biāo),可以使用屬性作為的替代。 在整個(gè)學(xué)習(xí)過(guò)程中,我最關(guān)心的內(nèi)容有號(hào)幾點(diǎn),其中一點(diǎn)是【前后端分離的情況下如何不跳轉(zhuǎn)頁(yè)面而是返回需要的返回值】。下面就說(shuō)一下學(xué)習(xí)結(jié)果,以xml配置位李。 登錄成功,不跳轉(zhuǎn)頁(yè)...

    mushang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<