How to resolve javax.servlet.ServletException: Circular view path for basic Spring Security? - spring

Im new to Spring Boot and was trying to implement basic Spring Security for a single endpoint in my Spring Boot controller. But I dont know how to resolve the Circular View Error
My Controller
#Controller
#RequestMapping("/api")
public class HelloSecurityController {
#RequestMapping({"/hello"})
public static String helloWorld() {
return "hello";
}
My Security Configurer Class
#EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService myUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
```
My MyUserDetailsService which returns a simple User with Password
#Service
public class MyUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
return new User("foo","foo", new ArrayList<>());
The Dependencies I kept in maven pom file during initializing Spring Boot Project
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
The Package structure of my project:
proj_struct
After logging in using the username and password at the login form page generated by spring security Im getting the error:
javax.servlet.ServletException: Circular view path [hello]: would dispatch back to the current handler URL [/api/hello] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
I have not kept any static template (HTML/JSP) in the templates folder. I dont know if I have to as after login I just want to see a simple String. How do I resolve this?

Related

Spring Security Keycloak Adapter cannot handle single sign-out when logout from another tab in browser

I have implemented a spring boot web app using spring security and Keycloak to authenticate users based on this tutorial. I used maven, spring boot 2.2.2 and Keycloak 8.0.1. All things works correctly except a problem in single sign-out. When I open the secured path of spring boot app in a tab of my browser (http://localhost:8080/books) and Keycloak account page (http://localhost:8180/auth/realms/{realm_name}/account) in another tab and login with one of the users in one of them, the other tab will aware of the login and after reload the page, that page will also be authenticated. But the problem is where, when both tabs are logged in and first I log out from account page, and reload spring boot app, the user remains active and the app does not aware of the log out action in other tabs. How can I handle this problem?
my project resource tree is like image below:
Project resource tree
This is my pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sso</groupId>
<artifactId>demoapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demoapp</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>13</java.version>
<keycloak.version>8.0.1</keycloak.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- Keycloak Adapter -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-test-helper</artifactId>
<version>${keycloak.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>${keycloak.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
This is SecurityConfig.java :
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setPrefix("ROLE_");
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* Define an HttpSessionManager bean only if missing.
*/
#Bean
#Override
#ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
/**
* Define security constraints for the application resources.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/books").authenticated()
.antMatchers("/manager").hasRole("admin")
.anyRequest().permitAll();
}
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
}
This is LibraryController.java :
#Controller
public class LibraryController {
private final HttpServletRequest request;
private final BookRepository bookRepository;
#Autowired
public LibraryController(HttpServletRequest request, BookRepository bookRepository) {
this.request = request;
this.bookRepository = bookRepository;
}
#GetMapping(value = "/")
public String getHome() {
return "index";
}
#GetMapping(value = "/books")
public String getBooks(Model model) {
configCommonAttributes(model);
model.addAttribute("books", bookRepository.readAll());
return "books";
}
#GetMapping(value = "/manager")
public String getManager(Model model) {
configCommonAttributes(model);
model.addAttribute("books", bookRepository.readAll());
return "manager";
}
#GetMapping(value = "/logout")
public String logout() throws ServletException {
request.logout();
return "redirect:/";
}
private void configCommonAttributes(Model model) {
model.addAttribute("firstname", getKeycloakSecurityContext().getIdToken().getGivenName());
model.addAttribute("lastname", getKeycloakSecurityContext().getIdToken().getFamilyName());
model.addAttribute("email", getKeycloakSecurityContext().getIdToken().getEmail());
}
/**
* The KeycloakSecurityContext provides access to several pieces of information
* contained in the security token, such as user profile information.
*/
private KeycloakSecurityContext getKeycloakSecurityContext() {
return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
}
}
and below is my DemoappApplication.java :
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class DemoappApplication {
public static void main(String[] args) {
SpringApplication.run(DemoappApplication.class, args);
}
}
The problem was just solved by setting the base URL of my spring boot application as Admin-URL in the corresponding client configuration page of Keycloak server (admin console). Now, the spring boot app is aware of user log out from other apps.

log4j2 java classes do not load application.properties entries with spring boot

I am using log4j2(log4j-core and log4j-api) with Spring boot 2.2.0.RELEASE. I want to masking sensitive data inmy json payload in the logs. I have written a custom LogMaskingConverter which extends LogEventPatternConverter.
#Plugin(name = "LogMaskingConverter", category = "Converter")
#ConverterKeys({ "spi", "trscId" })
#Configuration
public class LogMaskingConverter extends LogEventPatternConverter {
#Value("${maskingFields}")
private String maskingFields;
protected LogMaskingConverter(String name, String style) {
super(name, style);
}
public static LogMaskingConverter newInstance(String[] options) {
return new LogMaskingConverter("spi", Thread.currentThread().getName());
}
#Override
public void format(LogEvent event, StringBuilder outputMessage) {
........
}
private String mask(String message) {
....
}
The problem is that the maskingFields does not get loaded from the application properties file.I read that log4j2 gets loaded before spring boot. Hence I have renamed my xml to log4j2-spring.xml so that spring boot is the first to intialise and load all the properties before it gets read via LogMaskingConverter.
Also below are my pom.xml dependencies related to log4j2
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
To summarise below code does not fetch any values:
#Value("${maskingFields}")
private String maskingFields;

Injecting Specific DataSource in Spring Boot 2.0.2 Release

When trying to autowire DataSource by using Spring Boot 2.0.2 RELEASE, it gives the following error and cannot manage to inject it:
'Could not autowire. There is more than one bean of 'DataSource' type.'
The beans are created in DataSourceConfiguration class for HikariDataSource, BasicDataSource and for Tomcat.
Is there a way of qualifying one of them and avoid creation of the remaining ones?
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public SecurityConfig() {
this.usersByUsernameQuery = // definitions here
this.authoritiesByUsernameQuery = //
this.allowedAuthorities = //
}
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth, final DataSource dataSource) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(usersByUsernameQuery)
.authoritiesByUsernameQuery(authoritiesByUsernameQuery);
}
}
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
</dependencies>
application.properties:
spring.datasource.url=jdbc:mysql://localhost:9400/test
spring.datasource.username=root
spring.datasource.password=test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Custom Login Page through LDAP in Spring Boot

I want to implement LDAP authentication in my Spring Boot app. I have configuration class as below:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//#Value("${ad.domain}")
private String AD_DOMAIN;
//#Value("${ad.url}")
private String AD_URL;
WebSecurityConfig() {
AD_DOMAIN = "domain.com";
AD_URL = "ldap://URL";
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin().permitAll().and().logout().permitAll();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailsService());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(AD_DOMAIN, AD_URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
controller:
#Controller
public class HomeController {
#GetMapping("/")
public String index() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String user = auth.getName();
return "Welcome to the home page "+ user;
}
And my pom file have dependencies as :
<dependencies>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
Here all is working fine I am getting SpringBoot's default login page and Authentication is also working.
But now I want a Custom login Page for that in my WebSecurityConfig class I have done the following :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();
}
where /login is my custom login page. But this is not working. I think that the AuthenticationManager is bind to the Spring's default login Page.
Can Someone suggest me, it will be helpful. Thanks

Spring Boot/Spring Integration Servlet Context/DelegatingWebMvcConfiguration issue

I'm not sure the best way to word this issue, because I have no idea why this happens.
When I start up my Spring Boot application without the spring-integration-core jar, my app starts up fine. The second I add the spring-boot-starter-integration or any spring-integration-core library to my Maven pom file the servlet context is always null on my DelegatingWebMvcConfiguration class and I get the following error:
java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
After doing some debugging, I see the DelegatingWebMvcConfiguration class initialized before the EmbeddedWebApplicationContext sets the servlet context for my application, which makes sense why the servlet context is null for my DelegatingWebMvcConfiguration and set for all the other classes initialized with postProcessBeforeInitialization in ServletContextAwareProcessor. Simply removing the Spring Integration dependency fixes the issue, but I'd like to use Spring Integration in my Boot application.
My question is what would cause the DelegatingWebMvcConfiguration to be created before the EmbeddedWebApplicationContext can call prepareEmbeddedWebApplicationContext method.
Spring dependencies using Spring Boot 1.1.8.RELEASE:
org.springframework.boot
spring-boot-starter-web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>0.16.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-core</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-cassandra</artifactId>
<version>1.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>4.1.2.RELEASE</version>
</dependency>
My root configuration file:
#Configuration
#EnableAutoConfiguration
#EnableAsync
#EnableEntityLinks
#EnableSpringDataWebSupport
#ComponentScan
#PropertySource(value = "file:../application.properties",
ignoreResourceNotFound = true)
public class ApplicationInitializer extends SpringBootServletInitializer
implements EmbeddedServletContainerCustomizer {
public static void main(String[] args) throws Exception {
SpringApplication.run(ApplicationInitializer.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application){
return application.sources(ApplicationInitializer.class);
}
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setContextPath(APPLICATION_ROOT_PATH);
}
// Global beans defined here
}
Update: After trying many different things, I found out after disabling my SchedulingConfigurer my app now starts. Here is my SchedulingConfigurer
#Configuration
#EnableScheduling
#EnableConfigurationProperties(SchedulingProperties.class)
public class SchedulingConfig implements SchedulingConfigurer {
//Inject Runnables and properties
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
// call taskRegistrar.addFixedRateTask with runnable and property rate value
}
#Bean(destroyMethod = "shutdown")
public Executor taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(Runtime.getRuntime().availableProcessors());
return taskScheduler;
}
}

Resources