Mock user in spring secutity reactive application - spring-security

I have a spring application that uses the following dependencies:
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</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>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-tools</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
The security application is configured as following:
#Configuration
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.csrf().disable()
.authorizeExchange()
.pathMatchers("/**").hasAuthority("myAuthority")
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(converter());
return http.build();
}
#Bean
public Converter<Jwt, Mono<AbstractAuthenticationToken>> converter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new GrantedAuthoritiesExtractor());
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
static class GrantedAuthoritiesExtractor implements Converter<Jwt, Collection<GrantedAuthority>> {
public Collection<GrantedAuthority> convert(Jwt jwt) {
Collection<String> authorities = (Collection<String>) jwt.getClaims().get("roles");
if(authorities == null) {
authorities = List.of();
}
return authorities.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
}
so I need authenticated user to request the api and the authority myAuthority.
I would like to write some tests for this to be sure a test fails if someone changes the security configuration.
I use this:
protected WebTestClient webTestClient(Object controller) {
return WebTestClient.bindToController(controller)
.apply(springSecurity())
.configureClient()
.build()
.mutateWith(mockJwt().authorities(new SecurityConfiguration.GrantedAuthoritiesExtractor()));
}
but all tests pass and no authenticated user is needed nor authority.
I would like to use #MockUser to test roles.
EDIT for further info on tests
here is the test class
#ExtendWith(SpringExtension.class)
#WebFluxTest(controllers = MyController.class)
#Import({MyRepository.class, ValidationConfiguration.class})
public class MyControllerTest extends BaseControllerTest {
#Autowired
MyController controller;
#Test
public void test() {}
}

Related

Logging-Intervene when token is wrong Spring Cloud Gateway, Spring Security

I want to log when the token is wrong. I prepared the ServerAuthenticationFailureHandler class, but it doesn't work.
SecurityConfig class
#Configuration
public class SecurityConfig {
#Bean
public SecurityWebFilterChain configure(ServerHttpSecurity http) {
http.csrf().disable()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.oauth2Login().authenticationFailureHandler(authenticationFailureHandler())
.and().oauth2ResourceServer().jwt();
return http.build();
}
#Bean
public ServerAuthenticationFailureHandler authenticationFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
}
CustomAuthenticationFailureHandler class
#Component
public class CustomAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {
Logger logger = LogFactory.getLogger(CustomAuthenticationFailureHandler.class);
#Override
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
logger.error("invalidToken");
return null;
}
}
pom.xml
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</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-oauth2-resource-server</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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.2.1</version>
</dependency>
</dependencies>
It does not fall under the CustomAuthenticationFailureHandler class.
How can I intervene when the token is invalid?

Spring Security (5.7.3): this.authenticationManager is null

I'm trying POST requests to login to the page with JWT Authentication.
I had initially tried to do JWT Authentication using WebSecurityConfigurerAdapter. But WebSecurityConfigurerAdapter is deprecated. I then tried to integrate it according to the new usage.But When I send POST request in Postman, I m getting this error.
"org.springframework.security.authentication.AuthenticationManager.authenticate(org.springframework.security.core.Authentication)" because "this.authenticationManager" is null
My Dependencies are:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.project</groupId>
<artifactId>questapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>questapp</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</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.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
My Controller Class is:
#RestController
#RequestMapping("/auth")
public class AuthenticationController {
private AuthenticationManager authenticationManager;
private JwtTokenProvider jwtTokenProvider;
#PostMapping("/login")
public String login(#RequestBody UserRequest userLoginRequest) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userLoginRequest.getUserName(), userLoginRequest.getPassword());
Authentication auth = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
String jwtToken = jwtTokenProvider.generateJwtToken(auth);
return "Bearer "+jwtToken;
}
}
My SecurityConfig Class is:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsService jwtUserDetailsService;
private final JwtAuthenticationEntryPoint handler;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(jwtUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
return authenticationManagerBuilder.build();
}
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{
httpSecurity
.cors()
.and()
.csrf().disable()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(handler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.GET,"/auth/**")
.permitAll()
.antMatchers("/auth/**")
.permitAll()
.anyRequest().authenticated();
return httpSecurity.build();
}
}
Can anyone tell me where I made a mistake?

