春季安全块websocket(sockjs) [英] spring-security block websocket (sockjs)
问题描述
在我的一个项目中,我配置了rest服务和websocket,都通过了spring安全过滤器来检查JWT.对于客户端上的Websocket,应用程序使用sockjs&服务器端(Tomcat 8)上的stomp(在Angular2上)和Spring websockets.当我在启用Spring安全性的情况下打开连接时,打开连接两秒钟后,我得到以下错误.但是,当我在没有启用Spring Security的情况下打开连接时,连接不会丢失.
In one of my projects I configured both rest services and websockets, both go through spring security filter that check for JWT. For websockets on the client side, application uses sockjs & stomp (on Angular2) and Spring websockets on the server side (Tomcat 8). When I open connection with Spring security enabled then I get below error two seconds after it gets opened. However when I open connection without spring security enabled connection does not get dropped.
angular2 connect()/subscribe()/send()-全部与JWT令牌一起使用
angular2 connect()/subscribe()/send() - all go with JWT token
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>
推荐答案
@ user1516873终于使它起作用了:
@user1516873 finally I got it working:
- 将正确的参数传递给STOMP解决了一个问题
- 没有必要添加 {transports:["websocket"]} (没有它就可以使用)
- passing correct parameters to STOMP fixed one problem
- adding {transports: ["websocket"]} was not necessary (it works without it)
问题是我在端口4200上使用angular-cli服务器,并带有如下所示的代理文件:
Problem was that I was using angular-cli server on port 4200 with proxy file like this:
{ /休息": { "target":" http://localhost:8080 ", 安全":错误 } }
{ "/rest": { "target": "http://localhost:8080", "secure": false } }
但应该是这样的:
{ /休息": { "target":" http://localhost:8080 ", 安全":错误, "ws":是的, "logLevel":调试" } }
{ "/rest": { "target": "http://localhost:8080", "secure": false, "ws": true, "logLevel": "debug" } }
因此,通过所有配置组合,我总是通过4200代理服务器进行检查,而很少通过本地8080直接进行检查.我只是不知道在应用spring安全性时,angular-cli代理不支持.我会接受您的回答,因为您提供了很多帮助!
so through all the combinations of configuration I was always checking through 4200 proxy server and very rarely through native 8080 directly. I just didn't know that angular-cli proxy does not support when spring security is applied. I will accept your answer as you helped a lot!
这篇关于春季安全块websocket(sockjs)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!