#63 added mvc tests on security

This commit is contained in:
Fabio Formosa
2022-09-30 23:42:30 +02:00
parent 8cb0ac09e8
commit adb8e06f0a
22 changed files with 559 additions and 444 deletions

View File

@@ -51,18 +51,17 @@ import {
AuthService,
UserService,
SchedulerService,
JobService,
ConfigService,
ProgressWebsocketService,
LogsWebsocketService,
getHtmlBaseUrl,
TriggerService
} from './services';
import { ChangePasswordComponent } from './views/change-password/change-password.component';
import { ForbiddenComponent } from './views/forbidden/forbidden.component';
import { APP_BASE_HREF } from '@angular/common';
import { environment } from '../environments/environment';
import {SimpleTriggerConfigComponent} from './components/simple-trigger-config';
import JobService from './services/job.service';
export function initUserFactory(userService: UserService) {
return () => userService.jsessionInitUser();
@@ -116,7 +115,6 @@ export function jwtOptionsFactory(apiService: ApiService) {
SchedulerControlComponent,
LogsPanelComponent,
ProgressPanelComponent,
ChangePasswordComponent,
ForbiddenComponent,
TriggerListComponent
],

View File

@@ -1,16 +1,17 @@
<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">
<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>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>3.1.1-SNAPSHOT</version>
</parent>
<artifactId>quartz-manager-starter-security</artifactId>
<name>Quartz Manager Starter Security</name>
<description>Security Layer for Quartz Manager. Import it in your spring webapp</description>
<url>https://github.com/fabioformosa/quartz-manager</url>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
@@ -18,16 +19,12 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
@@ -51,7 +48,39 @@
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependency>
<!-- TEST -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</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-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,184 @@
package it.fabioformosa.quartzmanager.security;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import com.fasterxml.jackson.databind.ObjectMapper;
import it.fabioformosa.quartzmanager.security.properties.InMemoryAccountProperties;
import it.fabioformosa.quartzmanager.security.properties.JwtSecurityProperties;
import it.fabioformosa.quartzmanager.security.helpers.LoginConfigurer;
import it.fabioformosa.quartzmanager.security.helpers.impl.AuthenticationFailureHandler;
import it.fabioformosa.quartzmanager.security.helpers.impl.AuthenticationSuccessHandler;
import it.fabioformosa.quartzmanager.security.helpers.impl.FormLoginConfig;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtAuthenticationSuccessHandler;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtAuthenticationSuccessHandlerImpl;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtTokenAuthenticationFilter;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtTokenHelper;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtUsernamePasswordFiterLoginConfig;
import it.fabioformosa.quartzmanager.security.helpers.impl.LogoutSuccess;
import it.fabioformosa.quartzmanager.security.helpers.impl.QuartzManagerHttpSecurity;
/**
* @author Fabio.Formosa
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
private static final String[] PATTERNS_SWAGGER_UI = {"/swagger-ui.html", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**"};
public static final String QUARTZ_MANAGER_CONTEXT_PATH = "/quartz-manager";
protected static final String LOGIN_PATH = QUARTZ_MANAGER_CONTEXT_PATH + "/api/login";
private static final String LOGOUT_PATH = QUARTZ_MANAGER_CONTEXT_PATH + "/api/logout";
private static final String WEBJAR_PATH = "/quartz-manager-ui";
@Value("${server.servlet.context-path:/}")
private String contextPath;
@Value("${app.name:quartz-manager}")
private String appName;
@Value("${quartz-manager.security.login-model.form-login-enabled:true}")
private Boolean formLoginEnabled;
@Value("${quartz-manager.security.login-model.userpwd-filter-enabled:false}")
private Boolean userpwdFilterEnabled;
@Autowired
private JwtSecurityProperties jwtSecurityProps;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private InMemoryAccountProperties inMemoryAccountProps;
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
configureInMemoryAuthentication(authenticationManagerBuilder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //
.exceptionHandling().authenticationEntryPoint(restAuthEntryPoint()).and() //
.addFilterBefore(jwtAuthenticationTokenFilter(), BasicAuthenticationFilter.class) //
.authorizeRequests().anyRequest().authenticated();
QuartzManagerHttpSecurity.from(http).withLoginConfigurer(loginConfigurer(), logoutConfigurer()) //
.login(LOGIN_PATH, authenticationManager()).logout(LOGOUT_PATH);
// temporary disabled csfr
// http.csrf().ignoringAntMatchers("/api/login", "/api/signup") //
// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) //
}
@Override
public void configure(WebSecurity web) {
web.ignoring()//
.antMatchers(HttpMethod.GET, PATTERNS_SWAGGER_UI) //
.antMatchers(HttpMethod.GET, WEBJAR_PATH + "/css/**", WEBJAR_PATH + "/js/**", WEBJAR_PATH + "/img/**", WEBJAR_PATH + "/lib/**", WEBJAR_PATH + "/assets/**");
}
private void configureInMemoryAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
if (inMemoryAccountProps.isEnabled() && inMemoryAccountProps.getUsers() != null && !inMemoryAccountProps.getUsers().isEmpty()) {
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuth = authenticationManagerBuilder.inMemoryAuthentication();
inMemoryAccountProps.getUsers()
.forEach(u -> inMemoryAuth
.withUser(u.getName())
.password(encoder.encode(u.getPassword()))
.roles(u.getRoles().toArray(new String[0])));
}
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
public LoginConfigurer formLoginConfigurer() {
JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler = jwtAuthenticationSuccessHandler();
AuthenticationSuccessHandler authenticationSuccessHandler = new AuthenticationSuccessHandler(jwtAuthenticationSuccessHandler);
AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationFailureHandler();
LoginConfigurer loginConfigurer = new FormLoginConfig(authenticationSuccessHandler, authenticationFailureHandler);
return loginConfigurer;
}
@Bean
public JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
JwtTokenHelper jwtTokenHelper = jwtTokenHelper();
JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler = new JwtAuthenticationSuccessHandlerImpl(contextPath, jwtSecurityProps, jwtTokenHelper, objectMapper);
return jwtAuthenticationSuccessHandler;
}
// @Bean
public JwtTokenAuthenticationFilter jwtAuthenticationTokenFilter() {
return new JwtTokenAuthenticationFilter(jwtTokenHelper(), userDetailsService);
}
@Bean
public JwtTokenHelper jwtTokenHelper() {
return new JwtTokenHelper(appName, jwtSecurityProps);
}
public LoginConfigurer loginConfigurer() {
if (BooleanUtils.isTrue(userpwdFilterEnabled))
return userpwdFilterLoginConfigurer();
if (BooleanUtils.isNotFalse(formLoginEnabled))
return formLoginConfigurer();
throw new RuntimeException("No login configurer enabled!");
}
public LogoutSuccess logoutConfigurer() {
return new LogoutSuccess(objectMapper);
}
@Bean
public AuthenticationEntryPoint restAuthEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
public LoginConfigurer userpwdFilterLoginConfigurer() {
LoginConfigurer loginConfigurer = new JwtUsernamePasswordFiterLoginConfig(jwtAuthenticationSuccessHandler());
return loginConfigurer;
}
}

View File

@@ -1,194 +0,0 @@
package it.fabioformosa.quartzmanager.security.configuration;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import com.fasterxml.jackson.databind.ObjectMapper;
import it.fabioformosa.quartzmanager.security.configuration.properties.InMemoryAccountProperties;
import it.fabioformosa.quartzmanager.security.configuration.properties.JwtSecurityProperties;
import it.fabioformosa.quartzmanager.security.helpers.LoginConfigurer;
import it.fabioformosa.quartzmanager.security.helpers.impl.AuthenticationFailureHandler;
import it.fabioformosa.quartzmanager.security.helpers.impl.AuthenticationSuccessHandler;
import it.fabioformosa.quartzmanager.security.helpers.impl.FormLoginConfig;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtAuthenticationSuccessHandler;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtAuthenticationSuccessHandlerImpl;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtTokenAuthenticationFilter;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtTokenHelper;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtUsernamePasswordFiterLoginConfig;
import it.fabioformosa.quartzmanager.security.helpers.impl.LogoutSuccess;
import it.fabioformosa.quartzmanager.security.helpers.impl.QuartzManagerHttpSecurity;
/**
*
* @author Fabio.Formosa
*
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
private static final String[] PATTERNS_SWAGGER_UI = {"/swagger-ui.html", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**"};
private static final String LOGIN_PATH = "/quartz-manager/api/login";
private static final String LOGOUT_PATH = "/quartz-manager/api/logout";
private static final String WEBJAR_PATH = "/quartz-manager-ui";
@Value("${server.servlet.context-path:/}")
private String contextPath;
@Value("${app.name:quartz-manager}")
private String appName;
@Value("${quartz-manager.security.login-model.form-login-enabled}")
private Boolean formLoginEnabled;
@Value("${quartz-manager.security.login-model.userpwd-filter-enabled}")
private Boolean userpwdFilterEnabled;
@Autowired
private JwtSecurityProperties jwtSecurityProps;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private InMemoryAccountProperties inMemoryAccountProps;
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder)throws Exception {
configureInMemoryAuthentication(authenticationManagerBuilder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //
.exceptionHandling().authenticationEntryPoint(restAuthEntryPoint()).and() //
.addFilterBefore(jwtAuthenticationTokenFilter(), BasicAuthenticationFilter.class) //
.authorizeRequests().anyRequest().authenticated();
QuartzManagerHttpSecurity.from(http).withLoginConfigurer(loginConfigurer(), logoutConfigurer()) //
.login(LOGIN_PATH, authenticationManager()).logout(LOGOUT_PATH);
// temporary disabled csfr
// http.csrf().ignoringAntMatchers("/api/login", "/api/signup") //
// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) //
}
@Override
public void configure(WebSecurity web) {
web.ignoring()//
.antMatchers(HttpMethod.GET, PATTERNS_SWAGGER_UI) //
.antMatchers(HttpMethod.GET, WEBJAR_PATH + "/css/**", WEBJAR_PATH + "/js/**", WEBJAR_PATH + "/img/**", WEBJAR_PATH + "/lib/**", WEBJAR_PATH + "/assets/**");
}
private void configureInMemoryAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
if(inMemoryAccountProps.isEnabled() && inMemoryAccountProps.getUsers() != null && !inMemoryAccountProps.getUsers().isEmpty()) {
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuth = authenticationManagerBuilder.inMemoryAuthentication();
inMemoryAccountProps.getUsers()
.forEach(u -> inMemoryAuth
.withUser(u.getName())
.password(encoder.encode(u.getPassword()))
.roles(u.getRoles().toArray(new String[0])));
}
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
@Bean
public LoginConfigurer formLoginConfigurer() {
JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler = jwtAuthenticationSuccessHandler();
AuthenticationSuccessHandler authenticationSuccessHandler = new AuthenticationSuccessHandler(jwtAuthenticationSuccessHandler);
AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationFailureHandler();
LoginConfigurer loginConfigurer = new FormLoginConfig(authenticationSuccessHandler, authenticationFailureHandler);
return loginConfigurer;
}
@Bean
public JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
JwtTokenHelper jwtTokenHelper = jwtTokenHelper();
JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler = new JwtAuthenticationSuccessHandlerImpl(contextPath, jwtSecurityProps, jwtTokenHelper, objectMapper);
return jwtAuthenticationSuccessHandler;
}
@Bean
public JwtTokenAuthenticationFilter jwtAuthenticationTokenFilter() {
return new JwtTokenAuthenticationFilter(jwtTokenHelper(), userDetailsService);
}
@Bean
public JwtTokenHelper jwtTokenHelper() {
return new JwtTokenHelper(appName, jwtSecurityProps);
}
@Bean
public LoginConfigurer loginConfigurer() {
if(BooleanUtils.isTrue(userpwdFilterEnabled))
return userpwdFilterLoginConfigurer();
if(BooleanUtils.isNotFalse(formLoginEnabled))
return formLoginConfigurer();
throw new RuntimeException("No login configurer enabled!");
}
@Bean
public LogoutSuccess logoutConfigurer() {
return new LogoutSuccess(objectMapper);
}
@Bean
public AuthenticationEntryPoint restAuthEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
@Bean
public LoginConfigurer userpwdFilterLoginConfigurer() {
LoginConfigurer loginConfigurer = new JwtUsernamePasswordFiterLoginConfig(jwtAuthenticationSuccessHandler());
return loginConfigurer;
}
// @Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
}

View File

@@ -1,33 +0,0 @@
package it.fabioformosa.quartzmanager.security.configuration.properties;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "quartz-manager.security.jwt")
@Getter @Setter
public class JwtSecurityProperties {
private boolean enabled;
private String secret;
private long expirationInSec;
private CookieStrategy cookieStrategy;
private HeaderStrategy headerStrategy;
@Data
public static class CookieStrategy {
private boolean enabled;
private String cookie;
}
@Data
public static class HeaderStrategy {
private boolean enabled;
private String header;
}
}

View File

@@ -1,7 +1,7 @@
package it.fabioformosa.quartzmanager.security.helpers.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import it.fabioformosa.quartzmanager.security.configuration.properties.JwtSecurityProperties;
import it.fabioformosa.quartzmanager.security.properties.JwtSecurityProperties;
import it.fabioformosa.quartzmanager.security.models.UserTokenState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -3,7 +3,7 @@ package it.fabioformosa.quartzmanager.security.helpers.impl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import it.fabioformosa.quartzmanager.security.configuration.properties.JwtSecurityProperties;
import it.fabioformosa.quartzmanager.security.properties.JwtSecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -1,55 +0,0 @@
package it.fabioformosa.quartzmanager.security.models;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.springframework.security.core.GrantedAuthority;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* Temporary enabled only inMemoryAuthentication
*
* @author Fabio.Formosa
*
*/
//@Entity
//@Table(name="Authority")
public class Authority implements GrantedAuthority {
private static final long serialVersionUID = 1L;
@Id
@Column(name="id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name="name")
String name;
@Override
public String getAuthority() {
return name;
}
@JsonIgnore
public Long getId() {
return id;
}
@JsonIgnore
public String getName() {
return name;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -1,132 +0,0 @@
package it.fabioformosa.quartzmanager.security.models;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* Temporary enabled only inMemoryAuthentication
*
* @author Fabio.Formosa
*
*/
//@Entity
//@Table(name = "USER")
public class User implements UserDetails, Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username")
private String username;
@JsonIgnore
@Column(name = "password")
private String password;
@Column(name = "firstname")
private String firstname;
@Column(name = "lastname")
private String lastname;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "user_authority", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id"))
private List<Authority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public String getFirstname() {
return firstname;
}
public Long getId() {
return id;
}
public String getLastname() {
return lastname;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
// We can add the below fields in the users table.
// For now, they are hardcoded.
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
public void setAuthorities(List<Authority> authorities) {
this.authorities = authorities;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public void setId(Long id) {
this.id = id;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@@ -0,0 +1,35 @@
package it.fabioformosa.quartzmanager.security.properties;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "quartz-manager.security.jwt")
@Getter
@Setter
public class JwtSecurityProperties {
private boolean enabled = true;
private String secret = RandomStringUtils.randomAlphabetic(10);
private long expirationInSec = 28800;
private CookieStrategy cookieStrategy = new CookieStrategy();
private HeaderStrategy headerStrategy = new HeaderStrategy();
@Data
public static class CookieStrategy {
private boolean enabled = false;
private String cookie = "AUTH-TOKEN";
}
@Data
public static class HeaderStrategy {
private boolean enabled = true;
private String header = "Authorization";
}
}

View File

@@ -1,4 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
it.fabioformosa.quartzmanager.security.configuration.WebSecurityConfigJWT,\
it.fabioformosa.quartzmanager.security.configuration.properties.JwtSecurityProperties,\
it.fabioformosa.quartzmanager.security.configuration.properties.InMemoryAccountProperties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
it.fabioformosa.quartzmanager.security.WebSecurityConfigJWT,\
it.fabioformosa.quartzmanager.security.properties.JwtSecurityProperties,\
it.fabioformosa.quartzmanager.security.properties.InMemoryAccountProperties

View File

@@ -0,0 +1,29 @@
package it.fabioformosa.quartzmanager.security;
import it.fabioformosa.quartzmanager.security.models.UserTokenState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static it.fabioformosa.quartzmanager.security.WebSecurityConfigJWT.LOGIN_PATH;
public class AbstractSecurityLoginTest {
@Autowired
private TestRestTemplate testRestTemplate;
protected ResponseEntity<UserTokenState> doLogin() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("username", "foo");
map.add("password", "bar");
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map, headers);
ResponseEntity<UserTokenState> responseEntity = testRestTemplate.exchange(LOGIN_PATH, HttpMethod.POST, entity, UserTokenState.class);
return responseEntity;
}
}

View File

@@ -0,0 +1,71 @@
package it.fabioformosa.quartzmanager.security;
import it.fabioformosa.quartzmanager.security.controllers.TestController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static it.fabioformosa.quartzmanager.security.WebSecurityConfigJWT.LOGIN_PATH;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = {
"quartz-manager.security.jwt.enabled=true",
"quartz-manager.security.jwt.secret=bibidibobidiboo",
"quartz-manager.security.jwt.expiration-in-sec=28800",
"quartz-manager.security.jwt.header-strategy.enabled=false",
"quartz-manager.security.jwt.header-strategy.header=Authorization",
"quartz-manager.security.jwt.cookie-strategy.enabled=true",
"quartz-manager.security.jwt.cookie-strategy.cookie=AUTH-TOKEN",
"quartz-manager.accounts.in-memory.enabled=true",
"quartz-manager.accounts.in-memory.users[0].name=foo",
"quartz-manager.accounts.in-memory.users[0].password=bar",
"quartz-manager.accounts.in-memory.users[0].roles[0]=admin",
})
public class SecurityControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void givenAnAnonymousUser_whenCalledATestController_thenShouldRaiseForbidden() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/test"))
.andExpect(status().isUnauthorized());
}
@Test
void givenAnAnonymousUser_whenCalledATestScheduler_thenShouldRaiseForbidden() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get(TestController.QUARTZ_MANAGER + "/scheduler"))
.andExpect(status().isUnauthorized());
}
@Test
void givenAnAnonymousUser_whenRequestedSwaggerResource_thenShouldReturn2xx() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/swagger-ui.html"))
.andExpect(status().isOk());
}
@Test
@WithMockUser("admin")
void givenAnUser_whenCalledATestScheduler_thenShouldReturn2xx() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get(TestController.QUARTZ_MANAGER + "/scheduler"))
.andExpect(status().isOk());
}
@Test
void givenAnAnonymousUser_whenCalledTheLoginPath_thenShouldReturn2xx() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post(LOGIN_PATH)
.contentType("application/x-www-form-urlencoded")
.accept("application/json")
.param("username", "foo")
.param("password", "bar"))
.andExpect(status().isOk());
}
}

