I'm using spring boot with spring security
I have rest controller
#RequestMapping("/foo")
public String foo(#RequestBody Foo foo) {
return foo.getBar();
}
And I've added Spring security to this endpoint
.mvcMatchers("/foo").access("#securityChecker.check(#foo)")
Now I have this security checker
#Service
public class SecurityChecker {
public boolean check(Foo foo) {
return foo != null;
}
}
the problem is that Foo is always null.
I'm guessing that it's because Jackson's filter is after Security one. Is there any way to get request body object without injecting HttpRequest object to "check" method and parsing request body? I would like to maybe have security checks after parsing JSON from the request's body.
Here is quick example of what I'm trying to do:
https://github.com/kedrigen/spring-security-request-body
You are missing #RequestBody (docs):
#PostMapping("/foo") // has to be post mapping
public String foo(#RequestBody Foo foo) {
return foo.getBar();
}
This annotation is used to have the request body read and deserialized into an Object through an HttpMessageConverter.
You are also missing #EnableWebSecurity:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { ... }
But the problem, in general, is you cannot simply do what you are trying to do.
foo that you expect in your controller has nothing to do with security and security context, so this "#securityChecker.check(#foo)" won't work.
Consider getting familiar with Referring to Beans in Web Security Expressions documentation.
Example from the docs:
public class WebSecurity {
public boolean check(Authentication authentication, HttpServletRequest request) {
...
}
}
http
.authorizeRequests(authorize -> authorize
.antMatchers("/user/**").access("#webSecurity.check(authentication,request)")
...
)
In a nutshell: this works, because Spring is aware of what authentication and request are and they exist in the context. But foo means nothing to Spring :)
Related
I have a spring boot app and want to create a Feign client which has a statically defined header value (for auth, but not basic auth). I found the #Headers annotation but it doesn't seem to work in the realm of Spring Boot. My suspicion is this has something to do with it using the SpringMvcContract.
Here's the code I want to work:
#FeignClient(name = "foo", url = "http://localhost:4444/feign")
#Headers({"myHeader:value"})
public interface LocalhostClient {
But it does not add the headers.
I made a clean spring boot app with my attempts and posted to github here: github example
The only way I was able to make it work was to define the RequestInterceptor as a global bean, but I don't want to do that because it would impact other clients.
You can also achieve this by adding header to individual methods as follows:
#RequestMapping(method = RequestMethod.GET, path = "/resource", headers = {"myHeader=value"})
Using #Headers with dynamic values in Feign client + Spring Cloud (Brixton RC2) discusses a solution for dynamic values using #RequestHeader.
You can set a specific configuration class on your feign interface and define a RequestInterceptor bean in there. For example:
#FeignClient(name = "foo", url = "http://localhost:4444/feign",
configuration = FeignConfiguration.class)
public interface LocalhostClient {
}
#Configuration
public class FeignConfiguration {
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
// Do what you want to do
}
};
}
}
You could specify that through the application.yml file:
feign:
client:
config:
default:
defaultRequestHeaders:
Authorization:
- Basic 3ncond2dS3cr2t
otherHeader:
- value
Note that this will be applicable to all your Feign Clients if it happened that you're using more than one. If that's the case, you could add a section per client instead of adding this to the default section.
Try this
#Component
public class AuthFeignInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
final HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
template.header("Header_name","Value");
}
}
}
I am using Spring Security in my application. I am authenticating APIs based on the role (ADMIN, USER).
There is one API endpoint which I would like to restrict access using the value of a variable passed as parameter to it.
I have
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().exceptionHandling().authenticationEntryPoint(this.unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll();
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
I have a call
#PostMapping("/something")
public ResponseEntity<BotResponse> handleRequest(#Valid #RequestBody SomeClass someClass) {
// if someClass.getSomeValue() is not present in the User permissions, then it should give an unauthorized response.
return value(someClass);
}
The User in Spring Security is :
public Class User {
String userId;
String userName;
String authorities;
List<String> someList;
//Getters and setters for variables
}
And the SomeClass used is :
public Class SomeClass {
String someValue;
String userName;
...
// Getters and Setters
}
How do I not allow users based on if the value of someClass.getSomeValue is present in User's someList?
As per your question, one approach would be to get the UserDetails stored in your Spring Security Authentication Context and then check the concerned data in this context object against the value passed as the parameter. I'm assuming that you have all the required values stored in the Security Context.
This check can be done in the endpoint code itself(if you have a small number of such APIs). If there are multiple APIs that need the same logic, you will have to implement either a filter that filters only these API(config can be written in web.xml) or a pointcut(through AOP).
Perhaps you could do such kind of authorization with spring's global method security.
To use Method Level Authorization you need to add the following annotation to your Security Configuration class.
#EnableGlobalMethodSecurity(prePostEnabled = true)
Then apply #PreAuthorize using Spring Expression Language, to your end point. Something like..
#PostMapping("/something")
#PreAuthorize("#someService.checkUserAccess(principal, #someClass)")
public ResponseEntity<BotResponse> handleRequest(#Valid #RequestBody SomeClass someClass) {
// if someClass.getSomeValue() is not present in the User permissions, then it should give an unauthorized response.
return value(someClass);
}
#someService is a Bean which you would autowired in the Controller and define checkUserAccess() method in this been. Something like ..
public boolean checkUserAccess(Pricipal principal, SomeClass someClass) {
// here you can fetch your full user object from db or session (depending on your application architecture)
// apply what ever logic you want to apply, return true if user has access and false if no.
}
Note / Suggestion- You may add this checkUserAccess() method to your existing user service if your application design allows it, and autowire user service in the controller.
I've got an application which serves some web content via Spring MVC and also some JSON stuff under the same URI.
#Controller
public class SomeController {
#RequestMapping(value = {"/someUri"}, method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
public String getView() {
return "index.html";
}
#RequestMapping(path = "/someUri", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String getJson() {
return "{ \"some\": \"json\" }";
}
And now I want to secure only the REST API which produces the MediaType.APPLICATION_JSON_VALUE with Spring Security.
When I add the #PreAuthorize or #Secured annotation on each method it works fine. But I want to configure it globally, like in the WebSecurityConfiguration#configure(HttpSecurity http) method. Is it possible to secure in any way globally an endpoint which produces a specific media type?
You could use MediaTypeRequestMatcher:
Allows matching HttpServletRequest based upon the MediaType's resolved from a ContentNegotiationStrategy. By default, the matching process will perform the following:
The ContentNegotiationStrategy will resolve the MediaType's for the current request
Each matchingMediaTypes that was passed into the constructor will be compared against the MediaType instances resolved from the ContentNegotiationStrategy.
If one of the matchingMediaTypes is compatible with one of the resolved MediaType returned from the ContentNegotiationStrategy, then it returns true
For example, consider the following example
GET /
Accept: application/json
ContentNegotiationStrategy negotiationStrategy = new HeaderContentNegotiationStrategy()
MediaTypeRequestMatcher matcher = new MediaTypeRequestMatcher(negotiationStrategy, MediaType.APPLICATION_JSON);
assert matcher.matches(request) == true // returns true
AFAIK Spring security does not have to do anything with the media type produced by the url. The security constraints are applied to URL patterns. When you talk about #PreAuthorized and #Secured, I assume you are looking for a global authorization mechanism. Yes, you can do something like that
#Configuration
#EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN");
}
...
}
But it is a good idea to move all of your rest apis that require authorization to some sort of sub domain like /secure/** so that you can apply the security to a single pattern directly. Otherwise, you need to register all patterns one by one.
I'm using the latest Spring Data Rest and I'm handling the event "before create". The requirement I have is to capture also the HTTP Headers submitted to the POST endpoint for the model "Client". However, the interface for the RepositoryEventHandler does not expose that.
#Component
#RepositoryEventHandler
public class ClientEventHandler {
#Autowired
private ClientService clientService;
#HandleBeforeCreate
public void handleClientSave(Client client) {
...
...
}
}
How can we handle events and capture the HTTP Headers? I'd like to have access to the parameter like Spring MVC that uses the #RequestHeader HttpHeaders headers.
You can simply autowire the request to a field of your EventHandler
#Component
#RepositoryEventHandler
public class ClientEventHandler {
private HttpServletRequest request;
public ClientEventHandler(HttpServletRequest request) {
this.request = request;
}
#HandleBeforeCreate
public void handleClientSave(Client client) {
System.out.println("handling events like a pro");
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements())
System.out.println(names.nextElement());
}
}
In the code given I used Constructor Injection, which I think is the cleanest, but Field or Setter injection should work just as well.
I actually found the solution on stackoverflow: Spring: how do I inject an HttpServletRequest into a request-scoped bean?
Oh, and I just noticed #Marc proposed this in thecomments ... but I actually tried it :)
I have a use case where my application hosts REST API and web application and we need to add custom header to REST APIs only. REST APIs are enabled through Spring Data REST. Typically we could use Servlet Filter to achieve this but we need code the logic of isolating requests to our REST API and add the custom headers. It would be nice if Spring Data REST API allows to add a default header to all the responses it generates. What are your thoughts? Don't say I am lazy :)
For folks looking for actual implementation details..
Interceptor
public class CustomInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("adding CORS headers.....");
response.addHeader("HEADER-NAME", "HEADER-VALUE");
return true;
}
}
Java Configuration
#Configuration
public class RepositoryConfig extends
RepositoryRestMvcConfiguration {
#Override
public RequestMappingHandlerMapping repositoryExporterHandlerMapping() {
RequestMappingHandlerMapping mapping = super
.repositoryExporterHandlerMapping();
mapping.setInterceptors(new Object[] { new CustomInterceptor() });
return mapping;
}
}
As Spring Data REST is built on top of Spring MVC, the easiest way is to configure a custom HandlerInterceptor as described in the reference documentation.
With Spring Data REST the easiest way is to extend RepositoryRestMvcConfiguration and override repositoryExporterHandlerMapping, call the parent method and then invoke ….setInterceptors(…) on it.
Finally I managed to make the setup of custom interceptor working also on spring-data-rest 2.4.1.RELEASE.
#Configuration
public class RestMvcConfig extends RepositoryRestMvcConfiguration {
#Autowired UserInterceptor userInterceptor;
#Autowired ApplicationContext applicationContext;
#Override
public DelegatingHandlerMapping restHandlerMapping() {
RepositoryRestHandlerMapping repositoryMapping = new RepositoryRestHandlerMapping(resourceMappings(), config());
repositoryMapping.setInterceptors(new Object[] { userInterceptor }); // FIXME: not nice way of defining interceptors
repositoryMapping.setJpaHelper(jpaHelper());
repositoryMapping.setApplicationContext(applicationContext);
repositoryMapping.afterPropertiesSet();
BasePathAwareHandlerMapping basePathMapping = new BasePathAwareHandlerMapping(config());
basePathMapping.setApplicationContext(applicationContext);
basePathMapping.afterPropertiesSet();
List<HandlerMapping> mappings = new ArrayList<HandlerMapping>();
mappings.add(basePathMapping);
mappings.add(repositoryMapping);
return new DelegatingHandlerMapping(mappings);
}
}
I had to override the restHandlerMapping method, copy-paste it's content and add a line repositoryMapping.setInterceptors for adding custom interceptor, in my case the UserInterceptor.
Is there any better way?