We are looking to migrate our project to Spring Boot. However it is unclear how to replicate the functionality of AbstractAnnotationConfigDispatcherServletInitializer in Spring Boot?
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
#Override
protected Class<?>[] getRootConfigClasses()
{
return new Class<?>[]{AppConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses()
{
return new Class<?>[]{WebappConfig.class};
}
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setAsyncSupported(true);
}
#Override
protected String[] getServletMappings()
{
return new String[]{"/"};
}
#Override
protected Filter[] getServletFilters()
{
DelegatingFilterProxy shiroFilter = new DelegatingFilterProxy("shiroFilter");
shiroFilter.setTargetFilterLifecycle(true);
CompositeFilter compositeFilter = new CompositeFilter();
compositeFilter.setFilters(ImmutableList.of(new CorsFilter(),shiroFilter));
return new Filter[]{compositeFilter};
}
}
The AppConfig and WebappConfig parent/child relationship can be handled by SpringApplicationBuilder, although you might also consider a flat hierarchy.
Assuming that you are going the whole hog, and running an embedded servlet container you can register Filters and Servlets directly as beans.
You can also use ServletRegistrationBean and FilterRegistrationBean if you need to set things such as setAsyncSupported. The final option is to add a bean that implements org.springframework.boot.context.embedded.ServletContextInitializer then do the registration yourself.
Something like this might get you a bit further:
#Bean
public ServletRegistrationBean dispatcherServlet() {
ServletRegistrationBean registration = new ServletRegistrationBean(
new DispatcherServlet(), "/");
registration.setAsyncSupported(true);
return registration;
}
#Bean
public Filter compositeFilter() {
CompositeFilter compositeFilter = new CompositeFilter();
compositeFilter.setFilters(ImmutableList.of(new CorsFilter(), shiroFilter));
return compositeFilter
}
Also, take a look at this section in the reference manual http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-embedded-container
Well there is nothing special like just mark your AppInitializer with Boot annotations:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
...
}
I haven't tried it, but just combined the documentation:
Normally all the code from an existing WebApplicationInitializer can be moved into a SpringBootServletInitializer. If your existing application has more than one ApplicationContext (e.g. if it uses AbstractDispatcherServletInitializer) then you might be able to squash all your context sources into a single SpringApplication.
And SpringBootServletInitializer JavaDocs:
If your application is more complicated consider using one of the
other WebApplicationInitializers.
Related
I am setting up a completely java based spring app with no xml config :
public class WebApp extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebMvcConfigurer.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
and
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { mypackages })
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/static-assets/");
}
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
where do I put this, which used to be in my web.xml ?
<session-config>
<!-- Disables URL-based sessions (no more 'jsessionid' in the URL using Tomcat) -->
<tracking-mode>COOKIE</tracking-mode>
</session-config>
you can do it as in below
public class WebConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext)
throws ServletException {
HashSet<SessionTrackingMode> set = new HashSet<SessionTrackingMode>();
set.add(SessionTrackingMode.COOKIE);
servletContext.setSessionTrackingModes(set);
}
}
In a Spring Boot app, you can configure the mode using the application property server.session.tracking-modes.
In your application.properties add:
server.session.tracking-modes=cookie
Or if you use application.yml:
server:
session:
tracking-modes: 'cookie'
The Spring Boot autoconfiguration internally uses the same call to servletContext.setSessionTrackingModes which Bassem recommended in his answer.
UPDATE
In newer versions of Springboot, use
server.servlet.session.tracking-modes=cookie
Since 3.2.0.RC1 this is available in the AbstractSecurityWebApplicationInitializer like so:
public class WebSecutityInit extends AbstractSecurityWebApplicationInitializer {
#Override
protected Set<SessionTrackingMode> getSessionTrackingModes() {
return EnumSet.of(SessionTrackingMode.SSL);
}
}
As of spring boot 2.4.0 and later, the application property is renamed to server.servlet.session.tracking-modes.
So, in application.properties, need to add
server.servlet.session.tracking-modes="cookie"
If using application.yml, need to have
server:
servlet:
session:
tracking-modes: cookie
Another solution, that works for me, has been the code below inside the SecurityConfig class.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) //No sessionId eppended
...
}
In my older Spring 4 web-app, I used an applicationContext.xml file, and my default spring profile was as follows;
<beans profile="default">
<context:property-placeholder location="file:/opt/myapp/myapp-ws.properties" />
</beans>
And now I am using Spring 5 Framework, but NOT Spring Boot 2.x, and I want to do this in my Java Config class.
My main configuration class looks like this;
#Configuration
#ComponentScan(basePackages = "com.tomholmes.myapp")
#EnableWebMvc
public class MyAppConfig
{
}
And I have the AppInitializer as follows;
public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
private static final Log logger = LogFactory.getLog(ApplicationInitializer.class);
#Override
protected Class<?>[] getRootConfigClasses()
{
return new Class[]
{ MyAppConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses()
{
return new Class[]{};
}
#Override
protected String[] getServletMappings()
{
return new String[]
{ "/api/*" };
}
}
I've been doing some research on the Net since there is a lot of information on this, but a lot of it conflates Spring Boot,and I just want a Spring 5 without Spring Boot solution. I'll keep looking, I am sure this is a simple issue.
Thanks!
I believe something like this might do the trick:
#Configuration
public class PropertiesConfig {
#Bean
public PropertyPlaceholderConfigurer properties() {
final PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
final List<Resource> resources = new ArrayList<>();
resources.add(new FileSystemResource("/etc/app/application-{profile_1}.properties"));
resources.add(new FileSystemResource("/etc/app/application-{profile_2}.properties"));
ppc.setLocations(resourceLst.toArray(new Resource[]{}));
return ppc;
}
Please note, that this is just the suggestion, this code is not tested.
Profile specific application properties should be resolved automatically by current active profile.
I haven't tested this, but a #Configuration class with both #Profile and #PropertySource should work:
#Configuration
#Profile("default")
#PropertySource("file:/opt/myapp/myapp-ws.properties")
public class MyappWebservicePropertyConfig {
}
Using Spring Security 3.2.5 and Spring 4.1.2, 100% Java config
Our webapp has global method security enabled and service methods annotated with #PreAuthorize - everything is working as expected. I'm trying to add a role hierarchy and having no success at all. Here's the hierarchy I'm trying to achieve:
ROLE_ADMIN can access all methods that ROLE_USER can access.
ROLE_USER can access all methods that ROLE_DEFAULT can access.
Despite my best efforts, a user with ROLE_ADMIN receives a 403 when doing something that results in a call to a method annotated with #PreAuthorized("hasAuthority('ROLE_DEFAULT')")
Here's the relevant configuration code:
AppInitializer
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
#Override
protected Class<?>[] getRootConfigClasses()
{
return new Class[]
{
AppConfig.class, SecurityConfig.class
};
}
#Override
protected Class<?>[] getServletConfigClasses()
{
return new Class[]
{
MvcConfig.class
};
}
// other methods not shown for brevity
}
AppConfig.java
#Configuration
#ComponentScan(basePackages={"myapp.config.profile", "myapp.dao", "myapp.service", "myapp.security"})
public class AppConfig
{
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> detailSvc) throws Exception
{
PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
authProvider.setPreAuthenticatedUserDetailsService(detailSvc);
auth.authenticationProvider(authProvider);
}
// other methods not shown for brevity
}
SecurityConfig.java
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
PKIAuthenticationFilter pkiFilter = new PKIAuthenticationFilter();
pkiFilter.setAuthenticationManager(authenticationManagerBean());
http.authorizeRequests()
.antMatchers("/app/**").fullyAuthenticated()
.and()
.anonymous().disable()
.jee().disable()
.formLogin().disable()
.csrf().disable()
.x509().disable()
.addFilter(pkiFilter)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(WebSecurity web) throws Exception
{
// ignore everything but /app/*
web.ignoring().regexMatchers("^(?!/app/).*");
}
}
MvcConfig.java
#Configuration
#EnableWebMvc
#ComponentScan({"myapp.controller"})
public class MvcConfig extends WebMvcConfigurerAdapter
{
// resource handlers, content negotiation, message converters configured here
}
In the same package as SecurityConfig (so it is thus part of the AppConfig component scan) I had this class:
GlobalMethodSecurityConfig.java
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration
{
#Bean
public RoleHierarchy roleHierarchy()
{
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER > ROLE_DEFAULT");
return roleHierarchy;
}
#Bean
public RoleVoter roleVoter()
{
return new RoleHierarchyVoter(roleHierarchy);
}
#Bean
#Override
protected AccessDecisionManager accessDecisionManager()
{
return new AffirmativeBased(Arrays.asList(roleVoter()));
}
// The method below was added in an attempt to get things working but it is never called
#Override
protected MethodSecurityExpressionHandler createExpressionHandler()
{
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setRoleHierarchy(roleHierarchy());
return handler;
}
}
In another attempt I made AppConfig extend GlobalMethodSecurityConfiguration but a user with ROLE_ADMIN cannot call a method requiring ROLE_DEFAULT access.
I'm sure I've misconfigured something somewhere but I can't figure out where I've gone wrong despite reading everything I can find on configuring global method security with a role hierarchy. It appears this would be trivial using XML configuration but the Java config solution eludes me.
I'd override GlobalMethodSecurityConfiguration#accessDecisionManager method. You can see source code that RoleVoter uses.
Here is my suggested overridden source code.
#Override
protected AccessDecisionManager accessDecisionManager() {
var roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_SUPER > ROLE_ADMIN");
var expressionHandler = (DefaultMethodSecurityExpressionHandler) getExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
var expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(expressionHandler);
return new AffirmativeBased(List.of(
new RoleHierarchyVoter(roleHierarchy),
new PreInvocationAuthorizationAdviceVoter(expressionAdvice),
new AuthenticatedVoter(),
new Jsr250Voter()
));
}
Since this question keeps getting views I thought I'd post a follow-up to it. The problem appears to be with the line
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER > ROLE_DEFAULT");
I don't remember why I wrote the hierarchy like that but it's not correct. The API for that method handles the same situation thusly:
Role hierarchy: ROLE_A > ROLE_B and ROLE_B > ROLE_C.
Directly assigned authority: ROLE_A.
Reachable authorities: ROLE_A, ROLE_B, ROLE_C.
Eventually it became clear that a hierarchical model didn't fit our roles so we instead implemented a finer-grained set of authorities mapped to roles, as mentioned in the Spring Security Reference:
For more complex requirements you may wish to define a logical mapping between the specific access-rights your application requires and the roles that are assigned to users, translating between the two when loading the user information.
Here is the problem: I can successfully register the Filter, but don't know how to set the mapping URL using this specific configuration.
Here is my Class:
public class WebInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{AppConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
#Override
protected Filter[] getServletFilters() {
return new Filter[]{
new DelegatingFilterProxy("springSecurityFilterChain"),
new DelegatingFilterProxy("customFilter")
};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
P.D.
I had done it using WebApplicationInitializer, but I want to use AbstractAnnotationConfigDispatcherServletInitializer.
The only way I was able to do this was to use the FilterRegistration.Dynamic interface. In your WebInitializer class, add your custom filter manually in the onStartup method (an override from a superclass). There is no way that is more elegant at the moment to my knowledge.
#Override
public void onStartup(ServletContext servletContext)
throws ServletException {
FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("my-filter", new MyFilter());
encodingFilter.setInitParameter("blah", "blah");
encodingFilter.addMappingForUrlPatterns(null, false, "/toBeFiltered/*");
super.onStartup(servletContext);
}
If you want this filter to work correctly then it would be best for you to comment out the getServletFilters method you have overridden so that this filter is served back correctly from the servletContext.
For the springSecurityFilterChain, simply add this class to the same package as other configuration classes
#Order(2)
public class MyAppSecureWebAppInitializer extends
AbstractSecurityWebApplicationInitializer {
#Override
protected boolean enableHttpSessionEventPublisher() {
return true;
}
}
The AbstractSecurityWebApplicationInitializer (from javadocs)
Registers the DelegatingFilterProxy to use the
springSecurityFilterChain before any other registered Filter. When
used with AbstractSecurityWebApplicationInitializer(Class), it will
also register a ContextLoaderListener. When used with
AbstractSecurityWebApplicationInitializer(), this class is typically
used in addition to a subclass of AbstractContextLoaderInitializer.
By default the DelegatingFilterProxy is registered without support,
but can be enabled by overriding isAsyncSecuritySupported() and
getSecurityDispatcherTypes().
Additional configuration before and after the
springSecurityFilterChain can be added by overriding
afterSpringSecurityFilterChain(ServletContext).
And from the spring security example in Github
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceEncoding(true);
DelegatingFilterProxy reconnectDelegate = new DelegatingFilterProxy("apiExceptionHandler");
return new Filter[] { reconnectDelegate, encodingFilter, new HiddenHttpMethodFilter() };
}
i am trying to combine a Spring Web Application (completed Annotation Based configuration, no xml configuration) with metrics 3.0.
I am running the application inside a jetty.
This is my current configuration for the default DispatcherServlet:
public class WebInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
return new Filter[] { characterEncodingFilter };
}
}
This is the WebConfig:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.rebuy.silo.amqpredelivery")
#EnableJpaRepositories(basePackages = "com.rebuy.silo.amqpredelivery.domain")
#EnableAspectJAutoProxy
#EnableTransactionManagement
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
jacksonConverter.setObjectMapper(objectMapper());
converters.add(jacksonConverter);
super.configureMessageConverters(converters);
}
#Bean
public ObjectMapper objectMapper() {
SimpleDateFormat format = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ssXXX");
format.setTimeZone(TimeZone.getTimeZone("GMT+1"));
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(format);
return mapper;
}
}
I want to add these two Servlets:
https://github.com/codahale/metrics/blob/master/metrics-servlets/src/main/java/com/codahale/metrics/servlets/HealthCheckServlet.java
https://github.com/codahale/metrics/blob/master/metrics-servlets/src/main/java/com/codahale/metrics/servlets/MetricsServlet.java
What is the best way to do this? I think there should be some spring magic to make this extremly easy to do! But I was not able to find it :(
Thanks in advance
Björn
You can follow this codebase https://github.com/spiritedtechie/metrics-examples.
Or use this library called metrics-spring http://ryantenney.github.io/metrics-spring/
If you are using Spring and Metrics you should also be using #RyanTenney's Metrics-Spring module. It will simplify your Java config and make your Metrics usage much cleaner.
Take a look at the code behind the MetricsServlet and HealthCheckServlet. In my opinion its easier to just write your own Spring Controller to do the same thing than to figure out how to embed and wrap those old servlets.
Its easy!
Create a metrics specific config:
#Configuration
#EnableMetrics
public class MetricsConfig extends MetricsConfigurerAdapter {
#Override
public void configureReporters(MetricRegistry metricRegistry) {
registerReporter(ConsoleReporter
.forRegistry(metricRegistry)
.build()).start(5, TimeUnit.MINUTES);
}
}
And include it from your existing config by adding:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.rebuy.silo.amqpredelivery")
#EnableJpaRepositories(basePackages = "com.rebuy.silo.amqpredelivery.domain")
#EnableAspectJAutoProxy
#EnableTransactionManagement
#Import({MetricsConfig.class})
public class WebConfig extends WebMvcConfigurerAdapter {
...
The above config changes make it trivial to inject a MetricRegistry in any Spring component. All the MetricsServlet does is send the registry in response to the request. That is really easy to accomplish in a simple controller. For example:
#Controller
public class AdminMetricsController
{
#Autowired
MetricRegistry metricRegistry;
#RequestMapping(value = "/admin/metrics/", produces = {APPLICATION_JSON_VALUE})
public #ResponseBody MetricRegistry getMetrics(final HttpServletResponse response)
{
response.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
return metricRegistry;
}
}
A HealthCheckRegistry can be injected in a similar way and another method added which would respond to /admin/health/ or whatever url you wanted.
Take a look at the following answer. It explains how to register a Servlet via JavaConfig:
Spring JavaConfig: Add mapping for custom Servlet