How to setup Spring Security's Authorization Config for Method Security? - spring

It is Spring Security's recommendation to deny all requests with missing authorization rules by default:
http
.authorizeRequests((authorize) -> authorize
.filterSecurityInterceptorOncePerRequest(true)
.mvcMatchers("/app/**").hasRole("APP")
// ...
.anyRequest().denyAll()
)
// ...
Now let's apply this to a simple real-world example.
The goal is to define 3 endpoints with different access permissions:
"/": homepage, everyone can access
"/login": only anonymous users can access
"/private": only logged in users with the authority "ROLE_USER" can access
All other pages should automatically be considered as not allowed as long as no explicit permission has been set via annotation at the endpoint method.
The controller therefore looks as follows:
#Controller
#RequestMapping
public class MainController {
#GetMapping
public String index() { return "index"; }
#GetMapping("/login")
#PreAuthorize("isAnonymous()")
public String login() { return "login"; }
#GetMapping("/private")
#PreAuthorize("hasAuthority('ROLE_USER')")
public String secrets() { return "private"; }
}
According to Spring Security's suggestion, the WebSecurityConfig looks like this:
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
#Bean
public SecurityFilterChain mainFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequestsauthorize -> authorize
.anyRequest().denyAll())
// ...
}
This configuration should declare all endpoints as restricted by default and since method declarations have a higher precedence, permissions on the methods should override accordingly.
But that is not what happens. With this approach, all endpoints are forbidden and the method annotations are not considered.
The following customization of the authorization config object respects the method annotations but ignores denyall():
#Bean
public SecurityFilterChain mainFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequestsauthorize -> authorize
.mvcMatchers("/**").permitAll()
.anyRequest().denyAll())
// ...
}
What sense does it make if you have to give permissions both in the filterchain and on the method itself, without the possibility to declare new endpoints as forbidden by default? With this approach, any new endpoint that is not explicitly restricted on method level is completely open to any user.
So my question is:
How do I configure the SecurityFilterChain in a way that all endpoints, and thus also all new endpoints, are automatically considered as forbidden, and access is exclusively set via method annotations?
Many thanks to all of you who can help to clarify the situation

Related

Restrict the Rest API Methods except GET on a Spring Boot Project

I made a Rest API project with Spring Boot. There are every standard methods like get, post, put and delete in the Controller.
My aim is to make it possible for me to only be able to access the api calls (except get calls) via my angular app. Other methods (post, put and delete) can not be accessible from outside.
I tried to solve this problem with WebSecurityConfigurerAdapter and configure function but I couldn't get it.
When I first imported the security dependency (spring-boot-starter-security) on pom.xml, then all methods were blocked. I tried to permit the get calls in configure method but then I could not make a post call with basic auth over postman. Everytime I got 403 Forbidden error.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers("/h2-console/**")
.antMatchers("/login/**");
}
}
By the way, I wanted to make my own username and passwort for spring security in the application.properties file. But I think that doesn't work if I use a SecurityConfig configuration file.
spring.security.user.name=myUsername
spring.security.user.password=myPassword
Regardless of my attempts, how can I actually get this from shortest and easiest way?
Then how can I call the blocked methods (post, put, delete) from my angular application?
Thanks.
If I'm not mistaken, you want your project to have no access restrictions for GET methods and everyone should have access to this method type.
All remaining requests (post, put, delete, etc.) can be accessed with an authentication.
You can achieve this as follows. Assuming you have a controller like below:
#RestController
#RequestMapping("security")
public class SecurityController {
#GetMapping("get")
public ResponseEntity<String> get() {
return ResponseEntity.ok("Get Method");
}
#PostMapping("post")
public ResponseEntity<String> post() {
return ResponseEntity.ok("Post Method");
}
#PutMapping("put")
public ResponseEntity<String> put() {
return ResponseEntity.ok("Put Method");
}
#DeleteMapping("delete")
public ResponseEntity<String> delete() {
return ResponseEntity.ok("delete");
}
}
In this case your WebSecurityConfigurer should be like below:
#EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.GET).permitAll()
.anyRequest().authenticated().and().httpBasic();
}
}
The first thing to do here is to determine that GET, which is an http method, can access without any authorization. It then authorizes the accesses of the remaining HttpMethod's. Finally, we specify that we are using Basic Auth with httpBasic(). This information consists of username and password information defined in your application.properties file.
You can see the difference between HttpSecurity and WebSecurity by examining the question here.
I hope this answer helps you.