View File

@@ -0,0 +1,40 @@
package it.fabioformosa.quartzmanager.security;
import it.fabioformosa.quartzmanager.security.models.UserTokenState;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"quartz-manager.security.login-model.form-login-enabled = true",
"quartz-manager.security.login-model.userpwd-filter-enabled = false",
"quartz-manager.security.jwt.enabled=true",
"quartz-manager.security.jwt.secret=bibidibobidiboo",
"quartz-manager.security.jwt.expiration-in-sec=28800",
"quartz-manager.security.jwt.header-strategy.enabled=false",
"quartz-manager.security.jwt.header-strategy.header=Authorization",
"quartz-manager.security.jwt.cookie-strategy.enabled=true",
"quartz-manager.security.jwt.cookie-strategy.cookie=AUTH-TOKEN",
"quartz-manager.accounts.in-memory.enabled=true",
"quartz-manager.accounts.in-memory.users[0].name=foo",
"quartz-manager.accounts.in-memory.users[0].password=bar",
"quartz-manager.accounts.in-memory.users[0].roles[0]=admin",
})
public class SecurityLoginViaCookieTest extends AbstractSecurityLoginTest {
@Test
void givenAnAnonymousUser_whenTheLoginIsSubmitted_thenShouldReturn2xx() {
ResponseEntity<UserTokenState> responseEntity = doLogin();
Assertions.assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
Assertions.assertThat(responseEntity.getBody().getAccess_token()).isNotEmpty();
Assertions.assertThat(responseEntity.getBody().getExpires_in_sec()).isNotNull().isPositive();
Assertions.assertThat(responseEntity.getHeaders().get("set-cookie")).hasSizeGreaterThan(0);
Assertions.assertThat(responseEntity.getHeaders().get("set-cookie").get(0)).startsWith("AUTH-TOKEN");
}
}

