Spring中Websocket身份驗(yàn)證和授權(quán)的實(shí)現(xiàn)
一、需要了解的事項(xiàng)
- http和WebSocket的安全鏈和安全配置是完全獨(dú)立的。
- SpringAuthenticationProvider根本不參與 Websocket 身份驗(yàn)證。
- 將要給出的示例中,身份驗(yàn)證不會(huì)發(fā)生在 HTTP 協(xié)商端點(diǎn)上,因?yàn)?JavaScript STOMP(websocket)庫(kù)不會(huì)隨 HTTP 請(qǐng)求一起發(fā)送必要的身份驗(yàn)證標(biāo)頭。
- 一旦在 CONNECT 請(qǐng)求上設(shè)置,用戶(hù)( simpUser) 將被存儲(chǔ)在 websocket 會(huì)話(huà)中,并且以后的消息將不再需要進(jìn)行身份驗(yàn)證。
二、依賴(lài)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-messaging</artifactId> </dependency>
三、WebSocket 配置
3.1 、簡(jiǎn)單的消息代理
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(final MessageBrokerRegistry config) { config.enableSimpleBroker("/queue/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { registry.addEndpoint("stomp"); setAllowedOrigins("*") } }
3.2 、Spring安全配置
由于 Stomp 協(xié)議依賴(lài)于第一個(gè) HTTP 請(qǐng)求,因此需要授權(quán)對(duì) stomp 握手端點(diǎn)的 HTTP 調(diào)用。
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(final HttpSecurity http) throws Exception http.httpBasic().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests().antMatchers("/stomp").permitAll() .anyRequest().denyAll(); } }
然后創(chuàng)建一個(gè)負(fù)責(zé)驗(yàn)證用戶(hù)身份的服務(wù)。
@Component public class WebSocketAuthenticatorService { public UsernamePasswordAuthenticationToken getAuthenticatedOrFail(final String username, final String password) throws AuthenticationException { if (username == null || username.trim().isEmpty()) { throw new AuthenticationCredentialsNotFoundException("Username was null or empty."); } if (password == null || password.trim().isEmpty()) { throw new AuthenticationCredentialsNotFoundException("Password was null or empty."); } if (fetchUserFromDb(username, password) == null) { throw new BadCredentialsException("Bad credentials for user " + username); } return new UsernamePasswordAuthenticationToken( username, null, Collections.singleton((GrantedAuthority) () -> "USER") // 必須給至少一個(gè)角色 ); } }
接著需要?jiǎng)?chuàng)建一個(gè)攔截器,它將設(shè)置“simpUser”標(biāo)頭或在 CONNECT 消息上拋出“AuthenticationException”。
@Component public class AuthChannelInterceptorAdapter extends ChannelInterceptor { private static final String USERNAME_HEADER = "login"; private static final String PASSWORD_HEADER = "passcode"; private final WebSocketAuthenticatorService webSocketAuthenticatorService; @Inject public AuthChannelInterceptorAdapter(final WebSocketAuthenticatorService webSocketAuthenticatorService) { this.webSocketAuthenticatorService = webSocketAuthenticatorService; } @Override public Message<?> preSend(final Message<?> message, final MessageChannel channel) throws AuthenticationException { final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT == accessor.getCommand()) { final String username = accessor.getFirstNativeHeader(USERNAME_HEADER); final String password = accessor.getFirstNativeHeader(PASSWORD_HEADER); final UsernamePasswordAuthenticationToken user = webSocketAuthenticatorService.getAuthenticatedOrFail(username, password); accessor.setUser(user); } return message; } }
請(qǐng)注意:preSend() 必須返回 UsernamePasswordAuthenticationToken,Spring 安全鏈中會(huì)對(duì)此進(jìn)行測(cè)試。如果UsernamePasswordAuthenticationToken構(gòu)建沒(méi)有通過(guò)GrantedAuthority,則身份驗(yàn)證將失敗,因?yàn)闆](méi)有授予權(quán)限的構(gòu)造函數(shù)自動(dòng)設(shè)置authenticated = false 這是一個(gè)重要的細(xì)節(jié),在 spring-security 中沒(méi)有記錄。
最后再創(chuàng)建兩個(gè)類(lèi)來(lái)分別處理授權(quán)和身份驗(yàn)證。
@Configuration @Order(Ordered.HIGHEST_PRECEDENCE + 99) public class WebSocketAuthenticationSecurityConfig extends WebSocketMessageBrokerConfigurer { @Inject private AuthChannelInterceptorAdapter authChannelInterceptorAdapter; @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { // 這里不用給任何東西 } @Override public void configureClientInboundChannel(final ChannelRegistration registration) { registration.setInterceptors(authChannelInterceptorAdapter); } }
請(qǐng)注意:這@Order是至關(guān)重要的,它允許我們的攔截器首先在安全鏈中注冊(cè)。
@Configuration public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { @Override protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) { // 添加自己的映射 messages.anyMessage().authenticated(); } // 這里請(qǐng)自己按需求修改 @Override protected boolean sameOriginDisabled() { return true; } }
之后編寫(xiě)客戶(hù)端進(jìn)行連接,我們就可以這樣指定客戶(hù)端進(jìn)行消息的發(fā)送。
@MessageMapping("/greeting") public void greetingReturn(@Payload Object ojd){ simpMessagingTemplate.convertAndSendToUser(username,"/topic/greeting",ojd); }
到此這篇關(guān)于Spring中Websocket身份驗(yàn)證和授權(quán)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Spring Websocket身份驗(yàn)證和授權(quán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring WebSocket 404錯(cuò)誤的解決方法
- 完美解決spring websocket自動(dòng)斷開(kāi)連接再創(chuàng)建引發(fā)的問(wèn)題
- SpringBoot集成WebSocket實(shí)現(xiàn)前后端消息互傳的方法
- SpringBoot+WebSocket+Netty實(shí)現(xiàn)消息推送的示例代碼
- SpringBoot+Websocket實(shí)現(xiàn)一個(gè)簡(jiǎn)單的網(wǎng)頁(yè)聊天功能代碼
- springboot websocket簡(jiǎn)單入門(mén)示例
- SpringMVC整合websocket實(shí)現(xiàn)消息推送及觸發(fā)功能
- Spring集成webSocket頁(yè)面訪(fǎng)問(wèn)404問(wèn)題的解決方法
- Spring Boot 開(kāi)發(fā)私有即時(shí)通信系統(tǒng)(WebSocket)
- Spring Cloud微服務(wù)使用webSocket的方法
- java中實(shí)現(xiàn)兼容ie6 7 8 9的spring4+websocket
相關(guān)文章
Springboot+Shiro+Mybatis+mysql實(shí)現(xiàn)權(quán)限安全認(rèn)證的示例代碼
Shiro是Apache?的一個(gè)強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證、授權(quán)、密碼學(xué)和會(huì)話(huà)管理,Shiro?主要分為兩個(gè)部分就是認(rèn)證和授權(quán)兩部分,這篇文章主要介紹了Springboot+Shiro+Mybatis+mysql實(shí)現(xiàn)權(quán)限安全認(rèn)證的示例代碼,需要的朋友可以參考下2024-07-07SpringBoot項(xiàng)目的漏洞修復(fù)經(jīng)驗(yàn)分享
在局域網(wǎng)環(huán)境下,由于無(wú)法連接外網(wǎng)下載Maven包,常見(jiàn)解決方案是在外網(wǎng)環(huán)境搭建相同的開(kāi)發(fā)環(huán)境以便更新Maven包,本次漏洞掃描包括Tomcat、jackson-databind、fastjson、logback等組件,通常解決方法是升級(jí)到更高版本2024-10-10java算法題解LeetCode30包含min函數(shù)的棧實(shí)例
這篇文章主要為大家介紹了java算法題解LeetCode30包含min函數(shù)的棧實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Spring Boot命令行運(yùn)行器的實(shí)現(xiàn)方法
這篇文章主要介紹了Spring Boot命令行運(yùn)行器的實(shí)現(xiàn)方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-10-10Java基礎(chǔ)學(xué)習(xí)之Swing事件監(jiān)聽(tīng)
今天學(xué)習(xí)java的Swing庫(kù),創(chuàng)建桌面應(yīng)用的時(shí)候,突然發(fā)現(xiàn)有些按鈕需要特定的功能響應(yīng),故來(lái)研究一番Swing的事件監(jiān)聽(tīng),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-05-05java實(shí)現(xiàn)學(xué)生成績(jī)錄入系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生成績(jī)錄入系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01Maven中plugins與pluginManagement的區(qū)別說(shuō)明
這篇文章主要介紹了Maven中plugins與pluginManagement的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09java固定大小隊(duì)列的幾種實(shí)現(xiàn)方式詳解
隊(duì)列的特點(diǎn)是節(jié)點(diǎn)的排隊(duì)次序和出隊(duì)次序按入隊(duì)時(shí)間先后確定,即先入隊(duì)者先出隊(duì),后入隊(duì)者后出隊(duì),這篇文章主要給大家介紹了關(guān)于java固定大小隊(duì)列的幾種實(shí)現(xiàn)方式,需要的朋友可以參考下2021-07-07