How to protect the same resource using both spring-session and spring-security-oauth

I have a requirement to use two kinds of authentication,
for web we #EnableRedisHttpSession and for other consumers like mobile we use #EnableAuthorizationServer with #EnableResourceServer.
suppose we try to protect a controller common to both the authentication mechanisms for e.g /api/v1/test
i have hit a roadblock.
i am only able to use one kind of authentication scheme
if i set #WebSecurityConfigurerAdapter #order(2) and #ResourceServerConfigurerAdapter #order(3) then i can only access the resource via web
and if i set #ResourceServerConfigurerAdapter #order(2) and #WebSecurityConfigurerAdapter #order(3) then only OAuth works.
i am unable to use both the mechanism at the same time.how can we make the two work together, for e.g if the request comes from web use the filter responsible for that and if the request comes from mobile use the appropriate filter. web uses cookies and API Consumers use Authorization : Bearer header.
please help
It's sounds very strange. I suggest you to review how REST API is used and why it should be used by browser users. Better to separate web views and REST API, don't mix it.
However, answering your question "can I use two kinds of authentication for some URI at once" - yes, you can.
You need custom RequestMatcher that will decide how to route incoming request.
So:
for "API consumers" - check existence of Authorization header
contains "Bearer"
for "browser users" - just inverse first rule
Code example:
public abstract class AntPathRequestMatcherWrapper implements RequestMatcher {
private AntPathRequestMatcher delegate;
public AntPathRequestMatcherWrapper(String pattern) {
this.delegate = new AntPathRequestMatcher(pattern);
}
#Override
public boolean matches(HttpServletRequest request) {
if (precondition(request)) {
return delegate.matches(request);
}
return false;
}
protected abstract boolean precondition(HttpServletRequest request);
}
OAuth2 authentication
#EnableResourceServer
#Configuration
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcherWrapper("/api/v1/test") {
#Override
protected boolean precondition(HttpServletRequest request) {
return String.valueOf(request.getHeader("Authorization")).contains("Bearer");
}
}).authorizeRequests().anyRequest().authenticated();
}
}
Web authentication
#Configuration
#EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcherWrapper("/api/v1/test") {
#Override
protected boolean precondition(HttpServletRequest request) {
return !String.valueOf(request.getHeader("Authorization")).contains("Bearer");
}
}).authorizeRequests().anyRequest().authenticated();
}
}
Using this configuration it's possible to use two different authentication types for one URI /api/v1/test.
In addition, I highly recommended to read the article about Spring Security architecture by Dave Syer, to understand how does it work:
https://spring.io/guides/topicals/spring-security-architecture/#_web_security

Spring #RestController single method for anonymous and authorized users

I have a following Spring RestController:
#RestController
#RequestMapping("/v1.0/tenants")
public class TenantController {
#Autowired
private TenantService tenantService;
#RequestMapping(value = "/{tenantId}", method = RequestMethod.GET)
public TenantResponse findTenantById(#PathVariable #NotNull #DecimalMin("0") Long tenantId) {
Tenant tenant = tenantService.findTenantById(tenantId);
return new TenantResponse(tenant);
}
}
findTenantById method should be accessed by anonymous and authorized users. In case of anonymous user SecurityContextHolder.getContext().getAuthentication() must return NULL or AnonymousAuthenticationToken but in case of authorized - Authentication object.
In my application I have implemented security model with OAuth2 + JWT tokens.
This my config:
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.antMatcher("/v1.0/**").authorizeRequests()
.antMatchers("/v1.0/tenants/**").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(STATELESS);
// #formatter:on
}
Also, for secure endpoints I'm applying #PreAuthorize annotation where needed but not in case of findTenantById because as I said previously, I need to grant access to this endpoint for anonymous and authorized users. Inside of endpoint business logic I'll decide who will be able to proceed based on different conditions.
Right now even I have provided my accessToken for this endpoint I can't get an authenticated User object from SecurityContextHolder.getContext().getAuthentication().
How to configure this endpoint in order to be working in a way described above ?
I think I have found a solution - I have annotated my method with:
#PreAuthorize("isAnonymous() or isFullyAuthenticated()")
Please let me know if there is any better solutions.

spring security permitAll still considering token passed in Authorization header and returns 401 if token is invalid

