Error 403 after #EnableOAuth2Sso in Spring security - spring

I've got my own mapping setCred/ and when it gets called via a http POST request it returns a 403 Error. But when I remove the #EnableOAuth2Sso it all works fine.
I don't have any idea what part I'm missing here.
#EnableOAuth2Sso
#Controller
public class TestAPI {
#RequestMapping(value = "/setCred", method = RequestMethod.POST, consumes = "application/json", produces = "text/plain")
public ResponseEntity<?> setCred(#RequestBody StringToken json) throws JsonParseException, JsonMappingException, IOException {
ResponseEntity<?> res = null;
....
My end goal is to add a facebook login on my webpage (from https://spring.io/guides/tutorials/spring-boot-oauth2/)
I've got also security.basic.enabled=false in my application.properties

protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
Starting with Spring Security 4.0 CSRF protection is enabled by default, I removed it for now, at the time my front end is not handling any CSRF tokens. But in the future I'll make sure to include a CSRF token within each POST request. Of course depending on one's intentions CSRF may be or not enabled, the only "bad" part about enabling the CSRF is adding more complexity to our code.

Related

Default 401 instead of redirecting for OAuth2 login Spring Security

I support two types of authentication and need to return 401 for most paths instead of redirects. For Keycloak I used the HttpUnauthorizedEntryPoint below and its fine, but for the OAuth2 login, it prevents the automatic redirect (on "/auth/challenge" in my case) to "/oauth2/authorization/azure" on NegatedRequestMatcher(/login, and some other things) to be put in place. The valid process is reflected in logs below:
org.springframework.security.web.util.matcher.NegatedRequestMatcher: matches = true
org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint: Match found! Executing org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint#112c824c
org.springframework.security.web.context.SecurityContextPersistenceFilter: SecurityContextHolder now cleared, as request processing completed org.springframework.security.web.DefaultRedirectStrategy: Redirecting to 'http://localhost:2222/oauth2/authorization/azure'
This is the code that adds the Ouath2 bit to the common configuration:
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.commonConfiguration()
.exceptionHandling()
.authenticationEntryPoint(HttpUnauthorizedEntryPoint())
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(userService)
}
public class HttpUnauthorizedEntryPoint implements AuthenticationEntryPoint {
private static final Logger LOG = LoggerFactory.getLogger(HttpUnauthorizedEntryPoint.class);
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "This endpoint requires authorization.");
}
}
The question is, how can I by default return 401 and let all the OAuth2 redirects to be placed under the hood?
Thanks in advance.
I know that because of the HttpUnauthorizedEntryPoint the DelegatingAuthenticationEntryPoint is not filled in by Spring Security. I tried to add this manually, but I would rather have this process done by Spring.
val entryPoints = LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>()
entryPoints[loginPageMatcher] = LoginUrlAuthenticationEntryPoint("/oauth2/authorization/azure")
val loginEntryPoint = DelegatingAuthenticationEntryPoint(entryPoints)
loginEntryPoint.setDefaultEntryPoint(HttpUnauthorizedEntryPoint())
My guess is you serve both a REST API and server-side rendered UI (Thymeleaf, JSF or whatever) and you want to return
401 for unauthorized requests to #RestController
302 (redirect to login) for unauthorized requests to UI pages.
Define two SecurityFilterChain beans:
first with client configuration restricted to UI paths
#Order(Ordered.HIGHEST_PRECEDENCE)
http.securityMatcher(new OrRequestMatcher(new AntPathRequestMatcher("/login/**"), new AntPathRequestMatcher("/oauth2/**"), ...); // add here all paths to UI resources
http.oauth2Login(); // and all client configuration you need for UI
http.authorizeHttpRequests().requestMatchers("/login/**").permitAll().requestMatchers("/oauth2/**").permitAll().anyRequest().authenticated();
second being default with resource-server configuration (no securityMatcher and an order greater than HIGHEST_PRECEDENCE)
Details in this answer and that tutorial

Is possible ask for an acces token oauth2 just with refresh token in spring security? without basic authentication?

I would like to know if in spring oauth2 is possible get a new pair tokens (access token and refresh token) just using another refresh token, without the basic authentication (without clientId and clientSecret, is there any way?
For exemple:
WITH BASIC AUTH
curl -u clientId:clientSecret -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
WITHOUT BASIC AUTH
curl -u -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
I note that sprint BasicAuthenticationFilter in spring uses validation bellow, maybe override this filter and make the authentication just with refresh token.
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
The short answer is no. The class used to manage the Spring Oauth 2 endpoints is the following one:
#FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint
Both requests, I mean, get access token and refresh one use the same endpoint with different parameters. And the method to manage those ones is:
#RequestMapping(
value = {"/oauth/token"},
method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
...
As you can see, a Principal object is required (in this case provided by the Basic Authentication).
Even, if you configure the security of your project to permit that url without checking authentication, you will achieve to "enter" in above method but you will receive an InsufficientAuthenticationException because no Authentication instance has been provided.
Why custom authentication will not work
1. Create a custom AuthenticationProvider will not work because the method postAccessToken is invoked before. So you will receive an InsufficientAuthenticationException.
2. Create a OncePerRequestFilter and configure it to execute before process the current request:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.anyRequest().authenticated()
.and()
.addFilterBefore(myCustomFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(POST, "/accounts/oauth/**");
}
with a code "similar to":
#Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("existingUser",
"passwordOfExistingUser",
Collections.emptyList()));
...
filterChain.doFilter(request, response);
}
The problem with this approach is the principal in TokenEndpoint comes from the HttpServletRequest not from Spring context, as you can see debugging BasicAuthenticationFilter class.
In your custom filter you can try, using reflection, set a value in userPrincipal property but, as you can verify, request has several "internal request properties" and that could be a "too tricky option".
In summary, Oauth standard needs user/pass to access to the resources, if you want to workaround in almost of provided endpoints maybe that project is not what you are looking for.
Workaround to include your own object in Spring Principal
I do not recommend that but if you still want to go ahead with this approach, there is a way to include your own value inside the principal parameter received by TokenEndpoint class.
It is important to take into account BasicAuthorizationFilter will be still executed, however you will be able to override the Spring principal object by your own one.
For this, we can reuse the previous CustomAuthenticationFilter but now your have to include the filters you need, I mean, allowed urls, parameters, etc You are going to "open the doors", so be careful about what you allow and not.
The difference in this case is, instead of add the configuration in our class that extends WebSecurityConfigurerAdapter we are going to do it in:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private CustomAuthenticationFilter customAuthenticationFilter;
...
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()");
security.addTokenEndpointAuthenticationFilter(customAuthenticationFilter);
}
...