View File

@@ -0,0 +1,30 @@
package it.fabioformosa.quartzmanager.security;
import it.fabioformosa.quartzmanager.security.models.UserTokenState;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"quartz-manager.accounts.in-memory.enabled=true",
"quartz-manager.accounts.in-memory.users[0].name=foo",
"quartz-manager.accounts.in-memory.users[0].password=bar",
"quartz-manager.accounts.in-memory.users[0].roles[0]=admin",
})
public class SecurityLoginViaDefaultStrategyTest extends AbstractSecurityLoginTest {
@Test
void givenAnAnonymousUser_whenTheLoginIsSubmitted_thenShouldReturn2xx() {
ResponseEntity<UserTokenState> responseEntity = doLogin();
Assertions.assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
Assertions.assertThat(responseEntity.getBody().getAccess_token()).isNotEmpty();
Assertions.assertThat(responseEntity.getBody().getExpires_in_sec()).isNotNull().isPositive();
Assertions.assertThat(responseEntity.getHeaders().get("set-cookie")).isNull();
}
}

View File

@@ -0,0 +1,38 @@
package it.fabioformosa.quartzmanager.security;
import it.fabioformosa.quartzmanager.security.models.UserTokenState;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"quartz-manager.security.login-model.form-login-enabled = false",
"quartz-manager.security.login-model.userpwd-filter-enabled = true",
"quartz-manager.security.jwt.enabled=true",
"quartz-manager.security.jwt.secret=bibidibobidiboo",
"quartz-manager.security.jwt.expiration-in-sec=28800",
"quartz-manager.security.jwt.header-strategy.enabled=true",
"quartz-manager.security.jwt.header-strategy.header=Authorization",
"quartz-manager.security.jwt.cookie-strategy.enabled=false",
"quartz-manager.accounts.in-memory.enabled=true",
"quartz-manager.accounts.in-memory.users[0].name=foo",
"quartz-manager.accounts.in-memory.users[0].password=bar",
"quartz-manager.accounts.in-memory.users[0].roles[0]=admin",
})
public class SecurityLoginViaHeaderAndLoginFilterTest extends AbstractSecurityLoginTest {
@Test
void givenAnAnonymousUser_whenTheLoginIsSubmitted_thenShouldReturn2xx() {
ResponseEntity<UserTokenState> responseEntity = doLogin();
Assertions.assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
Assertions.assertThat(responseEntity.getBody().getAccess_token()).isNotEmpty();
Assertions.assertThat(responseEntity.getBody().getExpires_in_sec()).isNotNull().isPositive();
Assertions.assertThat(responseEntity.getHeaders().get("set-cookie")).isNull();
}
}