I am using spring security oauth in my project. I am excluding some urls from authentication by configuring in spring security ResourceServerConfigurerAdapter. I added http.authorizeRequests().antMatchers(url).permitAll().
Now, what I am seeing is that, if I don't pass the Authorization header to these urls, it is not authenticated. And the API is called properly.
If the call is made with an Authorization header, then it validates the token and fails the call if the token is not validated.
My question is what do I need to do so that the token is ignored in the request for which I have permitAll.
Spring OAuth2 will intercept all url with header: Authorization Bearer xxx.
To avoid Spring OAuth2 from intercept the url. I have created a SecurityConfiguration which has higher order than Spring OAuth2 configuration.
#Configuration
#EnableWebSecurity
#Order(1) // this is important to run this before Spring OAuth2
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
List<RequestMatcher> requestMatchers = new ArrayList<RequestMatcher>();
// allow /api/public/product/** and /api/public/content/** not intercepted by Spring OAuth2
requestMatchers.add(new AntPathRequestMatcher("/api/public/product/**"));
requestMatchers.add(new AntPathRequestMatcher("/api/public/content/**"));
http
.requestMatcher(new OrRequestMatcher(requestMatchers))
.authorizeRequests()
.antMatchers("/api/public/product/**", "/api/public/content/**").permitAll()
}
}
The above configuration allows /api/public/product/** and /api/public/content/** to be handled by this configuration, not by Spring OAuth2 because this configuration has higher #Order.
Therefore, even setting invalid token to above api call will not result in invalid access token.
As per spring-oauth2 docs https://projects.spring.io/spring-security-oauth/docs/oauth2.html
Note: if your Authorization Server is also a Resource Server then there is another security filter chain with lower priority controlling the API resources. Fo those requests to be protected by access tokens you need their paths not to be matched by the ones in the main user-facing filter chain, so be sure to include a request matcher that picks out only non-API resources in the WebSecurityConfigurer above.
So define WebSecurityConfigurer implementation with higher order than ResourceServerConfig.
In case you are dealing with Reactive Spring webflux, from SooCheng Koh's answer.
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
#Order(1) // this is important to run this before Spring OAuth2
public class PublicSecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/api/public/**").permitAll();
return http.build();
}
}
It's not a bug it's a feature :)
As already mentioned by other people, even if you have permitAll, Spring Security will still check the token if there is a header "Authorization".
I don't like the workaround on the backend with Order(1) so I did a change on the frontend simply removing the header "Authorization" for the specific request.
Angular example with interceptor:
#Injectable()
export class PermitAllInterceptor implements HttpInterceptor {
constructor() {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if(req.url.includes('permitAllUrl')){
req = req.clone({ headers: req.headers.delete('Authorization') });
}
return next.handle(req);
}
}
and then just register the interceptor in app.module.ts:
{
provide: HTTP_INTERCEPTORS,
useClass: PermitAllInterceptor ,
multi: true
}

Deny access to some inherited controller methods

I have a base REST controller that provides general methods (GET, PUT, POST, DELETE).
Then specific controllers inherit these methods and either use them or override them. When such a subclass controller override an inherited controller method, I sometimes needs to add for example #PreAuthorize("hasAnyRole('ROLE_ADMIN')") to restrict the access to some security roles.
However, now I have another subclass controller for which I need to allow access only to the GET inherited methods. All other inherited methods (PUT, POST, DELETE) should be forbidden to absolutely everyone.
I tried to Override the DELETE with an empty #PreAuthorize:
#PreAuthorize
#Override
#RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
#ResponseStatus(HttpStatus.NO_CONTENT)
#ResponseBody
public void removeResource(#PathVariable("id") final Long id, final Principal principal) throws UnknownResourceException {
// Deny access to everyone
}
However, with no value, it's not a valid annotation.
Is there a usual pattern to solve this issue ?
Spring recommends to secure your Mapping Handler with HttpSecurity:
In practice we recommend that you use method security at your service
layer, to control access to your application, and do not rely entirely
on the use of security constraints defined at the web-application
level. URLs change and it is difficult to take account of all the
possible URLs that an application might support and how requests might
be manipulated. You should try and restrict yourself to using a few
simple ant paths which are simple to understand. Always try to use
a"deny-by-default" approach where you have a catch-all wildcard ( /**
or **) defined last and denying access.
So for example it is secured using URL security and can be differentiate by Http method:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// ...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/myurl").hasRole("ADMIN")
.antMatchers(HttpMethod.GET, "/myurl/**").hasRole("USER")
.antMatchers(HttpMethod.DELETE, "/myurl/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/myurl/**").hasRole("USER");
}
// ...
}

Resources