Keycloak + Spring. Ignore certain APIs regardless of Authorization Header

I want to allow certain APIs of pattern api/v1/public/** for all authenticated as well as anonymous users.
I have Keycloak adapter setup in my project, and my WebConfiguration looks like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class KeycloakWebConfig extends KeycloakWebSecurityConfigurerAdapter {
// ........ other methods .........//
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.authorizeRequests()
.anyRequest().permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/v1/public/**");
}
}
With this setup, when I call /api/v1/public/ping API without any headers, it works fine. I get the expected response.
However, if I add a random or empty Authorization header while making the request to the same api, it does not respond at all.
Using Postman to make request, it says "There was an error connecting to http://localhost:8080/api/v1/public/ping".
Using cURL terminal command to make request, it does not print anything and terminates the process.
If I make the same call again (using either postman or curl) without any header fields, it works perfectly fine again. Any idea what is causing this?

SpringSecurity: How to Continue Forwarding Request to RestController After a Successful Authentication?

I am doing a pure backend project with REST APIs (not MVC) and would like to use SpringSecurity with JWT token to project these APIs. The implementation is good and all APIs are successfully protected with the token, and I can post a JSON string with username and password to "/login" path to get token
My problem is:
The SpringSecurity will return the response with token directly in successfulAuthentication() rather than keep forwarding to RestController (RestController's "/login" path gets no data)
And my question is:
What should I do, after a successful authentication, to allow SpringSecurity can keep forwarding the request to RestController's "/login" path so that I can do something else on the request and the newly built token beside the security in the path?
Appreciate any helps, Thank you!
My code:
#Component
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
// ...
#Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.authorizeRequests()
.antMatchers("/registry").permitAll() // allow path /registry
.antMatchers("/login").permitAll() // allow path /login
.antMatchers("/verify").permitAll() // allow path /verify
.anyRequest().authenticated();
// ...
}
}
#RestController
public class EntranceEndpoint {
#RequestMapping(path = "/login", method = RequestMethod.POST)
public RestResponse<String> login(LoginMetaInfo login) {
System.out.println(login); // no output here when login
// some further operations for a successful login, and return a REST response
}
}
And this is what the SpringSecurity do on a successful login
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
// ...
/**
* on login success
*/
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException {
// here build the token and insert into response for commitment
// - the SpringSecurity soon returns the response directly, rather then keep forwarding to RestController
String token = xxxx;
response.setStatus(StatusCode.SUCCESS().getCode());
RestResponse<String> body = RestResponse.succeeded(StatusCode.SUCCESS().withMsg(LoginResponseCode.LOGIN), token);
response.setContentType(MediaType.APPLICATION_JSON);
response.setCharacterEncoding(MediaType.CHARSET);
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getWriter(), body );
}
}
What about simply using HttpServletResponse's sendRedirect instead of writing to the response?
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException {
// do what you want here
response.sendRedirect("/login");
// response.sendRedirect("https://yoururl");
}

Spring Security blocks POST requests despite SecurityConfig