How can I configure Spring Security 5 OAuth correctly with Vue.js?

We are building a Web Application with Spring Boot and Vue.js. Currently we are migrating from Security OAuth 2.x to Spring Security 5.2.x and having issues with the Oauth2 flow.
Below is the security configuration:
#EnableWebSecurity
#Configuration
#PropertySource("classpath:application.properties")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(new StaticAllowFromStrategy(location)));
http.authorizeRequests()
.antMatchers("/login", "/static/**","/", "/actuator/prometheus")
.permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2Login();
}
private static List<String> clients = Arrays.asList("authnext");
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
List<ClientRegistration> registrations = clients.stream()
.map(c -> getRegistration(c))
.filter(registration -> registration != null)
.collect(Collectors.toList());
return new InMemoryClientRegistrationRepository(registrations);
}
private ClientRegistration getRegistration(String client) {
if (client.equals("authnext")) {
return AuthNextOAuth2Provider.AUTHNEXT.getBuilder(client, "https://baseurl/auth/oauth2/realms/root/realms/intranetb2x/authorize",
"https://baseurl/auth/oauth2/realms/root/realms/intranetb2x/tokeninfo",
"https://baseurl/auth/oauth2/realms/root/realms/intranetb2x/userinfo")
.clientId("ID")
.clientSecret("SECRET")
.build();
}
return null;
}
#Bean
public OAuth2AuthorizedClientService authorizedClientService() {
return new InMemoryOAuth2AuthorizedClientService(
clientRegistrationRepository());
}
#Bean
public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() {
return new HttpSessionOAuth2AuthorizationRequestRepository();
}
}
Furthermore we have the following AuthNextOAuth2Provider:
public enum AuthNextOAuth2Provider {
AUTHNEXT {
#Override
public Builder getBuilder(String registrationId,String authUri,String tokenUri, String userInfoUri) {
ClientRegistration.Builder builder = getBuilder(registrationId, ClientAuthenticationMethod.BASIC,DEFAULT_LOGIN_REDIRECT_URL);
builder.scope("openid", "profile", "b2xroles");
builder.authorizationUri(authUri);
builder.tokenUri(tokenUri);
builder.userInfoUri(userInfoUri);
builder.userNameAttributeName(IdTokenClaimNames.SUB);
builder.clientName("authnext");
return builder;
}
};
private static final String DEFAULT_LOGIN_REDIRECT_URL = "{baseUrl}/login";
protected final ClientRegistration.Builder getBuilder(String registrationId, ClientAuthenticationMethod method,
String redirectUri) {
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(registrationId);
builder.clientAuthenticationMethod(method);
builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
builder.redirectUriTemplate(redirectUri);
return builder;
}
/**
* Create a new
* {#link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder
* ClientRegistration.Builder} pre-configured with provider defaults.
*
* #param registrationId the registration-id used with the new builder
* #return a builder instance
*/
public abstract ClientRegistration.Builder getBuilder(
String registrationId,
String authUri,
String tokenUri,
String userInfoUri
);
}
The problem is the redirect isn't working correctly. We can be correctly directed to the auth server and login there but when redirected back to the spring application we get a 404 error.
How it works is as follows. We have a Spring Boot application which lives at /. I want to allow all requests to the / /login und /static/** so that the vue.js resources are correctly served. The vue.js application is calling an backend endpoint status to check if the broswer is currently authenticated. If this is not the case then the user is redirected to the Oauth2 auth server as follows:
checkLoggedIn: function() {
AXIOS({
method: "GET",
url: "/status",
responseType: "blob"
})
.then(response => {
var element = document.getElementById("step1");
element.classList.add("step-active");
document.getElementById("lock1").style.visibility = "hidden";
document.getElementById("lock2").style.visibility = "hidden";
document.getElementById("lock3").style.visibility = "visible";
document.getElementById("lock4").style.visibility = "hidden";
this.connect();
})
.catch(error => {
if(typeof error.response === 'undefined'){
var localStorage = window.localStorage;
window.localStorage.setItem('route', JSON.stringify(this.$route));
window.location.href = "/login";
}else if(error.response.status===403){
this.$parent.upload = false;
this.$parent.uploadResult = false;
this.$parent.checkData = false;
this.$parent.approveData = false;
this.$parent.checkout = false;
this.$parent.unauthorized=true;
}
});
}},
Forcing the browser to visit /login seems to begin the authentication workflow correctly but on the way back there is a problem with the following request:
How can I configure the login flow correctly. It also seems to create some endpoints in the backend of the form:
oauth2/authorization/authnext
What is this and is it necessary. With Spring OAuth2 we just used #EnableOauth2Sso and everything just worked.
Here are the dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>${oauth-auto.version}</version>
</dependency>
-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>1.20</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-core</artifactId>
<version>1.1.44</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.1.44</version>
</dependency>
<!-- <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>1.1.44</version> </dependency> <dependency> <groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId> <version>1.1.44</version> </dependency> -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>4.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</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>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.metrics</groupId>
<artifactId>spring-metrics</artifactId>
<version>0.5.1.RELEASE</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_common</artifactId>
<version>0.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
</dependencies>

why can't I redirect after successful login?

I am trying to access a welcome page after login using
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private DataSource dataSource;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.successForwardUrl("/welcome")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/");
}
}
My UserDetailsService is:
#Service(value = "customUserDetailsService")
public class CustomUserDetailsServiceImpl implements UserDetailsService {
private static Logger logger = LoggerFactory.getLogger(CustomUserDetailsServiceImpl.class);
#Autowired
private UserService userService;
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userService.findByEmail(email);
if (user == null) {
logger.error("User with email" + email + " not found.");
throw new UsernameNotFoundException("Oops! User not found with username: " + email);
} else {
logger.info("User {} successfully logged", user.getUsername());
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), getAuthorities(user));
}
}
private Collection<GrantedAuthority> getAuthorities(User user) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (UserProfile userProfile : user.getUserProfileSet()) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + userProfile.getType()));
}
return authorities;
}
}
The relevant bits of the controllers are:
#RequestMapping(value = "/", method = RequestMethod.GET)
public String index(Principal principal) {
if (logger.isDebugEnabled()) {
logger.debug("getWelcome is executed");
System.out.println(principal);
}
return principal == null ? "homeNotSignedIn" : "welcome";
}
#GetMapping("/login")
public String login(Model model, String error, String logout) {
if (error != null)
model.addAttribute("error", "Your username and password is invalid.");
if (logout != null)
System.out.println(">>>>>>>>>>>>>>>>>>> LOGOUT <<<<<<<<<<<<<<<<<<");
model.addAttribute("message", "You have been logged out successfully.");
return "login";
}
#GetMapping("/welcome")
public String welcome(Model model) {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>> WELCOME <<<<<<<<<<<<<<<<<<<<<<<");
return "welcome";
}
My pom.xml is:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ie.gtludwig.pa</groupId>
<artifactId>pa</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>PA</name>
<modules>
<module>core</module>
<module>engine</module>
</modules>
<properties>
<!-- JAVA -->
<version.java>1.8</version.java>
<java.version>${version.java}</java.version>
<jdk.version>${version.java}</jdk.version>
<maven.compiler.target>${version.java}</maven.compiler.target>
<maven.compiler.source>${version.java}</maven.compiler.source>
<!-- Generic properties -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- External Dependency Versions -->
<spring-boot.version>2.1.3.RELEASE</spring-boot.version>
<jedis.version>2.9.1</jedis.version>
<junit.version>4.12</junit.version>
<logback.version>1.2.3</logback.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
<flyway.version>5.2.0</flyway.version>
<mysql.version>8.0.16</mysql.version>
<h2database.version>1.4.199</h2database.version>
<sendgrid-java.version>4.3.0</sendgrid-java.version>
<commons-lang3.version>3.7</commons-lang3.version>
<commons-io.version>2.6</commons-io.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<!--WEB-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
<version>${thymeleaf-layout-dialect.version}</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<!--SECURITY-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--OPERATIONS-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--SERVER-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!--DATABASE-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!--TEST DATABASE FOR TESTING PROCESSES AND RULES IN-MEMORY-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2database.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.2.17.Final</version>
<scope>runtime</scope>
</dependency>
<!--EMAIL-->
<!-- https://mvnrepository.com/artifact/com.sendgrid/sendgrid-java -->
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
<version>${sendgrid-java.version}</version>
</dependency>
<!--DEV/TEST-->
<!--DEV TOOLS CONFLICT WITH JREBEL - DISABLE THIS IF JREBEL IS BEING USED -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</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>
<version>5.0.7.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<!-- Resource plugin to enable expanding properties from this file so that they can be exposed by the zone (E.g. #project.version#) -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<nonFilteredFileExtensions>
<!--font/binary files must be excluded from filtering or they will be corrupted-->
<nonFilteredFileExtension>woff2</nonFilteredFileExtension>
<nonFilteredFileExtension>woff</nonFilteredFileExtension>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>jks</nonFilteredFileExtension>
</nonFilteredFileExtensions>
<delimiters>
<delimiter>#</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<!-- Add our project version to the manifest file -->
<version>${project.version}</version>
<description>${project.description}</description>
</manifestEntries>
</archive>
<failOnMissingWebXml>false</failOnMissingWebXml>
<attachClasses>false</attachClasses>
</configuration>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>external</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</profile>
</profiles>
What happens is:
When I access localhost:8080/core I get the correct homeNotSignedIn page, but there is no CSS or JS loaded from /resources/**.
I click a login redirection link to the login page (again without CSS or JS) and CAN login.
The logged username is displayed to the console, so the customUserDetailsService seems to be working alright, but I don't get redirected to /welcome.
At this point, I can only logout by typing /logout to the URL and it works and sends me to homeNotSignedIn.
I have been going back and forth at this and I can't seem to get it done.
What am I missing?
I have been going back and forth at this and I can't seem to get it done.
What am I missing?
EDIT
I managed to load CSS and JS by changing:
.antMatchers("/resources/**").permitAll()
to:
.antMatchers("/css/**", "/js/**").permitAll()
When you are using successForwardUrl you will be forwarded to the given URL, but not redirected.
That means that the URL in your browser will not change, however it will display the HTML in your welcome.html.
If you would like to redirect, you can use defaultSuccessUrl as mentioned on the spring-security documentation.
Then your security configuration would look like this.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/welcome", true)
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/");
}

Tests fail with a TransactionRequiredException: no transaction is in progress exception when loading both JPA and Neo4J configurations

I have a JPA web application with some integration tests against the JPA repositories. There are no integration tests against the Neo4J repositories yet.
Now, I have added some Neo4J functionality to this existing JPA web application.
I'm this now using Neo4J repositories, alongside JPA repositories. My entities and repositories are named differently and are sitting in different packages.
My tests all extend the following class:
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { ApplicationConfiguration.class, WebSecurityTestConfiguration.class, WebConfiguration.class })
#Transactional
public abstract class AbstractControllerTest {
...
}
The tests run fine when the application configuration does not have any
Neo4J configuration:
#Configuration
#ComponentScan(basePackages = { "it.robot.rest.config" })
#Import({ DatabaseConfiguration.class, Log4jWeb.class })
public class ApplicationConfiguration {
}
But they error on an exception when adding the Neo4J configuration:
#Configuration
#ComponentScan(basePackages = { "it.robot.rest.config" })
#Import({ DatabaseConfiguration.class, Neo4JRepositoryConfiguration.class, Log4jWeb.class })
public class ApplicationConfiguration {
}
The exception is:
javax.persistence.TransactionRequiredException: no transaction is in progress
Here is the Neo4J configuration (I tried both Neo4jConfiguration and CrossStoreNeo4jConfiguration classes and I get the same exception):
#Configuration
#EnableNeo4jRepositories(basePackages = { "it.robot.data.neo4j.repository" } )
#EnableTransactionManagement
#ComponentScan(basePackages = { "it.robot.data.neo4j.service" })
public class Neo4JRepositoryConfiguration extends Neo4jConfiguration {
public static final String URL = "http://localhost:7474/db/data/";
public static final String LOGIN = "neo4j";
public static final String PASSWORD = "mypassword";
Neo4JRepositoryConfiguration() {
setBasePackage("it.robot.data.neo4j.domain");
}
#Bean
GraphDatabaseService graphDatabaseService() {
return new SpringCypherRestGraphDatabase(URL, LOGIN, PASSWORD);
}
}
Here is the JPA configuration is:
#Configuration
#Import({ JpaService.class, Log4j.class })
#EnableTransactionManagement
#ComponentScan(basePackages = { "it.robot.data.config" })
#EnableJpaRepositories(basePackages = { "it.robot.data.jpa" }, repositoryFactoryBeanClass = it.robot.data.jpa.repository.GenericRepositoryFactoryBean.class)
public class DatabaseConfiguration {
...
}
It looks like the Neo4jConfiguration class transaction manager has the same name ("transactionManager") as the JPA transaction manager, and overrides it.
I would content myself with Neo4J using the JPA transaction manager provided by Spring but I wonder if that is possible.
Some additional information...
I'm using spring-data-neo4j and spring-data-neo4j-rest version 3.3.2.RELEASE
I'm using a server Neo4J database and not an embedded one and of course the Neo4J server is started.
I disabled the authentication on the database since it was standing in my way and my curl request didn't seem to update the password:
curl -H "Accept:application/json"
-H "Content-Type: application/json"
"http://localhost:7474/user/neo4j/password"
-X POST -d "{ \"password\" : \"myownpassword\" }"
The only user I know of doesn't seem to be too vocal:
stephane#stephane-ThinkPad-X301:~> curl -H "Accept:application/json" -H "Content-Type: application/json" "http://localhost:7474/user/neo4j"
stephane#stephane-ThinkPad-X301:~>
stephane#stephane-ThinkPad-X301:~>
I have not created any "schema/structure" in the graph and am not sure if I should do.
The Neo4J entities:
#NodeEntity
#SequenceGenerator(name = "id_generator", sequenceName = "sq_id_part")
public class Neo4JPart extends BaseEntity {
#Column(nullable = false)
private String name;
#Column(nullable = false, unique = true)
private String serialNumber;
private Integer weight;
#ManyToOne
#JoinColumn(name = "manufacturer_id", nullable = false)
private Neo4JManufacturer manufacturer;
#Fetch
#RelatedTo(type = "part", direction = Direction.BOTH)
public Set<Neo4JPart> parts;
public Neo4JPart() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public Neo4JManufacturer getManufacturer() {
return manufacturer;
}
public void setManufacturer(Neo4JManufacturer manufacturer) {
this.manufacturer = manufacturer;
}
public Set<Neo4JPart> getParts() {
return parts;
}
public void setParts(Set<Neo4JPart> parts) {
this.parts = parts;
}
public String toString() {
String results = name + "'s compatible parts include\n";
if (parts != null) {
for (Neo4JPart part : parts) {
results += "\t- " + part.name + "\n";
}
}
return results;
}
}
#MappedSuperclass
public class BaseEntity {
#GraphId
#GeneratedValue(strategy = GenerationType.AUTO, generator = "id_generator")
#Column(name = "id")
private Long id;
#Version
#Column(nullable = false)
private int version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public int getVersion() {
return this.version;
}
public void setVersion(int version) {
this.version = version;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) {
return false;
}
BaseEntity that = (BaseEntity) obj;
return this.id.equals(that.getId());
}
#Override
public int hashCode() {
return id == null ? 0 : id.hashCode();
}
}
And the Neo4J repositories:
public interface Neo4JPartRepository extends GraphRepository<Neo4JPart> {
public Neo4JPart findByName(String name);
public Neo4JPart findBySerialNumber(String serialNumber);
public Page<Neo4JPart> findByManufacturer(#Param("manufacturer") Neo4JManufacturer manufacturer, Pageable page);
public List<Neo4JPart> findByManufacturer(#Param("manufacturer") Neo4JManufacturer manufacturer);
public Page<Neo4JPart> findByPartsName(String name, Pageable page);
}
public interface Neo4JManufacturerRepository extends GraphRepository<Neo4JManufacturer> {
Neo4JManufacturer findByName(String name);
}
The Maven dependencies are:
<org.springframework.version>4.1.2.RELEASE</org.springframework.version>
<hibernate.version>4.3.6.Final</hibernate.version>
<dependencies>
<dependency>
<groupId>com.thalasoft</groupId>
<artifactId>toolbox</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.172</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.6.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-core</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.10.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.lazyluke</groupId>
<artifactId>log4jdbc-remix</artifactId>
<version>0.2.7</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.jadira.usertype</groupId>
<artifactId>usertype.jodatime</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.jasypt</groupId>
<artifactId>jasypt</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>3.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-rest</artifactId>
<version>3.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-cross-store</artifactId>
<version>3.3.2.RELEASE</version>
</dependency>
</dependencies>
I tried to upgrade to the 3.4.0 version of the newly available release on search.maven.org but the build now gives the following exception:
AnnotationFormatError: Invalid default: public abstract java.lang.Class org.springframework.data.neo4j.config.EnableNeo4jRepositories.repositoryBaseClass()
I could see nothing about that repositoryBaseClass in the reference documentation http://docs.spring.io/spring-data/neo4j/docs/3.4.0.RELEASE/reference/pdf/spring-data-neo4j-reference.pdf
The source code Javadoc only says:
Configure the repository base class to be used to create repository proxies for this particular configuration.
And that left me scratching my head wondering what is a repository proxy and if one is required in my case.
I could solve the issue with the solution provided at Implementing Spring ChainedTransactionManager according to the "best efforts 1PC" pattern with a chained transaction manager, following a tip by Simon at How do I properly set up cross-store persistence using Spring Data JPA + Neo4j?
I just had to change my Neo4j configuration. I didn't even have to touch anything in the other JPA transaction manager.
Here is my Neo4j configuration:
#EnableNeo4jRepositories(basePackages = { "it.robot.data.neo4j.repository" })
#EnableTransactionManagement
#ComponentScan(basePackages = { "it.robot.data.neo4j.service" })
public class Neo4JRepositoryConfiguration extends Neo4jConfiguration {
private static Logger logger = LoggerFactory.getLogger(Neo4JRepositoryConfiguration.class);
public static final String URL = "http://localhost:7474/db/data/";
public static final String LOGIN = "neo4j";
public static final String PASSWORD = "xxxxx";
Neo4JRepositoryConfiguration() {
setBasePackage("it.robot.data.neo4j.domain");
}
#Bean
GraphDatabaseService graphDatabaseService() {
return new SpringCypherRestGraphDatabase(URL, LOGIN, PASSWORD);
}
#Autowired
LocalContainerEntityManagerFactoryBean entityManagerFactory;
#Override
public PlatformTransactionManager neo4jTransactionManager(
GraphDatabaseService graphDatabaseService) {
return new ChainedTransactionManager(
new JpaTransactionManager(entityManagerFactory.getObject()),
new JtaTransactionManagerFactoryBean(graphDatabaseService).getObject());
}
}

Resources