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");
}
// ...
}
Related
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
I have this fonts
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/**").hasRole("myRole")
.anyRequest().authenticated()
.and()
.oauth2Login(Customizer.withDefaults());
}
I know "hasRole" looks at permissions inside securityContext.authentication.authorities but is there a way for "hasRole" to another place?
My roles are inside securityContext.authentication.principal.attributes.role :
https://i.stack.imgur.com/yl6bC.png
I even created an endpoint that returns if the role I want exists, but I don't know how it would help me inside the "configure" method:
public boolean isAllowed() {
UserAttributesDTO user = getUser();
if (nonNull(user)) {
return user.getRoles().stream().anyMatch(role -> role.equals("admin_cadastro_externo"));
}
return false;
}
Since you're using the built-in oauth2Login() and your principal is a DefaultOidcUser, you want to use .hasAuthority("myAuthority") instead. You can influence what authorities are present by providing a GrantedAuthoritiesMapper as an #Bean.
If you must access the attributes in place, you may be interested in .access(...) using an #Bean reference.
See Referring to Beans in Web Security Expressions. In that case, you should use the authentication passed to the method instead of the SecurityContextHolder to access the authentication.
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.
I have developed a regular spring mvc application, and want to add some rest controller for developing mobile application. I have written rest controller, and multi spring security configurations.
Problem is, they are in precedence, hence both are loaded at once, and whole application breaks down.I want to use one based upon what type of request it is getting, for example, If I am requesting from Postman, Rest API security configuration should work and if we are using web, web security configuration should work.
Here is my implementation, I don't know how to achieve that, Please suggest what is the right way to doing this. As separating whole Thymeleaf and MVC controller , and moving altogether with Angular is not possible at this stage.
Please note that, we have all rest api defined in /v1/ap1/** and all other mvc part is in /**
Any comments, suggestions would be much appreciated, it is killing my days since 3 days. Thanks in advance
#Configuration
#EnableWebSecurity
public class SecurityConfig {
// ... other codes
#Configuration
#Order(1)
public static class RestAPISecurity extends WebSecurityConfigurerAdapter {
//.. other codes
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/signin/**").permitAll()
.antMatchers("/api/v1/**").hasAnyAuthority("ADMIN", "USER")
.antMatchers("/api/users/**").hasAuthority("ADMIN")
.antMatchers("/api/v1/**").authenticated()
.antMatchers("/login", "/logout", "/register", "/j_spring_security_check").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint).accessDeniedHandler(new CustomAccessDeniedHandler());
}
// .. other codes
#Configuration
#Order(2)
public static class MVCSecurityConfiguration extends WebSecurityConfigurerAdapter {
//.. other codes
// form login and other MVC stuffs
}
}
You can add a request matcher for the first spring security filter chain and every thing else goes to second chain
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(httpServletRequest -> {
String userAgent = httpServletRequest.getHeader("User-Agent");
//If you want to check based on content type
String contentType = httpServletRequest.getContentType();
return userAgent.contains("....")
//check what value postman sends as user agent and use it
})
.sessionManagement()
....
}
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.