What I wanted to do is build a Rest backend application that needs the Authorization header on every request, validate and return the data or 401 Unauthorized if the user or password is wrong.
I have Spring Security in my classpath with this simple configuration:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("user")
.password("pass")
.roles("USER");
}
}
But one thing is not working correctly: when I make a request with valid username and password with Postman, the server responds the data correctly, but if I change the password to a wrong password and keep the correct username, the server stills responds with the data and OK status, as if it is using a JSESSIONID Cookie to check the further requests.
Is that a way of using spring security only for checking the header Authorization (with no cookies, sessions nor saving user informations or login and logout pages), or is better to just use Filters for doing that instead?
Thank you for any help !
Just add the following to your configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
This will configure spring security to never create a cookie, every request must reauthenticate.
Related
My architecture consists of three applications:
Authorization Server (10.10.1.1:8080)
Client A (10.10.3.3:8084)
Client B (10.10.2.2:8089)
Both Client A and B are serving static Angular files. Both clients are "communicating" with each other - it is possible to navigate from the first one to another one and back as well (through normal window.location.replace).
Everything works great instead of one specific situation.
I'm logging into Client A application (through Authorization server redirect).
I'm opening Client B - user is properly authenticated based on Client A.
I'm coming back to Client A - user is still authenticated. (I can repeat steps 2 and 3 endlessly)
I'm logging out from Client A.
I'm logging in again into Client A with the same or different user.
I'm opening Client B and getting blank page due to some network issues.
After page refresh everything works fine (JSESSIONID is changing in the browser and user is properly authenticated).
I've tried couple different approaches and configurations using session invalidation. Session is properly invalidated but then it is not created again (user is changing to anonymous instead of being properly taken from Client A).
Then follows redirect to authorization server, which isn't available for some reason.
The problem here is that normal flow after redirect (step 2) is:
Redirect to 10.10.2.2:8089/home-page
/home-page gets 302 REDIRECT in network tab to /login
/login redirects to 10.10.1.1:8080/oauth/authorize
then it redirects back to /home-page with status 200 OK.
Error flow after redirect (step 6) is:
Redirect to 10.10.2.2:8089/home-page
/home-page gets 200 OK in network tab
application loads the page and it makes request for user data (/api/user) which gets 401
the entire redirect cycle takes place and ends with unability to redirect to 10.10.1.1:8080/oauth/authorize
after page refresh everything works fine.
I've tried:
couple different approaches and configurations in security (both on Client A and Client B)
allowing all origins in CorsFilter (for testing purposes - even that didn't help)
adding another cookie through server.servlet.cookie.name and erasing it by deleteCookies() or proper handler
adding maximumSessions(2) for tests purposes - even that didn't help
At last I made some tricky solution. I made request to Client B before redirect to Client A. It removed JSESSIONID through HttpServletResponse. It helped, but only when I'm working on one browser tab.
If I have two tabs opened (one with Client A and one with Client B) after doing step 5 and 6 and refreshing the page on Client B, problem still persists (because I didn't erase JSESSIONID from the browser).
I don't know if I understand this problem properly (that JSESSIONID is problematic in the browser), so correct me if I'm wrong. Also - I don't know how to erase this cookie or allow OAuth2 Filters to automatically create new one and invalidate the session in proper way.
Can anybody help me with this problem and show what I'm doing wrong here?
Client A - Security Configuration
#Configuration
#EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public OAuth2RestTemplate oauth2RestTemplate(final OAuth2ClientContext context,
final OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, context);
}
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.logout().clearAuthentication(true)
.invalidateHttpSession(true)
// .deleteCookies("JSESSIONID")
// .logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {httpServletResponse.setStatus(HttpServletResponse.SC_OK);})
.addLogoutHandler(new ProperCookieClearingLogoutHandler("JSESSIONID"))
.and()
.authorizeRequests()
.antMatchers("/index.html", "/main.html", "/login", "/resources/**", ...)
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
Client B - Security Configuration
#Configuration
#EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public OAuth2RestTemplate oauth2RestTemplate(final OAuth2ClientContext context,
final OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, context);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.logout().clearAuthentication(true)
.invalidateHttpSession(true)
// .deleteCookies("JSESSIONID")
// .logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {httpServletResponse.setStatus(HttpServletResponse.SC_OK);})
.addLogoutHandler(new ProperCookieClearingLogoutHandler("JSESSIONID"))
.and()
.authorizeRequests()
.antMatchers("/index.html", "/main.html", "/resources/**", "/login/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
Authorization Server - Security Configuration
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login")
.permitAll().and()
.formLogin().failureHandler(new FailureAuthenticationHandler())
.loginPage("/login").permitAll()
.and().requestMatchers()
.antMatchers("/login")
.antMatchers("/oauth/authorize")
.antMatchers("/oauth/confirm_access")
.and()
.anyRequest().authenticated()
.and().sessionManagement().maximumSessions(-1).expiredUrl("/...").sessionRegistry(sessionRegistry());
}
#Bean
SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
... authentication providers ...
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
CustomTokenStore customTokenStore;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("x").secret("x")
.authorizedGrantTypes("x").autoApprove(true).scopes("x");
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(customTokenStore).authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.allowFormAuthenticationForClients();
}
}
Full log from Client B:
https://pastebin.com/aVc5AXcx
Thank you in advance.
24.10.2020 - TOPIC EDIT
After digging deeper and doing some research I probably found a core of the problem, but I don't know how to solve it yet.
First problem
There are two OAuth2 clients (annoted with #EnableOAuth2Sso) that share the same session and user data, but they don't know about each other openly (and about each other login/logout state).
I'm working on Client B and triggering logout call on that specific client.
Then I'm redirecting to authorization server login page with specific logout params.
I'm making logout call to authorization server on POST method and /logout path.
After successfull user logout I'm doing window.location.replace to Client A, which gets unauthorized error in network tab (401):
WWW-Authenticate header: Bearer realm="oauth2-resource", error="invalid_token", error_description="Invalid access token: 27ef8abe-e8e5-4d07-aaf4-a82a8757614e"
And in console of Client A I get:
UserInfoTokenServices: Could not fetch user details: class org.springframework.security.oauth2.client.resource.UserRedirectRequiredException, A redirect is required to get the users approval.
Second problem
Similiar situation is in the base problem stated in this topic. After relogin on Client A and page refresh on Client B, it has some session/token in cache and think that user is still authenticated in that client. It returns status 200 OK in HTML routing path (f.e. /home-page), but gets unauthorized on first request to API and giving the same invalid_token header:
WWW-Authenticate header: Bearer realm="oauth2-resource", error="invalid_token", error_description="Invalid access token: 4026cf9f-8081-4870-b9bf-6e6ff89d4ded" (401)
And in Resource Server I get:
Unable to obtain a new access token for resource 'null'. The provider manager is not configured to support it.
Both clients have the same configuration in properties
security.oauth2.client.client-id=x
security.oauth2.client.client-secret=y
security.oauth2.client.user-authorization-uri=http://${auhorization.server.url}/oauth/authorize
security.oauth2.client.access-token-uri=http://${auhorization.server.url}/oauth/token
security.oauth2.resource.user-info-uri=http://${resource.server.url}/user
Conclusion
I've tried adding csrfHeaderFilter and OAuth2ClientContextFilter from this topic, but it didn't help.
https://github.com/spring-guides/tut-spring-security-and-angular-js/issues/76
So the question is how to handle logout / refresh session and user context in another client after logout from the second one (and authorization server)? I don't know if I'm getting this process right, but I'm still anylizing what's going on here...
Can anybody show me some solution?
I want to secure my application with Spring Security, using OAuth 2. However, I don't want the server to redirect incoming unauthorized requests, but instead to respond with HTTP 401. Is it possible?
Example: this code redirects requests to a default login page.
application.properties
spring.security.oauth2.client.registration.google.client-id=...
spring.security.oauth2.client.registration.google.client-secret=...
AuthConfig.java
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login();
// https://stackoverflow.com/questions/31714585/spring-security-disable-login-page-redirect
// deos not work
// .and()
// .formLogin().successHandler((request, response, authentication) -> {});
}
}
You need to create new authentication entry point and set it in configuration.
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login();
}
}
public class AuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
public AuthenticationEntryPoint() {
super("");
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(401, "Unauthorized");
}
}
You need to set oauth2Login.loginPage in your HttpSecurity config and create a controller mapping to return whatever you want. Here's a simple example.
So in your security config
http
.authorizeRequests()
.antMatchers("/noauth").permitAll()
.oauth2Login()
.loginPage("/noauth")
In a controller
#GetMapping("/noauth")
public ResponseEntity<?> noAuth() {
Map<String, String> body = new HashMap<>();
body.put("message", "unauthorized");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body);
}
You can pass a map or pojo to the body method.
I would like to expand on Petr's answer by explaining that apparently for the time being first of all, the default login page is shown when there are more than one OAuth2 configured providers. I would expect that Spring Boot would have a smart trick to bypass this page easily and choose the right provider automatically, basing e.g. on the existence of the provider's client ID in the original request. I found out the hard way that this is not the case. So the way to do this is.. this not very apparent trick of providing a custom handler for failures - that will REDIRECT the user to the correct OAuth2 endpoint for each provider, based on the original HTTP request URL. I tried this and it works and I spent a whole day trying all manners of other solutions - my original scenario was to pass additional parameters to OAuth2 scheme in order to be able to get them back on successful authentication - they used to do this appending Base64 encoded information to the "state" URL request parameter, but Spring Security does not allow this at the moment. So the only alternative was to call a Spring Security-protected URL with those parameters already there, so when the successful authentication happens, this URL is accessed again automatically with those parameters intact.
Related: Multiple Login endpoints Spring Security OAuth2
My WebSecurity Config is like below;
#EnableWebSecurity
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.inMemoryAuthentication().withUser("hellouser")
.password("hellopass").roles("USER");
}
}
When i give wrong username, Authentication fails as expected. But, if i get success in authentication once, all other requests after that with wrong password but correct username gets authenticated successfully....
Is it getting cached somewhere?
Can i disable this feature?
Isn't it suppose to give authentication failure with wrong password?
NOTE: I am learning spring-security. I dont have any html pages in this app and testing from PostMan.
use http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); in the configure method.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//HTTP Basic authentication
.httpBasic()
.and()
....
.csrf().disable()
.formLogin().disable();
//to check password in each request
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
I was able to access the URL from below configuration using basic auth from Postman even with wrong credential, which was happening because once you provide the right credentials the credentials get stored in session and even if you repeats the same request the same session will be used to access the URL.
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/secure/admin/**").hasRole("ADMIN")
.antMatchers("/api/**","/secure/getUserByName/**").hasAnyRole("USER","ADMIN")
.anyRequest().fullyAuthenticated();
Solution:
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
Just add the above code. So this configuration assures that only a single instance of a user is authenticated at a time. And if the same user tries to access the URL then it's previous session is terminated and then the user has to provide login credentials again for which new session is created.
I want to secure a REST API. The rules are simple.
The user must call /api/authenticate to get a token
The user can use a token (received from /api/authenticate) to access the API /api/**
The endpoint /api/authenticate only accepts HTTP Basic authentication (no token authentication)
The endpoints /api/** (excluding /api/authenticate) only accepts token authentication (no Basic Authentication)
All remaining endpoints are public and doesn't require authentication.
I actually use this:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private TokenAuthenticationProvider tokenAuthenticationProvider;
#Override
protected void configure(final HttpSecurity httpSecurity) throws Exception {
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.headers().disable();
httpSecurity.setSharedObject(TokenAuthenticationProvider.class, this.tokenAuthenticationProvider);
httpSecurity.antMatcher("/api/authenticate").httpBasic();
httpSecurity.antMatcher("/api/**").apply(new TokenAuthenticationConfigurer());
httpSecurity.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll();
}
}
Actually, if I send a request with a token to /api/authenticate my configuration accepts the request. I think this happens because /api/authenticate is part of /api/**. So I need to exclude this path for token authentication.
How can I do that?
EDIT 1
If I use the .and() fluent style, the result is exactly the same.
#Override
protected void configure(final HttpSecurity httpSecurity) throws Exception {
httpSecurity.setSharedObject(TokenAuthenticationProvider.class, this.tokenAuthenticationProvider);
httpSecurity
.headers().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.antMatcher("/api/authenticate").httpBasic()
.and()
.antMatcher("/api/**").apply(new TokenAuthenticationConfigurer())
.and()
.authorizeRequests().antMatchers("/api/**").authenticated().anyRequest().permitAll();
}
EDIT 2
As I understand the SecurityBuilder (HttpSecurity), every call of antMatcher(...) in the configure(...) method overwrites the previous call. In the debug logs I can see, that Spring Security always tries to match the request path against /api/** but never agains /api/authenticate. If I switch the order, I can't access the API anymore, just /api/authenticate, because Spring Security now always tries to match agains /api/authenticate.
So the question is: How can I register multiple rules:
/api/authenticate -> HttpBasicConfigurer (.http())
/api/** -> TokenAuthenticationConfigurer (my token authentication configured, .apply(...))
Maybe it is because you always override the configuration of the parent and you do not use the and() method:
The Java Configuration equivalent of closing an XML tag is expressed using the and() method which allows us to continue configuring the parent. If you read the code it also makes sense. I want to configure authorized requests and configure form login and configure HTTP Basic authentication.
http://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#jc-httpsecurity
We are using a two-pronged approach to user authentication where we are using an in-memory authentication token store in clustered environment and saving the token in a shared database as well. Now I am able to get token using the in-memory store and authenticate users. Everything is working as expected.
I am looking for the following things :
Capture the token and save it in the database? How to achieve this with Spring Security (this should happen after successful authentication)?
If server restarts then I should still be able to validate the token from database.(if user name password is correct.)
#Component
public class CustomAuthSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
System.out.println(request);
System.out.println(response);
}
}
Following are my HTTP settings:
#Override
public void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler)
.and()
.csrf()
.requireCsrfProtectionMatcher(
new AntPathRequestMatcher("/oauth/authorize"))
.disable().headers().frameOptions().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers("/hello/")
.permitAll().antMatchers("/secure/**").authenticated();
}
PS : It looks like adding an interceptor can help, however I am not sure how do I to get token from response. Any suggestions?