I've been banging my head over this one for a while now. I've done everything I could in order to find an appropriate solution and followed a lot of Stackoverflow examples and solutions.
First, I'm using annotation based solution. When I annotate my services, prePostEnabled works, but not when I annotate the controllers, it doesn't. Also, even on my services, jsr250Enabled doesn't work.
I've found a lot of case closed by moving the annotation from the security config to the MVC config, which in my case doesn't work.
I've a setup that looks like this: https://github.com/spring-projects/spring-security-oauth-javaconfig/tree/master/samples/oauth2-sparklr
But I use Servlet 3.0 and doesn't have anything in my web.xml.
My SecurityInitializer looks like this:
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
My MVC initializer looks like this:
public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{WebSecurityConfig.class, MethodSecurityConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{SpringMvcConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{ApiPaths.API + "/*", "/res.jsp"};
}
My WebSecurity config is initialized like this:
#Configuration
#EnableWebSecurity
#ComponentScan(value = {"com.roler.res.**.server"}, excludeFilters = {
#Filter(type = FilterType.ASSIGNABLE_TYPE, value = SpringMvcConfig.class),
#Filter(type = FilterType.ASSIGNABLE_TYPE, value = MethodSecurityConfig.class),
#Filter(type = FilterType.REGEX, pattern = "com.xyz.*.controller.*")})
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
And my SpringMvcConfig is initialized like this:
#Configuration
#EnableWebMvc
#ComponentScan(value = "com.xyz.**.controller")
public class SpringMvcConfig extends WebMvcConfigurerAdapter {
If you have any ideas, I'm out of juice, Thanks!
The symptoms you describe make me think to a problem of proxying. Annotations works fine on service layer, because services generally implements interfaces, and Spring can easily use a JDK proxy to put the AOP authorizations.
But controllers generally do not implement interfaces. That's the reason why PreAuthorize annotation are more frequently used in service layer. IMHO, you'd better try to use URL pattern based authorization instead of PreAuthorize annotations on controller. The alternative would be to use target class proxying with CGLIB.
To use PreAuthorize and JSR-250 annotations, you must
annotate you spring security configuration class with :
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
if you use anywhere else in your application Spring AOP with JDK proxies, make all controller classes in which you want to use method security implement interfaces declaring all protected methods
if you use anywhere else in your application Spring AOP with CGLIB proxies, add proxyTargetClass = true to #EnableGlobalMethodSecurity :
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true,
proxyTargetClass = true)
if you want to use CGLIB proxies with Spring version under 3.2, add CGLIB library to your classpath (CGLIB classes are included in Spring 3.2+)
avoid mixing CGLIB and JDK proxying as it is not recommended by Spring documentation : Multiple sections are collapsed into a single unified auto-proxy creator at runtime, which applies the strongest proxy settings that any of the sections (typically from different XML bean definition files) specified. This also applies to the and elements.
To be clear: using 'proxy-target-class="true"' on , or elements will force the use of CGLIB proxies for all three of them.
But anyway, my advice is to try to move method security to service layer which normally already supports AOP.
Two things I noticed (as mentioned on this thread):
prePostEnabled in the annotation to enable Pre/Post annotations
use of CGLib proxies (Serge mentioned this too)
Does your #EnableGlobalMethodSecurity have those two attributes?
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
Related
public class Startup
{
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
loggerFactory.AddFile(logFilePath1);
services.AddSingleton<ILoggerFactory>(loggerFactory);
loggerFactory.AddFile(logFilePath2);
services.AddSingleton<ILoggerFactory>(loggerFactory);
}
}
With in the startup.cs class, I create two loggers . Since it has two loggers how can I set the Ilogger data in the controller? can it do using normal way? Or is there any different way to pass logger filename when logged within the controller?
OK, so you want to have two different loggers in a single controller and you want these two loggers to log to different files. The .NET Core logging does not have good support for this scenario so it requires a bit of hacking to achieve this. Whenever I find myself in a situation where I get a a lot of resistance from the framework I'm using I reconsider if what I'm trying to do is good idea and if it is whether I should use another framework so you might want to do the same. With that in mind here is a way to achieve what you want.
Loggers can be identified by a category. In your case you want a single controller to have two different loggers so you have to use ILoggerFactory to create the loggers (you could use the generic ILogger<T> interface but it becomes a bit weird because you need two different types for T):
public class MyController : Controller
{
private readonly ILogger logger1;
private readonly ILogger logger2;
public Controller1(ILoggerFactor loggerFactory)
{
logger1 = loggerFactory.Create("Logger1");
logger2 = loggerFactory.Create("Logger2");
}
}
The categories of the loggers are Logger1 and Logger2.
Each logger will by default log to all the configured providers. You want a logger with one category to log to one provider and a logger with another category to log to another provider.
While you can create filters that are based on category, provider and log level the problem is that you want to use the same provider for both categories. Providers are identified by their type so you cannot create a rule that targets a specific instance of a provider. If you create a rule for the file provider it will affect all configured file providers.
So this is where the hacking starts: You have to create your own provider types that are linked to the files to be able to filter on each file.
.NET Core does not have support for logging to files so you need a third party provider. You have not specified which provider you use so for this example I will use the Serilog file sink together with the Serilog provider that allows you to plug a Serilog logger into the .NET Core logging framework.
To be able to filter on provider you have to create your own provider. Luckily, that is easily done by deriving from the SerilogLoggerProvider:
class SerilogLoggerProvider1 : SerilogLoggerProvider
{
public SerilogLoggerProvider1(Serilog.ILogger logger) : base(logger) { }
}
class SerilogLoggerProvider2 : SerilogLoggerProvider
{
public SerilogLoggerProvider2(Serilog.ILogger logger) : base(logger) { }
}
These two providers does not add any functionality but allows you to create filter that targets a specific provider.
Next step is crating two different Serilog loggers that log to different files:
var loggerConfiguration1 = new LoggerConfiguration()
.WriteTo.File("...\1.log");
var loggerConfiguration2 = new LoggerConfiguration()
.WriteTo.File("...\2.log");
var logger1 = loggerConfiguration1.CreateLogger();
var logger2 = loggerConfiguration2.CreateLogger();
You configure your logging in Main by calling the extension method .ConfigureLogging:
.ConfigureLogging((hostingContext, loggingBuilder) =>
{
loggingBuilder
.AddProvider(new SerilogLoggerProvider1(logger1))
.AddFilter("Logger1", LogLevel.None)
.AddFilter<SerilogLoggerProvider1>("Logger1", LogLevel.Information)
.AddProvider(new SerilogLoggerProvider2(logger2))
.AddFilter("Logger2", LogLevel.None)
.AddFilter<SerilogLoggerProvider2>("Logger2", LogLevel.Information);
})
Each provider (which is associated with a specific file) are added and then two filters are configured for each provider. I find the filter evaluation rules hard to reason about but the two filters added - one with LogLevel.None and another with LogLevel.Information - actually achieves the desired result of ensuring that log messages for the two different categories are routed correctly to the two different providers. If a third provider is added it will not be affected by these filters and messages from both categories will be logged by the third provider.
Having upgraded to spring security 4.2.4 I discovered that StrictHttpFirewall is now the default.
Unfortunately it doesn't play well with spring MVC #MatrixVariable since ";" are not allowed anymore.
How to get around that?
Example:
#GetMapping(path = "/{param}")
public void example(#PathVariable String param,
#MatrixVariable Map<String, String> matrix) {
//...
}
This could be called like this:
mockMvc.perform(get("/someparam;key=value"))
And the matrix map would be populated.
Now spring security blocks it.
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
at org.springframework.security.web.firewall.StrictHttpFirewall.rejectedBlacklistedUrls(StrictHttpFirewall.java:140)
I could use a custom HttpFirewall that would allow semicolons.
Is there a way to use #MatrixVariable without using forbidden characters?
BTW: the javadoc is incorrect https://docs.spring.io/autorepo/docs/spring-security/4.2.x/apidocs/index.html?org/springframework/security/web/firewall/StrictHttpFirewall.html
Since:
5.0.1
I guess it was backported?
You can dilute the default spring security firewall using your custom defined instance of StrictHttpFirewall (at your own risk)
#Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedSlash(true);
firewall.setAllowSemicolon(true);
return firewall;
}
And then use this custom firewall bean in WebSecurity (Spring boot does not need this change)
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
// #formatter:off
web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
...
}
That shall work with Spring Security 4.2.4+, but of-course that brings some risks!
As mentioned by Крис in a comment if you prefer to use a XML approach, you can add the following part to your securityContext.xml (or whatever your spring-security related xml-config is called):
<bean id="allowSemicolonHttpFirewall"
class="org.springframework.security.web.firewall.StrictHttpFirewall">
<property name="allowSemicolon" value="true"/>
</bean>
<security:http-firewall ref="allowSemicolonHttpFirewall"/>
The <bean> part defines a new StrictHttpFirewall bean with the id allowSemicolonHttpFirewall which is then set as default http-firewall in the <security> tag by referencing the id.
I used combination of following two
https://stackoverflow.com/a/48636757/6780127
https://stackoverflow.com/a/30539991/6780127
First one resolved the
The request was rejected because the URL contained a potentially malicious String ";"
Second one Resolved the
Spring MVC Missing matrix variable
As I am using Spring Security with Spring Web I had to do both And the issue is now Resolved.
I found using #MatrixVariable Following Pattern is useful. First in Url {num} has to be mentioned to use it as #MatrixVariable
#RequestMapping(method = RequestMethod.GET,value = "/test{num}")
#ResponseBody
public ResponseEntity<String> getDetail(#MatrixVariable String num){
return new ResponseEntity<>("test"+num, HttpStatus.OK);
}
I use spring boot in a J2SE app.
I have some constant data, such as a map, indicating a HandlerClass to process one operation Type.
The map relation is not changed, so I want to config it in application.yml
I try this:
info:
modify_nodeip: omm.task.impl.ModifyNodeIpHandler
But the map is only can be recognized as Map<String,String>, How can I inject the map as Map<Enum,Class>?
Thank you!
Updated:
I followed #cfrick instruction, but it doen't work.
application.yml
config:
optHandlerMap:
modify_oms_nodeip: 'omm.task.opthandler.impl.ModifyOMSNodeIpHandler'
TestConfiguration:
#Configuration
#ConfigurationProperties(prefix = "config")
public class TestConfiguration
{
Map<OperationType,OptHandler> optHandlerMap; // here we store the handlers, same name in yaml
TestConfiguration() {}
}
and the main func used the configuration
#Autowired
private TestConfiguration testConfiguration;
what's wrong with that? But it doesn't work, optHandlerMap in testConfiguration is null.
You can play a trick like this:
In your TestConfiguration, define a Map<String,String>, and getter.
then provide a Map<Operator,Handler> getXXXX() function, in this function, convert the Map<String,String> to Map<Operator,Handler>.
Maybe you need to use reflect to new a instance.
By the way, you can use Maps.transform() in Guava to perform the conversion.
You write your own settings and annotate as #ConfigurationProperties (see 21.6 Typesafe Configuration Properties)
#Component
#ConfigurationProperties(prefix="cfg") // the root in my yaml
class HandlerConfiguration {
public enum Handler { Handler1, Handler2 } // enum
Map<Handler,Class> handlers // here we store the handlers, same name in yaml
HandlerConfiguration() {}
}
Then my application.yaml looks like this:
cfg:
handlers:
Handler1: 'app.Handler1'
And accessing it like this:
def ctx = SpringApplication.run(Application, args)
ctx.getBean(HandlerConfiguration).with{
assert handlers.size()==1 // there is the one
assert handlers[HandlerConfiguration.Handler.Handler1] // it's key is the enum
assert handlers[HandlerConfiguration.Handler.Handler1] == Handler1 // its value is the actual class
handlers[HandlerConfiguration.Handler.Handler1].newInstance().run()
}
(app.Handler1 is just some random class I put there, all this is in the same package (app))
just enable config spring.data.rest.enable-enum-translation (Spring document) and Spring will do the trick
A web.xml makes registering the order of an application's filters obvious/explicit. But, I'm using Java Config. I define a filter, MyProcessingFilter.java, which extends AbstractAuthenticationProcessingFilter. In the filter chain, I need to make sure that it comes after Spring Security's SecurityContextPersistenceFilter. See essential Spring Security filter ordering.
I'm using Spring Boot. This is how I declare my filter
#Configuration
public class Config {
/*...*/
#Bean
public Filter myProcessingFilter() {
MyProcessingFilter myProcessingFilter = new MyProcessingFilter(AnyRequestMatcher.INSTANCE);
myProcessingFilter.setAuthenticationManager(authenticationManager());
return myProcessingFilter;
}
}
Spring Boot orders this custom filter first.
Basically, I have a custom authentication filter and I need it to come after Spring Security's SecurityContextPersistenceFilter. Any suggestions?
Despite what is claimed here:
for applications not working because of missing #Path at class level
-> it should work now
I still have to annotate my endpoint implementations, as annotations on interfaces are not being picked up.
Is it related to the way I configure JAX-RS, or is it a bug still present in TomEE?
interface:
#Path("myPath") public interface MyEndpoint {
#Path("{id}") String getById(#PathParam("id") long id);
}
implementation:
#Stateless class EJBBackedMyEndpoint implements MyEndpoint {
String getById(long id) { return "foo"; }
}
openejb-jar.xml
<openejb-jar xmlns="http://www.openejb.org/openejb-jar/1.1">
<ejb-deployment ejb-name="EJBBackedMyEndpoint">
<properties>cxf.jaxrs.providers = exceptionMapper</properties>
</ejb-deployment>
</openejb-jar>
resources.xml
<resources>
<Service id="exceptionMapper" class-name="package.MyExceptionMapper"/>
</resources>
beans.xml present with just empty root element
Update:
JAX-RS Spec apparently doesn't mention class-level annotations at all
#Consumes and #Produces work when applied on the interface,
#Path (class level) doesn't work when applied on the interface,
#Path on method level is honoured when routing requests, however the UriBuilder is failing:
UriBuilder.path(EJBBackedMyEndpoint.class, "getById") throws IllegalArgumentException: No Path annotation for 'retrieve' method.
That blog post is perhaps misleading. Putting #Path, #GET, #PathParam or other JAX-RS annotations on an interface is not supported by JAX-RS. Per spec all these need to be on the "Resource Class", which is the #Stateless bean class in this situation.
If you move #Path from the interface to bean class it should work. At least it should get further.