View File

@@ -0,0 +1,37 @@
package it.fabioformosa.quartzmanager.security;
import it.fabioformosa.quartzmanager.security.models.UserTokenState;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.*;
import org.springframework.test.context.TestPropertySource;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"quartz-manager.security.login-model.form-login-enabled = true",
"quartz-manager.security.login-model.userpwd-filter-enabled = false",
"quartz-manager.security.jwt.enabled=true",
"quartz-manager.security.jwt.secret=bibidibobidiboo",
"quartz-manager.security.jwt.expiration-in-sec=28800",
"quartz-manager.security.jwt.header-strategy.enabled=true",
"quartz-manager.security.jwt.header-strategy.header=Authorization",
"quartz-manager.security.jwt.cookie-strategy.enabled=false",
"quartz-manager.accounts.in-memory.enabled=true",
"quartz-manager.accounts.in-memory.users[0].name=foo",
"quartz-manager.accounts.in-memory.users[0].password=bar",
"quartz-manager.accounts.in-memory.users[0].roles[0]=admin",
})
public class SecurityLoginViaHeaderTest extends AbstractSecurityLoginTest {
@Test
void givenAnAnonymousUser_whenTheLoginIsSubmitted_thenShouldReturn2xx() {
ResponseEntity<UserTokenState> responseEntity = doLogin();
Assertions.assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
Assertions.assertThat(responseEntity.getBody().getAccess_token()).isNotEmpty();
Assertions.assertThat(responseEntity.getBody().getExpires_in_sec()).isNotNull().isPositive();
Assertions.assertThat(responseEntity.getHeaders().get("set-cookie")).isNull();
}
}

