I have a working Spring Boot server with Authentication/Authorization. When trying to open a connection with SockJS it is blocked by my other security measures.
I don't fully understand how the flow of security works in Java yet, but I have an itch that I need to pass the JWT token with the handshake when trying to connect with SockJS. From what I understand this is not possible with SockJS. So I am just wandering what the best approach to getting the socket started with JWT. Also, I know I don't have CSRF enabled so some tips on that would be nice too.
In WebSecurityConfig.java:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors();
httpSecurity.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate", "/login", "/register").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
In WebSocketConfig.java:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("http://localhost:5000")
.withSockJS();
}
}
In my Vue app. Trying to connect with JS:
connect() {
this.socket = new SockJS("http://localhost:8080/ws");
this.stompClient = Stomp.over(this.socket);
this.stompClient.connect(
{
token: 'Bearer ' + localStorage.getItem('user-token')
},
frame => {
this.connected = true;
console.log(frame);
this.stompClient.subscribe("/topic/greetings", tick => {
console.log(tick);
this.received_messages.push(JSON.parse(tick.body).content);
});
},
error => {
console.log(error);
this.connected = false;
})
},
If I remove all the security config I can connect to the WebSocket just fine.
I expect to be able to connect to the WebSocket but right now I am getting a 401 and I cant find a way to authenticate when doing the handshake.
Self-awnser
I did a work around by disabling security for the handshake paths in WebSecurityConfig.java like this
.antMatchers(
"/authenticate",
"/login",
"/register",
"/secured/chat/**").permitAll()
I guess I have to authenticate the user through the socket now, as this makes the socket open to anyone, something I didn't really want.
Related
I'd like to secure my app using Spring Security's Resource Server and Authorization Server included in my component.
The desired flow include using only client-credentials grant type and passing client_id together with client_secret as base64 header, what should return token for further requests after hitting oauth/token endpoint. I also include grant_type: client-credentials in POST request parameters
For now I am receiving error:
"Full authentication is required to access this resource".
The strange thing is despite my configuration Spring still generates random security password what can be seen in console log.
This is my first approach to Spring Security so maybe I've missed something?
Below is my configuration:
AuthorizationServerConfig:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter {
#Override
public void configure(final AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("some-client")
.authorizedGrantTypes("client-credentials")
.authorities("ROLE_CLIENT")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(3600)
.secret("somePass")
.refreshTokenValiditySeconds(24*3600);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
ResourceServerConfig:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/token", "/oauth/authorize", "/oauth/confirm_access").permitAll()
.antMatchers("/**").authenticated()
.and().httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
I am using Spring Boot 2.0.1.RELEASE and Spring Security OAuth2 2.0.14.RELEASE.
As in my case, InMemoryTokenStore is used it will work with one instance, what is the best substitute for this if one wanted to create multiple instances of app?
When you are storing the users in memory, you are providing the passwords in plain text and then when you are trying to retrieve the encoder from the DelegatingPasswordEncoder to validate the password it can't find one.
You can try out the following changes so that you can override password encoding by adding {noop}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("some-client")
.authorizedGrantTypes("client-credentials")
.authorities("ROLE_CLIENT")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(3600)
.secret("{noop}somePass") //change here
.refreshTokenValiditySeconds(24*3600);
}
And also change your user in WebSecurityConfigurer doing the same for the password.
I am using spring security along with java config
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/*").hasRole("ADMIN")
.and()
.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(new SimpleUrlAuthenticationFailureHandler());
I am using PostMan for testing my REST services. I get 'csrf token' successfully and I am able to login by using X-CSRF-TOKEN in request header. But after login when i hit post request(I am including same token in request header that i used for login post request) I get the following error message:
HTTP Status 403 - Could not verify the provided CSRF token because your session was not found.
Can any one guide me what I am doing wrong.
According to spring.io:
When should you use CSRF protection? Our recommendation is to use CSRF
protection for any request that could be processed by a browser by
normal users. If you are only creating a service that is used by
non-browser clients, you will likely want to disable CSRF protection.
So to disable it:
#Configuration
public class RestSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
Note: CSRF protection is enabled by default with Java Configuration
try this: #Override protected boolean sameOriginDisabled() { return true;}
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
...
// Determines if a CSRF token is required for connecting. This protects against remote
// sites from connecting to the application and being able to read/write data over the
// connection. The default is false (the token is required).
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
source: WebSocket Security: Disable CSRF within WebSockets
Disabling CSRF protection is a bad idea.
Spring will automatically generate a new CSRF token after each request, and you need to include it in all HTTP requests with side-effects (PUT, POST, PATCH, DELETE).
In Postman you can use a test in each request to store the CSRF token in a global, e.g. when using CookieCsrfTokenRepository
pm.globals.set("xsrf-token", postman.getResponseCookie("XSRF-TOKEN").value);
And then include it as a header with key X-XSRF-TOKEN and value {{xsrf-token}}.
Came to same error just with POST methods, was getting 403 Forbidden "Could not verify the provided CSRF token because your session was not found."
After exploring some time found solution by adding #EnableResourceServer annotation to config.
Config looks like that (spring-boot.version -> 1.4.1.RELEASE, spring-security.version -> 4.1.3.RELEASE, spring.version -> 4.3.4.RELEASE)
#Configuration
#EnableWebSecurity
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(inMemoryUserDetailsManager()).passwordEncoder(passwordEncoder());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.csrf().disable();
http.authorizeRequests().anyRequest()
.permitAll();
}
private InMemoryUserDetailsManager inMemoryUserDetailsManager() throws IOException {
// load custom properties
Properties properties = new Properties();
return new InMemoryUserDetailsManager(properties);
}
private PasswordEncoder passwordEncoder() {
return new TextEncryptorBasedPasswordEncoder(textEncryptor());
}
private TextEncryptor textEncryptor() {
return new OpenSslCompatibleTextEncryptor();
}
}
I get this error message (HTTP Status 403 - Could not verify the provided CSRF token because your session was not found.) when I do a JS fetch AJAX call without using the credentials: "same-origin" option.
Wrong way
fetch(url)
.then(function (response) { return response.json(); })
.then(function (data) { console.log(data); })
Correct way
fetch(url, {
credentials: "same-origin"
})
.then(function (response) { return response.json(); })
.then(function (data) { console.log(data); })
This is an old question but this might help someone. I had the similar issue and this is how I was able to resolve it.
In order for the CSRF to work with the REST API you need to obtain a CSRF token via API before every single call and use that token. Token is different every time and cannot be re-used.
Here is the controller to get the CSRF token:
#RequestMapping(value = "/csrf", method = RequestMethod.GET)
public ResponseEntity<CSRFDTO> getCsrfToken(HttpServletRequest request) {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
return ResponseEntity.ok(CSRFDTO.builder()
.headerName(csrf.getHeaderName())
.token(csrf.getToken())
.build());
}
Additionally, you might consider configuring your Spring app to disable the CSRF for the REST API endpoints. To quote an article I've read somewhere:
I'm very certain that CSRF tokens on a REST endpoint grant zero additional protection. As such, enabling CSRF protection on a REST endpoint just introduces some useless code to your application, and I think it should be skipped.
Hope this helps.
I have solved it by adding the last attribute in my login page,maybe it will do yo a favor.
<%# page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false"%>
I am building a Stateless Spring (4.2.4.RELEASE) Solution using STOMP over Websockets with SockJS and a Rest Endpoint using JWT to connect mobile devices with Full Duplex communication. I am using Tomcat 8.0.33 as a Web Server and testing using html with sockjs javascript client. The stomp protocol works fine using the http fallback but I can't make it using only a websocket protocol. I tried CORS in many ways but I am not sure that is a Tomcat Problem or just bad spring configuration. I tested my html even in the same domain and port and SockJS is still falling back into xhr or iframes.
WebScoketConfig.java
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry)
{
RequestUpgradeStrategy upgradeStrategy = new TomcatRequestUpgradeStrategy();
registry.addEndpoint("/ws").setHandshakeHandler(new DefaultHandshakeHandler(upgradeStrategy))
.setAllowedOrigins("*").withSockJS().setSessionCookieNeeded(false)
.setStreamBytesLimit(512 * 1024)
.setHttpMessageCacheSize(1000)
.setDisconnectDelay(30 * 1000);
}
#Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.taskExecutor().corePoolSize(50);
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry)
{
registry.enableSimpleBroker("/queue/", "/topic/");
// registry.enableStompBrokerRelay("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/myapp");
}
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(500 * 1024);
registration.setSendBufferSizeLimit(1024 * 1024);
registration.setSendTimeLimit(20000);
}
}
WebSecurityConfig.java
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.authorizeRequests()
.antMatchers("/**").permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
}
}
I solved my problem, actually the code was good but the antivirus (Kaspersky) was closing the connection on my client browser just after opened. It forces SockJS to fallback into a different strategy. I tested the client with the antivirus turned off and the Websocket transport was beautifully running. Tested on mac & linux as well.
beforehand sorry about my english.
I'm currently developing an application with spring boot and angularjs as the front end and spring mvc as backend, all this with spring boot, my problem is, i'm using spring security for simple form login like this
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**").permitAll()
.anyRequest().authenticated();
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true);
http
.sessionManagement()
.maximumSessions(1)//max session per user
.expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true);
I implemented httpsessionlistener as simple as this
#Configuration
public class SessionListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("ok");
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("not ok");
}
}
and my Application.java as simple as this
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
//Get process id, es para el wrapper de java
}
and in my application.proterties this
server.session-timeout=5//Short session just for example
i'm facing 2 situatons, the first, when i logout it says not ok, ok, that means it is creating the session again, and when i try to log in again it says wrong username or password because it only allow 1 session per user.
My logout button is just a simple reques from angularjs like this
this.logout=function($scope){
var LogOut = resource('/sticker/logout');
LogOut.get(function(message){
//window.location.href="login?logout";
console.log(message);
//scope.message = message.message;
});
}
and the second situation is, when the session expires and i do a request from angularjs it returns the login page, i'm capturing this response with the following code snippet wich i took from interceptors in angularjs
app.factory("httpInterceptor", ["$q", "$window", "$log",
function ($q, $window, $log) {
return {
"response": function (response) {
var responseHeaders;
console.log(response);
responseHeaders = response.headers();
return response;
}
};
}
])
I want to be able to return istead something like an httpresponse og 440
any help appreciated.
fwiw 6 months later...
By default it looks like spring security is responding with a 403 once a session times out. I am getting it to respond with a 401 (Unauthorized) instead by adding the following to the HttpSecurity config:
#Configuration
#EnableWebSecurity
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
...
}
private AccessDeniedHandler accessDeniedHandler() {
return (request, response, ex) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getLocalizedMessage());
}
}
I rephrased question and deleted old one. Hopefully I will get answers now.
CAS server attempt to send a callback request on SLO to protected app:
<Error Sending message to url endpoint [http://localhost:8080/j_spring_cas_security_check]. Error is [Server returned HTTP response code: 403 for URL: http://localhost:8080/j_spring_cas_security_check]>
On debug the org.jasig.cas.client.session.SingleSignOutFilter never gets hit.
#Override
public void configure(HttpSecurity http) throws Exception {
http.
addFilter(casFilter()).
addFilterBefore(requestSingleLogoutFilter(), LogoutFilter.class).
addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
.logout().permitAll().logoutSuccessUrl("http://localhost:8080/j_spring_cas_security_logout").invalidateHttpSession(true).and()
.authorizeRequests().antMatchers("/home2").hasAuthority("USER").and()
.exceptionHandling()
.authenticationEntryPoint(casEntryPoint)
.and()
.httpBasic();
}
Filter casFilter() {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setServiceProperties(getServiceProperties());
casAuthenticationFilter.setAuthenticationManager(getProviderManager());
return casAuthenticationFilter;
}
LogoutFilter requestSingleLogoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter("http://localhost:8089/cas/logout",
new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl("/j_spring_cas_security_logout");
return logoutFilter;
}
SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
return singleSignOutFilter;
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new org.jasig.cas.client.session.SingleSignOutHttpSessionListener());
}
I have everything apart from the logout. Currently it invalidates the session thanks to LogoutFilter, destroys the ticket(on redirect to CAS) BUT if the SLO request would be sent from other protected application, obviously it will have no impact on this application(as sessionid will still be here).
Any suggestions?
Had the same situation (403 status code) with CAS SLO, in my spring security log I found:
Invalid CSRF token found for http://localhost:8080/j_spring_cas_security_check
so I disabled csrf filter in my security config:
http.csrf().disable();
This might not be a good practice, just a quick solution that works for me now. Also I am not shure if I can get the right csrf token if SLO is initiated in other protected application.