In a mixed Java/Scala Spring (v3.2) project I want to create a scala-based controller, and secure it with #Secure annotation. For java controllers this works just fine, but when I add #Secure annotation to the scala controller it disappears from URL mapping on application startup.
Sample code:
#Controller
#Secured(Array("ROLE_USER"))
class TestController {
#RequestMapping(value = Array("/show"), method = Array(RequestMethod.GET))
def show = {
"helloTemplate"
}
}
The same if I put secure annotation per method - the whole controller class will disappear from URL mapping (even if there are unsecured methods). No exceptions or warnings in the log. If I secure this URL via spring-security intercept-url in xml configuration - all things works fine (without #Secure annotation on controller). Spring and spring-security configuration via xml files and annotation-driven configuration is turned on. Thank you for any help.
In Spring 4/Spring you must enable proxyTargetClass = true to use CGLIB class proxying. This is superior to java class Proxying when working with scala.
SpringBoot
#EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
Related
I'm trying to configure springdoc-openapi-ui in a spring web app (non-springboot). In my application I have a controller that is mapped to "/" which conflicts with the Springdoc SwaggerUiHome Controller. I get an error similar to this:
java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'swaggerUiHome' method 'org.springdoc.webmvc.ui.SwaggerUiHome#index() to {GET [/]}: There is already 'loginLogoutController' bean method com.company.MyController#method() mapped.
If I disable my controller which is mapped to '/' then the swagger UI comes up as expected. However, I need to have my controller running in order for my web app to work.
Even if I set 'springdoc.swagger-ui.use-root-path=false' (which should be the default anyway) it is still causing an ambiguous mapping error.
Is there any way around this? Any way to disable the default mapping of the SwaggerUiHome.index() to '/'?
SwaggerUiHome is only invoked if you define: springdoc.swagger-ui.use-root-path=true
To disable it: springdoc.swagger-ui.use-root-path=false
Note that default path of the swagger-ui is not the root path, but: http://server:port/context-path/swagger-ui.html
I'm getting this error if using regular spring framework (not using spring-boot). After dig into the source /springdoc-openapi-starter-webmvc-ui-2.0.2.jar/org.springdoc.webmvc.ui/SwaggerConfig.class
#Bean
#ConditionalOnMissingBean
#ConditionalOnProperty(name = SPRINGDOC_USE_ROOT_PATH, havingValue = "true")
#Lazy(false)
SwaggerUiHome swaggerUiHome() {
return new SwaggerUiHome();
}
Found that String SPRINGDOC_USE_ROOT_PATH = "springdoc.swagger-ui.use-root-path". I guess there maybe a bug that this property is not working for springdoc.swagger-ui.use-root-path=false
Here is my working solution after reading https://www.baeldung.com/spring-requestmapping#4-ambiguous-mapping-error
#GetMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE)
public String index() {
return "home";
}
So I have a React app I want to serve from my Spring app (ala this blog). As part of my gradle build task, I run the npm build command and copy the resulting files to /build/resources/main/static. This works fine and I can access my app at mysite.com/index.html, but I want to control who has access more granularly. As such, I applied #EnableWebMvc to my app, but from there, I can't seem to get my API controller to actually serve the view from the build directory. It seems no matter where I put it, it doesn't like serving directly from /build. Any way to make this work?
The handler looks like:
#Controller
class MyController {
#RequestMapping("/")
fun index(): String {
return "index"
}
}
As indicated in the Spring Boot documentation, you do not need - in fact, it is not recommended - to use #EnableWebMvc when using Spring Boot. They state, when describing Spring MVC auto-configuration:
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
And:
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own #Configuration class of type WebMvcConfigurer but without #EnableWebMvc.
In the guide, they continue when describing static content handling:
By default, Spring Boot serves static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext. It uses the ResourceHttpRequestHandler from Spring MVC so that you can modify that behavior by adding your own WebMvcConfigurer and overriding the addResourceHandlers method.
In your example, following this advice, you can indicate the static resource handling location with something like (sorry, I am not fluent in Kotlin, forgive for write the example in Java):
#Controller
public class MyController implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static")
;
}
#GetMapping(path = "/")
public String index() {
return "index";
}
}
Please, adapt the paths in addResourceHandlers to your needs.
You can of course place this method in an ad hoc #Configuration.
Having said that, if when you say granular you mean security, the best approach you can take is to configure Spring Security and provide the necessary authorization rules: please, see the relevant documentation.
I'm working on upgrading an Spring Framework 4-based application to Spring Framework 5, and one of the differences between them is that ControllerClassNameHandlerMapping has been removed(deprecated since spring framework 4.3).
An example of its behavior:
#Controller
// thanks to ControllerClassNameHandlerMapping, this next annotation is unnecessary
// #RequestMapping("example")
public class ExampleController {
// this is automatically bound to POST /example/someMethod
// even though I didn't set any "path"
#RequestMapping(method = RequestMethod.POST)
public void someMethod() {
}
Is there any way to mimick this behavior on spring framework 5? Upgrading over 300 controllers to the same behavior using #RequestMapping and path is a very daunting task, and we're fine with ControllerClassNameHandlerMapping's behavior for now.
You can update context path in your configuration:
server:
servlet:
context-path: /example
I am developing a service(not a web application) using Spring-3.1.0.GA. I want to use hibernate-validator along with Spring to validate my service inputs.
I have enabled the bean validation support with:
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
I have annotated my service interface with #Validated and method parameters with #NotNull, #Size, #Valid etc and it is working fine.
But I have to validate my parameter, say Customer, based on validation group(s).
If I annotate method parameter with #Validated or #Validated(Group1.class) Spring is not performing any validation.
If I annotate with #Valid then validations are happening but with Default validation group(as expected).
I can't annotate #Validate(Group1.class) at interface level because various methods operate on various groups.
How can I perform service layer validations using Spring and hibernate-validator with groups support?
I have gone through Spring Source code and why the validation groups on method params are not being picked...As per the code it is picking the validation groups configuration only from #Validated on interface level, not on method level.
But I haven't gone through how it is working in SpringMVC context, I haven't checked yet.
Also in this process I came to know If you configure MethodValidationPostProcessor in child application context it is not picked up by Spring. I mean if you are using SpringMVC, MethodValidationPostProcessor will be registered only if you configure it in [servlet-name]-servlet.xml. If you configure in any child context configuration files that are picked by ContextLoaderListener Spring won't register MethodValidationPostProcessor. Also I am not sure it is only for MethodValidationPostProcessor or for any BeanPostProcessors also.
I get the following code working, I need to attach the #Validated at the method header and #Valid in the method parameters.
#Validated(Default.class)
public UserDto updateUser(#Valid UserDto userDto) {
In Spring 3.1.0+ you can use groups directly in #Valid annotation. E.g.:
#Valid(groups={Default.class, Group1.class})
Can Spring Security use #PreAuthorize on Spring controllers methods?
Yes, it works fine.
You need <security:global-method-security pre-post-annotations="enabled" /> in ...-servlet.xml. It also requires CGLIB proxies, so either your controllers shouldn't have interfaces, or you should use proxy-target-class = true.
See Spring Security FAQ (emphasis mine).
In a Spring web application, the application context which holds the
Spring MVC beans for the dispatcher servlet is often separate from the
main application context. It is often defined in a file called
myapp-servlet.xml, where “myapp” is the name assigned to the Spring
DispatcherServlet in web.xml. An application can have multiple
DispatcherServlets, each with its own isolated application context.
The beans in these “child” contexts are not visible to the rest of the
application. The “parent” application context is loaded by the
ContextLoaderListener you define in your web.xml and is visible to all
the child contexts. This parent context is usually where you define
your security configuration, including the
element). As a result any security constraints applied to methods in
these web beans will not be enforced, since the beans cannot be seen
from the DispatcherServlet context. You need to either move the
declaration to the web context or moved the
beans you want secured into the main application context.
Generally we would recommend applying method security at the service
layer rather than on individual web controllers.
If you apply pointcuts to service layer you only need to set <global-method-security> in your app's security context.
If you're using Spring 3.1, you can do some pretty cool stuff with this. Take a look at https://github.com/mohchi/spring-security-request-mapping. It's a sample project that integrates #PreAuthorize with Spring MVC's RequestMappingHandlerMapping so that you can do something like:
#RequestMapping("/")
#PreAuthorize("isAuthenticated()")
public String authenticatedHomePage() {
return "authenticatedHomePage";
}
#RequestMapping("/")
public String homePage() {
return "homePage";
}
A request for "/" will call authenticatedHomePage() if the user is authenticated. Otherwise it will call homePage().
It's over two years since this question was asked but because of problems I had today I'd rather discourage using #Secured, #PreAuthorize, etc. on #Controllers.
What didn't work for me was #Validated combined with #Secured controller:
#Controller
#Secured("ROLE_ADMIN")
public class AdministrationController {
// #InitBinder here...
#RequestMapping(value = "/administration/add-product", method = RequestMethod.POST)
public String addProductPost(#ModelAttribute("product") #Validated ProductDto product, BindingResult bindingResult) {
// ...
}
Validator simply does not fire (Spring MVC 4.1.2, Spring Security 3.2.5) and no checks are performed.
Similar problems are caused by CGLIB proxies used by Spring (when there is no interface implemented by a class, Spring creates CGLIB proxy; if the class implements any interface then JDK Proxy is generated - documentation, well explained here and here).
As mentioned in the answers that I linked above, is't better to use Spring Security annotations on service layer that usually implements interfaces (so JDK Proxies are used) as this does not lead to such problems.
If you want to secure web controllers, the better idea is to use <http> and <intercept-url /> that are bound to specific urls rather than methods in controllers and work pretty well. In my case:
<http use-expressions="true" disable-url-rewriting="true">
...
<intercept-url pattern="/administration/**" access="hasRole('ROLE_ADMIN')" />
</http>
There is already a reply regarding how to make it work by changing xml configuration; however, if you are working with code-based configuration, you can achieve the same by placing the following annotation over your #Configuration class:
#EnableGlobalMethodSecurity(prePostEnabled=true)
To Extend the answer provided by Andy, you can use:
#PreAuthorize("hasRole('foo')")
to check the specific role.
step1: add #EnableGlobalMethodSecurity(prePostEnabled = true) annotation in SecurityConfig class .
like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
.....
}
step 2: you can add #PreAuthorize() annotation in controller or service or repository layer. in a method or class level.
for example:
#RestController
#PreAuthorize("isAuthenticated()")
public class WebController {
#PreAuthorize("permitAll()")
#GetMapping("/")
public String home() {
return "Welcome home!";
}
#GetMapping("/restricted")
public String restricted() {
return "restricted method";
}
}
or
#RestController
public class AdminController {
#PreAuthorize("hasRole('ADMIN')")
#GetMapping("/admin")
public String adminMethod() {
}
}
First you need to add this annotation in your WebSecurityConfig to enable #Pre and #Post annotations.
#EnableGlobalMethodSecurity(prePostEnabled = true)
You can also check roles/authorities as follows
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
equivalent to
#PreAuthorize("hasRole('ROLE_ADMIN')")
You can also check multiple roles/authorities as follows
#PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER') or ...")