View File

@@ -0,0 +1,7 @@
package it.fabioformosa.quartzmanager.security;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringApplicationTest {
}

View File

@@ -0,0 +1,33 @@
package it.fabioformosa.quartzmanager.security.controllers;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping
@RestController
public class TestController {
public static final String QUARTZ_MANAGER = "/quartz-manager";
@ResponseStatus(HttpStatus.OK)
@GetMapping("/test")
public void getDMZTest(){
}
@ResponseStatus(HttpStatus.OK)
@GetMapping("/swagger-ui.html")
public void getSwaggerUI(){
}
@ResponseStatus(HttpStatus.OK)
@GetMapping(QUARTZ_MANAGER + "/scheduler")
public void getQuartzManagerScheduler(){
}
}

View File

@@ -9,7 +9,7 @@
</parent>
<artifactId>quartz-manager-web-showcase</artifactId>
<packaging>war</packaging>
<name>Quartz Manager Web Showcase</name>
@@ -21,7 +21,7 @@
<springfox.version>2.9.2</springfox.version>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
@@ -39,7 +39,7 @@
<!-- <groupId>it.fabioformosa.quartz-manager</groupId>-->
<!-- <artifactId>quartz-manager-starter-persistence</artifactId>-->
<!-- </dependency>-->
<!-- SPRING -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -68,7 +68,7 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MISC -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
@@ -118,8 +118,8 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
@@ -146,6 +146,6 @@
</plugins>
</pluginManagement>
</build>
</project>

View File

@@ -26,8 +26,6 @@ logging:
org.quartz: INFO
quartz-manager:
trigger:
name: "sampletrigger"
persistence:
quartz:
datasource: