I'm using Guacamole 0.9.12-incubating - on server-side extending GuacamoleHTTPTunnelServlet, on client using official JavaScript code. Guacamole server is compiled from source and is running on Ubuntu 17.04.
SSH connection is successfully established, but disconnects after 15 seconds. No keyboard strokes nor mouse are working.
May 7 17:14:09 dev guacd[4071]: Creating new client for protocol "ssh"
May 7 17:14:09 dev guacd[4071]: Connection ID is "$30e2e833-5640-4bc9-92c3-929ced3d6e0e"
May 7 17:14:09 dev guacd[4382]: User "#4209df46-e26a-4ced-93c4-c264578f85a5" joined connection "$30e2e833-5640-4bc9-92c3-929ced3d6e0e" (1 users now present)
May 7 17:14:10 dev guacd[4382]: SSH connection successful.
May 7 17:14:24 dev guacd[4382]: User is not responding.
May 7 17:14:24 dev guacd[4382]: User "#4209df46-e26a-4ced-93c4-c264578f85a5" disconnected (0 users remain)
May 7 17:14:24 dev guacd[4382]: Last user of connection "$30e2e833-5640-4bc9-92c3-929ced3d6e0e" disconnected
May 7 17:14:25 dev guacd[4382]: SSH connection ended.
May 7 17:14:25 dev guacd[4071]: Connection "$30e2e833-5640-4bc9-92c3-929ced3d6e0e" removed.
Client JavaScript is the same as in documentation - https://guacamole.incubator.apache.org/doc/gug/writing-you-own-guacamole-app.html .
When I Override methods in servlet, they show me that key strokes go to it. So problem is probably somewhere between servlet and guacd?
#Override
protected void doWrite(HttpServletRequest request, HttpServletResponse response, String tunnelUUID) throws GuacamoleException {
LOGGER.debug("Do WRITE to session " + tunnelUUID);
super.doWrite(request, response, tunnelUUID);
}
#Override
protected void doRead(HttpServletRequest request, HttpServletResponse response, String tunnelUUID) throws GuacamoleException {
LOGGER.debug("Do read to session " + tunnelUUID);
super.doRead(request, response, tunnelUUID);
}
Connection is established, but no key strokes are working:
Thanks.
Problem was in Spring Boot, as discussed there - https://glyptodon.org/jira/si/jira.issueviews:issue-html/GUAC-1252/GUAC-1252.html .
Solution is to create own implementation of HiddenHttpMethodFilter:
#Configuration
public class GuacamoleServletConfiguration {
#Bean
public GuacamoleServlet guacamoleTunnelServlet() {
return new GuacamoleServlet();
}
#Bean
public ServletRegistrationBean servletRegistrationBean() {
ServletRegistrationBean bean = new ServletRegistrationBean(guacamoleTunnelServlet(), "/remote/tunnel");
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
#Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new HiddenHttpMethodFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if ("POST".equals(request.getMethod()) && request.getRequestURI().startsWith("/remote/tunnel")) {
filterChain.doFilter(request, response);
} else {
super.doFilterInternal(request, response, filterChain);
}
}
};
}
}
Related
I am building a microservice using Spring Cloud Gateway and OAuth2 Resource Server. The app aims at redirecting to other microservices after doing the security part. I am trying to setup a filter before AnonymousAuthenticationFilter and handle my custom exception from there but however the custom exception filter is never being invoked. Following the security config I have in the app:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().disable()
.httpBasic().disable()
.formLogin().disable()
.addFilterBefore(customExceptionHandler, AnonymousAuthenticationFilter.class)
.authorizeRequests( auth -> auth.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers("/**").authenticated())
.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt())
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
}
In my customExceptionHandler, I have the following code:
public class CustomExceptionHandler extends OncePerRequestFilter {
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
log.error("Spring Security Filter Chain Exception:", e);
resolver.resolveException(request, response, null, e);
}
}
}
Also following is my build.gradle:
// Spring Boot
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// Spring Cloud
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
I also have an #ExceptionAdvice class that handles all the exceptions. However, if I pass in an expired JWT or any other error scenario to the service, I always get handled by the following error message in my WWW-Authenticate header:
Bearer error="invalid_token", error_description="Jwt expired at 2022-06-16T19:58:09Z", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
How do I throw a custom POJO instead of this message?
This error is coming from BearerTokenAuthenticationEntryPoint, so to override the behavior you can just easily provide a custom entryPoint
.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt().and().authenticationEntryPoint(myCustomEntryPoint))
I am trying to authorize all preflight request in (/secure/**) without an authorization header(oauth token in my case). The JwkFilter is used to validate the oauth token passed in the authorization header. Any suggestion, where I am going wrong here.
#Override
protected void configure(HttpSecurity http) throws Exception {
JwtAuthFilter jwtAuthTokenFilter = new JwtAuthFilter(oauthConfig);
jwtAuthTokenFilter.setAuthenticationManager(getAuthManager());
http.cors().and().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/secure/**")
.permitAll();
http.requiresChannel().anyRequest().requiresSecure().and()
.addFilterBefore(requireProtocolFilter, ChannelProcessingFilter.class).sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().anonymous().disable().csrf().disable()
.antMatcher("/**").authorizeRequests().anyRequest().permitAll().and()
.antMatcher(/secure/**")
.addFilterBefore(jwtAuthTokenFilter, BasicAuthenticationFilter.class).exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint()).and().authorizeRequests().anyRequest()
.authenticated();
}
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")
.allowedOrigins("*");
}
};
}
For preflight request with CORS, according to spring, they will execute before your jwtAuthTokenFilter (registered before BasicAuthenticationFilter filter) -> correct
The order was specified here (in spring code):
FilterComparator() {
Step order = new Step(INITIAL_ORDER, ORDER_STEP);
...
put(CorsFilter.class, order.next());
...
put(BasicAuthenticationFilter.class, order.next());
...
}
In CORS, for complex request (like using custom header Authorization header in your case), browser will send preflight request first to know whether the server allow client to access their resource or not before sending actual request.
The CORSFilter will execute like this (in spring code):
public class CorsFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
if (!isValid || CorsUtils.isPreFlightRequest(request)) {
return;
}
filterChain.doFilter(request, response);
}
}
They will check whether for every preflight request (extends OncePerRequestFilter) comes to server, if processRequest is valid or is preflight request to terminate the chain.
Here is the default processor to check preflight request (in spring code):
public class DefaultCorsProcessor implements CorsProcessor {
#Override
public boolean processRequest(#Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {
...
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
if (config == null) {
if (preFlightRequest) {
rejectRequest(new ServletServerHttpResponse(response));
return false;
}
else {
return true;
}
}
return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
}
In your case, I think you are missing configuring for enabling CORS.
So the server reject the client request (by sending HttpStatus.FORBIDDEN code), so that the browser don't send actual request to the server.
And your JwtAuthTokenFilter has no chance to execute.
You can refer to this post for configuring cors. Hope it helps
Adding the below snippet in to the jwkAuthFilter did the trick.
if (CorsUtils.isPreFlightRequest(request)) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
Use case: As a user of the Spring AMQP client connecting to RabbitMQ brokers over TLS, I want to verify that the hostname(s) either in the X.509 certificate Common Name field or in one of the Subject Alternative Names in the certificate X.509 extensions matches the hostname I used to connect to the broker.
One possible solution: The Spring Rabbit connection factory bean org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean has a setSocketConfigurator(com.rabbitmq.client.SocketConfigurator) method on it that can be used configure the SSLSocket with a javax.net.ssl.HandshakeCompletedListener as follows in this simple SocketConfigurator
static class MySocketConfigurator implements SocketConfigurator {
private final String[] validHostnames;
public MySocketConfigurator(String[] validHostnames) {
this.validHostnames = validHostnames;
}
#Override
public void configure(final Socket socket) throws IOException {
if (socket instanceof SSLSocket) {
SSLSocket sslSocket = (SSLSocket) socket;
sslSocket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
#Override
public void handshakeCompleted(final HandshakeCompletedEvent event) {
try {
if (event.getPeerCertificates()[0] instanceof X509Certificate) {
X509Certificate x509Certificate = (X509Certificate) event.getPeerCertificates()[0];
boolean verified = verifyHost(validHostnames, x509Certificate);
if (!verified) {
event.getSocket().close();
}
} else {
event.getSocket().close();
}
} catch (SSLPeerUnverifiedException e) {
throw new RuntimeException(e);
} catch (CertificateParsingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
// verify that one of the validHostname items matches a host found in the broker certificate
boolean verifyHost(String[] validHostnames, X509Certificate serverCertificate) throws CertificateParsingException {
...
}
}
Closing the socket referenced in event.getSocket().close() seems a bit heavy-handed, but does suffice to shut the connection down if the application deems the hostnames in the certificate are not a close enough match. I had originally conceived to throw a RuntimeException upon determining that the hostnames did not match, but it appears the Spring stack swallows those and does not induce the desired application-induced connection setup failure.
Is the SocketConfigurator approach shown above, with its direct call to socket.close(), the recommended way to fail TLS connection setup if the certificate hostnames are deemed an insufficient match?
I am not sure what you mean by "spring stack swallows the exception"; doesn't sound right; if you can point me to the code that does that I can take a look.
The spring connection factory just delegates to the rabbit connection factory.
I don't know the answer to your basic question about best practices; you might want to ping the rabbit guys on the rabbitmq-users google group.
Referring to the logout flow in oauth2 spring-guides project, once the the user has authenticated using user/password for the first time, the credentials are not asked next time after logout.
How can I ensure that username/password are asked every time after a logout.
This is what I am trying to implement:-
OAuth2 server issuing JWT token using "authorization_code" grant type
with auto approval. This has html/angularjs form to collect
username/password.
UI/Webfront - Uses #EnableSSO. ALL its endpoints are authenticated
i.e it does not have any unauthorized landing page/ui/link that user
clicks to go to /uaa server. So hitting http://localhost:8080
instantly redirects you to http://localhost:9999/uaa and presents
custom form to collect username/password.
Resource server - Uses #EnableResourceServer. Plain & simple REST api.
With the above approach I am not able to workout the logout flow. HTTP POST /logout to the UI application clears the session/auth in UI application but the users gets logged in again automatically ( as I have opted for auto approval for all scopes) without being asked for username password again.
Looking at logs and networks calls, it looks like that all the "oauth dance" happens all over again successfully without user being asked for username/password again and seems like the auth server remembers last auth token issued for a client ( using org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices? ).
How can I tell auth server to ask for username/password every time it is requested for code/token - stateless.
Or what is the best way to implement logout in my given scenario.
( To recreate somewhat near to my requirements, remove permitAll() part from the UiApplication and configure autoApproval in auth server of the mentioned boot project.)
github issue
I also faced the error as you described and I saw a solution from question
Spring Boot OAuth2 Single Sign Off. I don't mean this is the only and global truth solution.
But in the scenario,
authentication server has login form and you'd authenticated from it
browser still maintain the session with authentication server
after you have finished logout process (revoke tokens,remove cookies...)
and try to re-login again
authentication server do not send login form and automatically sign in
You need to remove authentication informations from authentication server's session as this answer described.
Below snippets are how did I configure for solution
Client (UI Application in your case) application's WebSecurityConfig
...
#Value("${auth-server}/ssoLogout")
private String logoutUrl;
#Autowired
private CustomLogoutHandler logoutHandler;
...
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl(logoutUrl)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.addLogoutHandler(logoutHandler)
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
// #formatter:on
}
Custom logout handler for client application
#Component
public class CustomLogoutHandler implements LogoutHandler {
private static Logger logger = Logger.getLogger(CustomLogoutHandler.class);
#Value("${auth-server}/invalidateTokens")
private String logoutUrl;
#Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
logger.debug("Excution CustomLogoutHandler for " + authentication.getName());
Object details = authentication.getDetails();
if (details.getClass().isAssignableFrom(OAuth2AuthenticationDetails.class)) {
String accessToken = ((OAuth2AuthenticationDetails) details).getTokenValue();
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("access_token", accessToken);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "bearer " + accessToken);
HttpEntity<Object> entity = new HttpEntity<>(params, headers);
HttpMessageConverter<?> formHttpMessageConverter = new FormHttpMessageConverter();
HttpMessageConverter<?> stringHttpMessageConverternew = new StringHttpMessageConverter();
restTemplate.setMessageConverters(Arrays.asList(new HttpMessageConverter[] { formHttpMessageConverter, stringHttpMessageConverternew }));
try {
ResponseEntity<String> serverResponse = restTemplate.exchange(logoutUrl, HttpMethod.POST, entity, String.class);
logger.debug("Server Response : ==> " + serverResponse);
} catch (HttpClientErrorException e) {
logger.error("HttpClientErrorException invalidating token with SSO authorization server. response.status code: " + e.getStatusCode() + ", server URL: " + logoutUrl);
}
}
authentication.setAuthenticated(false);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
new SecurityContextLogoutHandler().logout(request, response, auth);
}
}
I used JDBC tokenStore, so I need to revoke tokens.At the authentication server side, I added a controller to handle logout processes
#Controller
public class AuthenticationController {
private static Logger logger = Logger.getLogger(AuthenticationController.class);
#Resource(name = "tokenStore")
private TokenStore tokenStore;
#Resource(name = "approvalStore")
private ApprovalStore approvalStore;
#RequestMapping(value = "/invalidateTokens", method = RequestMethod.POST)
public #ResponseBody Map<String, String> revokeAccessToken(HttpServletRequest request, HttpServletResponse response, #RequestParam(name = "access_token") String accessToken, Authentication authentication) {
if (authentication instanceof OAuth2Authentication) {
logger.info("Revoking Approvals ==> " + accessToken);
OAuth2Authentication auth = (OAuth2Authentication) authentication;
String clientId = auth.getOAuth2Request().getClientId();
Authentication user = auth.getUserAuthentication();
if (user != null) {
Collection<Approval> approvals = new ArrayList<Approval>();
for (String scope : auth.getOAuth2Request().getScope()) {
approvals.add(new Approval(user.getName(), clientId, scope, new Date(), ApprovalStatus.APPROVED));
}
approvalStore.revokeApprovals(approvals);
}
}
logger.info("Invalidating access token :- " + accessToken);
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(accessToken);
if (oAuth2AccessToken != null) {
if (tokenStore instanceof JdbcTokenStore) {
logger.info("Invalidating Refresh Token :- " + oAuth2AccessToken.getRefreshToken().getValue());
((JdbcTokenStore) tokenStore).removeRefreshToken(oAuth2AccessToken.getRefreshToken());
tokenStore.removeAccessToken(oAuth2AccessToken);
}
}
Map<String, String> ret = new HashMap<>();
ret.put("removed_access_token", accessToken);
return ret;
}
#GetMapping("/ssoLogout")
public void exit(HttpServletRequest request, HttpServletResponse response) throws IOException {
new SecurityContextLogoutHandler().logout(request, null, null);
// my authorization server's login form can save with remember-me cookie
Cookie cookie = new Cookie("my_rememberme_cookie", null);
cookie.setMaxAge(0);
cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
response.addCookie(cookie);
response.sendRedirect(request.getHeader("referer"));
}
}
At authorization server's SecurityConfig, you may need to allow this url as
http
.requestMatchers()
.antMatchers(
"/login"
,"/ssoLogout"
,"/oauth/authorize"
,"/oauth/confirm_access");
I hope this may help a little for you.
As you are using JWT tokens, you can not really revoke them.
As a workaround, you can have a logout rest endpoint that would store the timestamp and userid for logout call.
Later, you can compare the logout time with JWT token issue time, and decide wether to allow an api call or not.
I have realized that redirecting to a controller when you logout from your client app and then programmatically logout on your authserver does the trick. This is my configuration on the client app:
#Configuration
#EnableOAuth2Sso
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${auth-server}/exit")
private String logoutUrl;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.logoutSuccessUrl(logoutUrl)
.and().authorizeRequests().anyRequest().authenticated();
}
}
and this is my configuration on my authserver (is just a controller handling the /exit endpoint):
#Controller
public class LogoutController {
public LogoutController() {
}
#RequestMapping({"/exit"})
public void exit(HttpServletRequest request, HttpServletResponse response) {
(new SecurityContextLogoutHandler()).logout(request, null, null);
try {
response.sendRedirect(request.getHeader("referer"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Here is a sample app that shows the full implementation using JWT. Check it out and let us know if it helps you.
I am creating a websocket server that interfaces with a web service endpoint on one side and another which receives web socket connection requests from multiple clients. Here are two approaches that I found:
Implement a web socket configurer and web socket handler as such:
Configurer
#Configuration
#EnableWebSocket
public class TestConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(testHandler(), "/testHandler")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.withSockJS();
}
#Bean
public WebSocketHandler testHandler() {
return new TestHandler();
}
Handler
public class TestHandler extends TextWebSocketHandler {
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
//Take request params and check if a current subscription to external webservice exists, if yes then directly add this session to a map cache repository with the subscription id as key
//If it is a new request then add session to a map cache repository and make new subscription to the external webservice
}
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
}
Configure a message broker endpoint to be subscribed to called /subscribe
public class TestWebSocketConfig implement WebSocketMessageBrokerConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> arg0) {}
#Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> arg0) {}
#Override
public void configureClientInboundChannel(ChannelRegistration arg0) {
System.out.println("");
}
#Override
public void configureClientOutboundChannel(ChannelRegistration arg0) {
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
}
#Override
public boolean configureMessageConverters(List<MessageConverter> arg0) {
return true;
}
#Override
public void configureWebSocketTransport(WebSocketTransportRegistration arg0) {}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/subscribe").withSockJS();
}
Create controller where websocket clients can communicate with
#Controller
public class SubscriptionController {
#Autowired
private SimpMessagingTemplate template;
#MessageMapping("/subscribe1")
#SendTo("/subscribe")
public void addSubscription(String message) {
System.out.println("hi");
}
Here is my question, am I misunderstanding somewhere where these two methods I speak of meant to be combined together? I was using a tomcat implementation of websocket before which matches method 1 which gives me easy direct control over sessions as I would like to be able to reuse web service subscriptions to avoid duplicate request from distinct clients and also a single requests may map to more than one subscription requests to the external webservice. Yet it seems method 2 would push all data requests to the same "/subscribe" endpoint and all connected clients would be receiving the same data, which is not what I am trying to accomplish. It also seems like the message broker api is limited as it does not allow me access to the subscribed sessions where I can control which sessions the receiving data will be sent to. I realized I had to switch to spring websocket as I needed built in browser compatibility fallback offered by SockJS and automatic heartbeat function offered by Stomp.js.
i think i found my answer, method 1 and 2 can be used side by side but not together. Method 2 is used when i want to implement a message broker that can create multiple channel destinations which many users can subscribe to the same destination. Now the question is how i can check whether i can check the number of subscriptions periodically for each existing destination