Spring Security OAuth - how to disable login page? - spring

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

Related

Securing same endpoint with multiple configurations

We have a microservice architecture with securities for front to back with JWT, and back-to-back security with HTTP Basic.
Here is our configuration class for JWT :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers(endpointsProperties.getJwtWithWildcard())
.and()
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
jwtFilter is a simple filter that reads the Authorization header, and set the SecurityContextHolder.
And the HTTP Basic :
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
for (Map<String, String> userData : properties.getUsers()) {
auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser(userData.get("login")).password(userData.get("password")).authorities(BASIC_AUTH_AUTHORITY);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers(endpoints.getBasicWithWildcard() )
.and().csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().httpBasic();
}
Those configuration class are used in differnets services, with distinct JWT and HTTP Auth endpoints. They are used either at the same time or independently. EndpointsProperties are loaded from application.yml.
But now, we have some referential microservices that we want to be reached either by other services or direclty by a (web) frontend application. We want to know if it is possible to secure a same URL ('/api/referential', for example) with the two different methods. Combining those configuration class with the same endpoints does not work, and it seems one configuration eraze the other.
Is it possible to secure a same URL path with different methods like that ? If yes, what do we need to change to enable it ?
Thanks for your support.
I think you can just add the two filters to the filter chain in this order
BasicAuthenticationFilter
JwtFilter
and make sure the ignoreFailure property of the BasicAuthenticationFilter is set to true.
This will make The basicAuthFilter authenticate requests with basicAuth and just continue down the filter chain if no basicAuth is sent - thus delegating to the JwtFilter.
then remove .httpBasic() from the WebsecurityConfig - as it will try to add another BasicSecurityFilter.
Just an off the cuff idea

Use AbstractAuthenticationProcessingFilter for multiple URLs

I have the below endpoint patterns in my application
/token -- accessible to all
/rest/securedone/** -- requires authentication
/rest/securedtwo/** -- requires authentication
/rest/unsecured/** -- does not require authentication
As of now, I am able to access the /token endpoint.
But /rest/securedone/** and /rest/unsecured/** return 401 when a token(JWT) is not sent. It is my intention to secure /rest/securedone/** and that is fine /rest/unsecured/** should be accessible.
My httpSecurity config is as below:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/token").permitAll()
.antMatchers("/rest/secured/**").authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
and my AbstractAuthenticationProcessingFilter extended class is as below:
public class MyAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter {
private static Logger log = LoggerFactory.getLogger(MyAuthenticationTokenFilter.class);
public MyAuthenticationTokenFilter() { super("/rest/**"); }
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, ServletException {
//authentication handling code
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
}
Can someone please help my figure out the below:
When is the MyAuthenticationTokenFilter used? For which URL will it be invoked? How come, /rest/unsecured/** is also expecting authentication? It happens even if i explicitly say .antMatchers("/rest/secured/**").permitAll().
Can I specify multiple url patterns in my super(defaultFilterProcessingUrl) call inside MyAuthenticationTokenFilter constructor? For example, if I have another url such as /api/secured/**, how can I get my MyAuthenticationTokenFilter to be invoked for /api/secured/** requests? I do not need different authentication handling so I want to re-use this filter.
When is the MyAuthenticationTokenFilter used ?
This filter is using for processing the request with client credential,it will filter the url when the
RequestMatcher match the request url, for example, in your configuration, it will handle the url that matches /rest/**, and try to convert the client credential to Authentication(e.g userInfo, role ...), it maybe throws an exception when the request with incorrect client credential.
It is different to authorizeRequests(xxx.authenticated() or xxx.permit()), authorizeRequests just check the whether the authentication has some special attributes (e.g role, scope).
By way of analogy, AbstractAuthenticationProcessingFilter just puts some cards(Authentication) into a box(SecurityContext) by different clients, authorizeRequests just check the box has the card that it needed, or it will deny the request. AbstractAuthenticationProcessingFilter
don't care who/how to use the cards, and authorizeRequests don't care where the cards come from.
Can I specify multiple url patterns in my super(defaultFilterProcessingUrl) call inside MyAuthenticationTokenFilter constructor ?
Yes, you can set the requiresAuthenticationRequestMatcher by setRequiresAuthenticationRequestMatcher, it will override the old requiresAuthenticationRequestMatcher, for example,
authenticationTokenFilter
.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/rest/secured/**")
, new AntPathRequestMatcher("/api/secured/**")
));

How to avoid redirecting to login form for some URL with Spring Security?

This is the Spring Security configuration of my webapp
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", LOGIN, "/webjars/**").permitAll()
.antMatchers(CONFIGURATION).hasAuthority(Authorities.AUTHORITY_SOLMAN72_EXPORT_ENABLED.getKey())
.antMatchers("/api/**").hasAuthority(Authorities.AUTHORITY_SOLMAN72_EXPORT_ENABLED.getKey())
.and()
.formLogin()
.loginPage(LOGIN)
.and()
.addFilterBefore(oAuth2ClientAuthenticationProcessingFilter, BasicAuthenticationFilter.class);
}
Currently the server is redirecting to the LOGIN page every request that does not have the right credentials.
I want to redirect to the LOGIN page only the unauthorized requests to CONFIGURATION, while the unauthorized requests to /api/** should answer with 403.
What's a good way of achieving that?
I solved my problem using an AuthenticationEntryPoint:
http
.authorizeRequests()
.antMatchers(LOGIN).permitAll()
.antMatchers("/**").hasAuthority(Authorities.AUTHORITY_SOLMAN72_EXPORT_ENABLED.getKey())
.and()
.addFilterBefore(oAuth2ClientAuthenticationProcessingFilter, BasicAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(unauthenticatedRequestHandler);
#Bean
UnauthenticatedRequestHandler unauthenticatedRequestHandler() {
return new UnauthenticatedRequestHandler();
}
static class UnauthenticatedRequestHandler implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
if (request.getServletPath().startsWith("/api/")) {
response.setStatus(403);
} else {
response.sendRedirect(LOGIN);
}
}
}
You could use DelegatingAuthenticationEntryPoint:
An AuthenticationEntryPoint which selects a concrete AuthenticationEntryPoint based on a RequestMatcher evaluation.
with Http403ForbiddenEntryPoint for /api/** and LoginUrlAuthenticationEntryPoint as default entry point.
#Bean
public DelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint() {
LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> entryPoints = new LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>();
entryPoints.put(new AntPathRequestMatcher("/api/**"), new Http403ForbiddenEntryPoint());
DelegatingAuthenticationEntryPoint defaultEntryPoint = new DelegatingAuthenticationEntryPoint(entryPoints);
defaultEntryPoint.setDefaultEntryPoint(new LoginUrlAuthenticationEntryPoint(LOGIN));
return defaultEntryPoint;
}
I went to implement dur's answer but noticed there's a ExceptionHandlingConfigurer.defaultAuthenticationEntryPointFor(...) (available from around Spring Security 3.2.x) which does effectively the same thing with much less dependent code:
http.exceptionHandling()
.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), new AntPathRequestMatcher("/api/**"));
Moreover, I noticed specifying any defaultAuthenticationEntryPointFor() sets the first up as the default entry point.
By default, FormLoginConfigurer, OAuth2LoginConfigurer, Saml2LoginConfigurer, etc. adds their own during SecurityConfigurer.init() and, unless we've specified one, the first among those becomes the default entry point.
This may or may not be useful, but because the
AuthenticationEntryPoint provided by FormLoginConfigurer, OAuth2LoginConfigurer, Saml2LoginConfigurer, etc. avoids requests containing the header X-Requested-With: XMLHttpRequest, the entry point we've specified with defaultAuthenticationEntryPointFor() will end up being used for AJAX, regardless of what we've specified for the request matcher argument.

Spring Security Authentication Success With Wrong Password

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.

Restrict authentication method for endpoints with Spring Security

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

Resources