Spring endpoint prevent default accept header */* - spring

I'm creating an endpoint where I want to force the user to set the Accept http header:
#GetMapping("/")
public void get(#RequestHeader(HttpHeaders.ACCEPT) MediaType accept) {
//accept == MediaType.ALL
}
Problem:
localhost:8080/ without any Accept header results in Accept=*/* on Spring side.
Question: how can I tell Spring to not use the */* all by default, and reject requests directly without that header?

What you want (force the user to set the Accept http header),
What you catch (without any Accept header results in Accept=*/* at back-end),
What you ask (how can I tell Spring to not use the */* all by default, and reject requests directly without that header)
Answer:
You use Spring Security config. No out-of-the-box setting
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.security
Write custom filter check header, implement GenericFilterBean.
public class CustomAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
// Get header and validate from request object.
filterChain.doFilter(request, response);
}
}
Hook the filter into your security configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// other security config
.addFilterBefore(new CustomAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}

Related

How to disable specific headers in Spring Boot

Is it possible to disable following headers in Spring Boot?
X-Forwarded-Host:
X-Host:
X-Forwarded-Server:
Following did not work for me
class MyFilter extends OncePerRequestFilter {
#Override
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, new HttpServletResponseWrapper((HttpServletResponse) response) {
public void setHeader(String name, String value) {
if (!name.equalsIgnoreCase("X-Forwarded-Host")) {
super.setHeader(name, value);
}
}
});
}
Let's try to have a look broader and start to think about request-response lifecycle.
Once a request has been initiated by a client, there are sort of stops and layers that the request/response goes through between client and the application. There might be a firewall, load-balancer, reverse proxy, middleware etc. On the other hand, based on the application server which serves the application, those headers might be added as well. If there is a mechanism which adds or removes or rewrites the headers apart from the application, those headers should have been managed out of the application.
That being said, if headers were added by the application, they could have been managed within the application. But if headers were added by another stop or layer, they should have been managed in a particular configuration.
Apart from the headers in general, if we think about these specific headers: Based on my general experience, the headers you provided are added when there is a reverse proxy between client and application.
You can leverage more information about them: https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#x-headers
To sum up, you should be managing those headers according to how and why they have been added.
If you want to disable all default headers you can do the folowing:
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.headers(headers -> headers
// do not use any default headers unless explicitly listed
.defaultsDisabled()
.cacheControl(withDefaults())
);
return http.build();
}
}
To disable specific ones you can follow the same strategy.
Reference: https://docs.spring.io/spring-security/reference/5.8/servlet/exploits/headers.html#page-title

Spring Security OAuth - how to disable login page?

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

How to create servlet filter to authorize the request?

How to create spring servlet filter to authorize the request.
Need to add a filter in the spring security filter chain that updates the user with few details (re-loading permissions or anything) for every request, if they need to be changed.
Need some sample code snippet to follow or understand.
Thanks in advance.
To add a custom filter you should extend the org.springframework.web.filter.GenericFilterBean, like so:
public class MySecurityFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
}
The next step is to actually register the filter by overriding the configure method of WebSecurityConfigurerAdapter:
#Configuration
public class CustomWebSecurityConfigurerAdapter
extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(
new MySecurityFilter(), BasicAuthenticationFilter.class);
}
}
As you can see the filter is added by adding the filter to the HttpSecurity object. The method that is used is the addFilterAfter which basically allocates your filter after the one that you provide in the second argument, in this example is the BasicAuthenticationFilter, so your filter will be executed after this one, in the spring secuirty chain of filters.
Update
Refer to this link to personalize the authorization of your servlet. The method gives you an Authentication object, through which you can obtain your User object and perform additional checks.

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/**")
));

Spring Security - Token based API auth & user/password authentication

I am trying to create a webapp that will primarily provide a REST API using Spring, and am trying to configure the security side.
I am trying to implement this kind of pattern: https://developers.google.com/accounts/docs/MobileApps (Google have totally changed that page, so no longer makes sense - see the page I was referring to here: http://web.archive.org/web/20130822184827/https://developers.google.com/accounts/docs/MobileApps)
Here is what I need to accompish:
Web app has simple sign-in/sign-up forms that work with normal spring user/password authentication (have done this type of thing before with dao/authenticationmanager/userdetailsservice etc)
REST api endpoints that are stateless sessions and every request authenticated based ona token provided with the request
(e.g. user logins/signs up using normal forms, webapp provides secure cookie with token that can then be used in following API requests)
I had a normal authentication setup as below:
#Override protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/mobile/app/sign-up").permitAll()
.antMatchers("/v1/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/loginprocess")
.failureUrl("/?loginFailure=true")
.permitAll();
}
I was thinking of adding a pre-auth filter, that checks for the token in the request and then sets the security context (would that mean that the normal following authentication would be skipped?), however, beyond the normal user/password I have not done too much with token based security, but based on some other examples I came up with the following:
Security Config:
#Override protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.addFilter(restAuthenticationFilter())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()).and()
.antMatcher("/v1/**")
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/mobile/app/sign-up").permitAll()
.antMatchers("/v1/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/loginprocess")
.failureUrl("/?loginFailure=true")
.permitAll();
}
My custom rest filter:
public class RestAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public RestAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
private final String HEADER_SECURITY_TOKEN = "X-Token";
private String token = "";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
this.token = request.getHeader(HEADER_SECURITY_TOKEN);
//If we have already applied this filter - not sure how that would happen? - then just continue chain
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
//Now mark request as completing this filter
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//Attempt to authenticate
Authentication authResult;
authResult = attemptAuthentication(request, response);
if (authResult == null) {
unsuccessfulAuthentication(request, response, new LockedException("Forbidden"));
} else {
successfulAuthentication(request, response, chain, authResult);
}
}
/**
* Attempt to authenticate request - basically just pass over to another method to authenticate request headers
*/
#Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
AbstractAuthenticationToken userAuthenticationToken = authUserByToken();
if(userAuthenticationToken == null) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return userAuthenticationToken;
}
/**
* authenticate the user based on token, mobile app secret & user agent
* #return
*/
private AbstractAuthenticationToken authUserByToken() {
AbstractAuthenticationToken authToken = null;
try {
// TODO - just return null - always fail auth just to test spring setup ok
return null;
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return authToken;
}
The above actually results in an error on app startup saying: authenticationManager must be specified
Can anyone tell me how best to do this - is a pre_auth filter the best way to do this?
EDIT
I wrote up what I found and how I did it with Spring-security (including the code) implementing a standard token implementation (not OAuth)
Overview of the problem and approach/solution
Implementing the solution with Spring-security
Hope it helps some others..
I believe the error that you mention is just because the AbstractAuthenticationProcessingFilter base class that you are using requires an AuthenticationManager. If you aren't going to use it you can set it to a no-op, or just implement Filter directly. If your Filter can authenticate the request and sets up the SecurityContext then usually the downstream processing will be skipped (it depends on the implementation of the downstream filters, but I don't see anything weird in your app, so they probably all behave that way).
If I were you I might consider putting the API endpoints in a completely separate filter chain (another WebSecurityConfigurerAdapter bean). But that only makes things easier to read, not necessarily crucial.
You might find (as suggested in comments) that you end up reinventing the wheel, but no harm in trying, and you will probably learn more about Spring and Security in the process.
ADDITION: the github approach is quite interesting: users just use the token as a password in basic auth, and the server doesn't need a custom filter (BasicAuthenticationFilter is fine).

Resources