I'm developing a REST API based on Spring Boot (spring-boot-starter-web) where I use Spring Security (spring-security-core e spring-security-config) to protect the different endpoints.
The authentication is done by using a local database that contains users with two different sets of roles: ADMIN andUSER. USER should be able toGET all API endpoints and POST to endpoints based onrouteA. ADMIN should be able to do the same asUSER plus POST andDELETE to endpoints based on `routeB
However the behavior I'm getting is that I can do GET requests to any endpoint but POST requests always return HTTP 403 Forbidden for either type of user - ADMIN and USER - which is not expected what I'm expecting based on my SecurityConfiguration.
Any ideas of what am I missing?
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private DataSource dataSource;
#Override
public void configure(AuthenticationManagerBuilder builder) throws Exception {
logger.info("Using database as the authentication provider.");
builder.jdbcAuthentication().dataSource(dataSource).passwordEncoder(new BCryptPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
authorizeRequests().antMatchers(HttpMethod.GET, "/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeA/*").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeB/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/routeB/*").hasRole("ADMIN").and().
requestCache().requestCache(new NullRequestCache()).and().
httpBasic().authenticationEntryPoint(authenticationEntryPoint).and().
cors();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
RouteBController .java
#RestController
public class RouteBController {
static final Logger logger = LoggerFactory.getLogger(RouteBController.class);
public RouteBController() { }
#RequestMapping(value = "routeB", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
public String getStuff() {
return "Got a hello world!";
}
#RequestMapping(value = "routeB", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.POST)
public String postStuff() {
return "Posted a hello world!";
}
}
RESTAuthenticationEntryPoint.java
#Component
public class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("AppNameHere");
super.afterPropertiesSet();
}
}
BEFORE disabling the CSFR as a way of fixing this issue, please check the resources on Mohd Waseem's answer to better understand why it is important and to have an idea of how it can be properly set up. As RCaetano has said, CSFR is here to help us from attacks and it should not be disabled blindly.
Since this answer still explained the 2 issues on my original questions, I'll leave it as the marked answer to create awareness about possible issues with the CSFT and security routes but don't take it literally.
There were 2 issues in SecurityConfiguration.java that made it misbehave.
Although the 403 Forbidden error message didn't contain any message indication of why it was failing (see example below) it turns out it was due to having CSRF enabled. Disabling it allowed for POST and DELETE requests to be processed.
{
"timestamp": "2018-06-26T09:17:19.672+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/routeB"
}
Also the expression used in antMatched(HttpMethod, String) for RouteB was incorrect because /routeB/* expects it to have something after /. The correct configurtion is /routeB/** since more paths can be present (or not).
The corrected SecurityConfiguration.java is
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
authorizeRequests().antMatchers(HttpMethod.GET, "/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeA/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeB/**").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/routeB/**").hasRole("ADMIN").and().
requestCache().requestCache(new NullRequestCache()).and().
httpBasic().authenticationEntryPoint(authenticationEntryPoint).and().
cors().and().
csrf().disable();
}
Source: StackOverflow em Português
Cross-site request forgery is a web security vulnerability that allows an attacker to induce users to perform actions that they do not
intend to perform.
In your case disabling CSRF protection exposes user to this vulnerability.
Note: If it was pure Rest API with O-Auth protection then CSRF was not
needed. Should I use CSRF protection on Rest API endpoints?
But In your case when user logs in a session is created and cookie is returned in response and without CSRF token Attacker can exploit it and perform CSRF.
It wouldn't be a good idea to disable CSRF instead you can configure your app to return CSRF token in response headers and then use it in all your subsequent state changing calls.
Add this line of code in your SecurityConfiguration.java
// CSRF tokens handling
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
CsrfTokenResponseHeaderBindingFilter.java
public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {
protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER";
protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM";
protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException {
CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);
if (token != null) {
response.setHeader(RESPONSE_HEADER_NAME, token.getHeaderName());
response.setHeader(RESPONSE_PARAM_NAME, token.getParameterName());
response.setHeader(RESPONSE_TOKEN_NAME, token.getToken());
}
filterChain.doFilter(request, response);
}
}
Header Response form Server:
Note that we now have CSRF token in the header. This will not change untill the session expires.
Also read: Spring Security’s CSRF protection for REST services: the client side and the server side for better understanding.
It's simple CSRF enabled issue that doesn't allow POST requests. I faced the same problem here's the solution: (Explained)
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/form").hasRole("ADMIN") // Specific api method request based on role.
.antMatchers("/home","/basic").permitAll() // permited urls to guest users(without login).
.anyRequest().authenticated()
.and()
.formLogin() // not specified form page to use default login page of spring security
.permitAll()
.and()
.logout().deleteCookies("JSESSIONID") // delete memory of browser after logout
.and()
.rememberMe().key("uniqueAndSecret"); // remember me check box enabled.
http.csrf().disable(); // ADD THIS CODE TO DISABLE CSRF IN PROJECT.**
}
Above code:
http.csrf().disable();
will solve the problem.

Resources