From 6f50f45aa8847dae71fa30c002702f011015a283 Mon Sep 17 00:00:00 2001 From: "fabio.formosa" Date: Sun, 31 Jan 2021 20:50:41 +0100 Subject: [PATCH] #30 added new module with all config for a security layer --- .../src/app/services/api.service.ts | 6 - quartz-manager-parent/pom.xml | 2 +- .../quartz-manager-security/pom.xml | 57 +++++ .../configuration/WebSecurityConfigJWT.java | 194 +++++++++++++++++ .../properties/InMemoryAccountProperties.java | 24 ++ .../properties/JwtSecurityProperties.java | 33 +++ .../security/helpers/LoginConfigurer.java | 21 ++ .../impl/AjaxAuthenticationFilter.java | 55 +++++ .../helpers/impl/AnonAuthentication.java | 45 ++++ .../impl/AuthenticationFailureHandler.java | 20 ++ .../impl/AuthenticationSuccessHandler.java | 35 +++ .../helpers/impl/ComboEntryPoint.java | 33 +++ .../helpers/impl/FormLoginConfig.java | 75 +++++++ .../helpers/impl/JwtAuthenticationFilter.java | 34 +++ .../impl/JwtAuthenticationSuccessHandler.java | 14 ++ .../JwtAuthenticationSuccessHandlerImpl.java | 78 +++++++ .../impl/JwtTokenAuthenticationFilter.java | 105 +++++++++ .../impl/JwtTokenBasedAuthentication.java | 42 ++++ .../security/helpers/impl/JwtTokenHelper.java | 163 ++++++++++++++ .../JwtUsernamePasswordFiterLoginConfig.java | 46 ++++ .../security/helpers/impl/LogoutSuccess.java | 36 +++ .../impl/QuartzManagerHttpSecurity.java | 57 +++++ .../helpers/impl/RESTRequestMatcher.java | 26 +++ .../impl/RestAuthenticationEntryPoint.java | 23 ++ .../helpers/impl/WebsocketRequestMatcher.java | 18 ++ .../security/models/Authority.java | 53 +++++ .../quartzmanager/security/models/User.java | 133 +++++++++++ .../security/models/UserRequest.java | 56 +++++ .../security/models/UserTokenState.java | 32 +++ .../repositories/AuthorityRepository.java | 13 ++ .../security/repositories/UserRepository.java | 11 + .../security/services/AuthorityService.java | 13 ++ .../security/services/UserService.java | 18 ++ .../services/impl/AuthorityServiceImpl.java | 33 +++ .../impl/CustomUserDetailsService.java | 67 ++++++ .../services/impl/UserServiceImpl.java | 79 +++++++ .../main/resources/META-INF/spring.factories | 4 + .../quartz-manager-ui-webjar/pom.xml | 206 +++++++++--------- .../quartz-manager-web-showcase/pom.xml | 8 +- 39 files changed, 1856 insertions(+), 112 deletions(-) create mode 100644 quartz-manager-parent/quartz-manager-security/pom.xml create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/WebSecurityConfigJWT.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/properties/InMemoryAccountProperties.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/properties/JwtSecurityProperties.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/LoginConfigurer.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AjaxAuthenticationFilter.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AnonAuthentication.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AuthenticationFailureHandler.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AuthenticationSuccessHandler.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/ComboEntryPoint.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/FormLoginConfig.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationFilter.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationSuccessHandler.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationSuccessHandlerImpl.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenAuthenticationFilter.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenBasedAuthentication.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenHelper.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtUsernamePasswordFiterLoginConfig.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/LogoutSuccess.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/QuartzManagerHttpSecurity.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/RESTRequestMatcher.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/RestAuthenticationEntryPoint.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/WebsocketRequestMatcher.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/Authority.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/User.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/UserRequest.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/UserTokenState.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/repositories/AuthorityRepository.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/repositories/UserRepository.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/AuthorityService.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/UserService.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/AuthorityServiceImpl.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/CustomUserDetailsService.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/UserServiceImpl.java create mode 100644 quartz-manager-parent/quartz-manager-security/src/main/resources/META-INF/spring.factories diff --git a/quartz-manager-frontend/src/app/services/api.service.ts b/quartz-manager-frontend/src/app/services/api.service.ts index d89a780..c7fde7c 100644 --- a/quartz-manager-frontend/src/app/services/api.service.ts +++ b/quartz-manager-frontend/src/app/services/api.service.ts @@ -53,9 +53,6 @@ export class ApiService { if (args) options['params'] = serialize(args); - // if(this.jwtToken) - // options.headers = options.headers.set('Authorization', `Bearer ${this.jwtToken}`); - return this.http.get(path, options) .pipe(catchError(this.checkError.bind(this))); } @@ -78,9 +75,6 @@ export class ApiService { withCredentials: true } - // if(this.jwtToken) - // options.headers = options.headers.append('Authorization', `Bearer ${this.jwtToken}`); - const req = new HttpRequest(method, path, body, options); return this.http.request(req) diff --git a/quartz-manager-parent/pom.xml b/quartz-manager-parent/pom.xml index 8fe58c4..a6213f7 100644 --- a/quartz-manager-parent/pom.xml +++ b/quartz-manager-parent/pom.xml @@ -19,7 +19,7 @@ quartz-manager-api quartz-manager-ui-webjar quartz-manager-web-showcase - + quartz-manager-security diff --git a/quartz-manager-parent/quartz-manager-security/pom.xml b/quartz-manager-parent/quartz-manager-security/pom.xml new file mode 100644 index 0000000..d8945a3 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + + it.fabioformosa.quartz-manager + quartz-manager-parent + 2.2.2-SNAPSHOT + + + quartz-manager-security + + Quartz Manager Security + Security Layer for Quartz Manager + + https://github.com/fabioformosa/quartz-manager + + ${basedir}/../.. + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-configuration-processor + true + + + io.jsonwebtoken + jjwt + 0.9.0 + + + org.apache.commons + commons-lang3 + + + org.projectlombok + lombok + provided + + + javax.servlet + javax.servlet-api + provided + + + + \ No newline at end of file diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/WebSecurityConfigJWT.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/WebSecurityConfigJWT.java new file mode 100644 index 0000000..c5bcb07 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/WebSecurityConfigJWT.java @@ -0,0 +1,194 @@ +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", "/v2/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 APP_NAME; + + @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) throws Exception { + 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 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() throws Exception { + return new JwtTokenAuthenticationFilter(jwtTokenHelper(), userDetailsService); + } + + @Bean + public JwtTokenHelper jwtTokenHelper() { + return new JwtTokenHelper(APP_NAME, 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(); + // } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/properties/InMemoryAccountProperties.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/properties/InMemoryAccountProperties.java new file mode 100644 index 0000000..279a079 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/properties/InMemoryAccountProperties.java @@ -0,0 +1,24 @@ +package it.fabioformosa.quartzmanager.security.configuration.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +@ConfigurationProperties(prefix = "quartz-manager.accounts.in-memory") +@Getter @Setter +public class InMemoryAccountProperties { + private boolean enabled; + private List users; + + @Getter @Setter + public static class User { + private String name; + private String password; + private List roles = new ArrayList<>(); + } +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/properties/JwtSecurityProperties.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/properties/JwtSecurityProperties.java new file mode 100644 index 0000000..1afec00 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/configuration/properties/JwtSecurityProperties.java @@ -0,0 +1,33 @@ +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; + } + +} \ No newline at end of file diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/LoginConfigurer.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/LoginConfigurer.java new file mode 100644 index 0000000..4446087 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/LoginConfigurer.java @@ -0,0 +1,21 @@ +package it.fabioformosa.quartzmanager.security.helpers; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +/** + * It configures filters to authenticate credentials sent by client or to set authenticationSuccessHandler + * + * Implement this interface for a login strategy + * + */ +public interface LoginConfigurer { + + /** + * If the authentication is based on cookie, it returns the name of cookie to be erased at the logout + */ + String cookieMustBeDeletedAtLogout(); + + HttpSecurity login(String loginPath, HttpSecurity http, AuthenticationManager authenticationManager) throws Exception; + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AjaxAuthenticationFilter.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AjaxAuthenticationFilter.java new file mode 100644 index 0000000..0c4e0b3 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AjaxAuthenticationFilter.java @@ -0,0 +1,55 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.WebAttributes; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +public class AjaxAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + public class AjaxLoginAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler + implements AuthenticationSuccessHandler { + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + response.setStatus(HttpServletResponse.SC_OK); + clearAuthenticationAttributes(request); + return; + } + + } + + public AjaxAuthenticationFilter(AuthenticationManager authenticationManager) { + setAuthenticationManager(authenticationManager); + setUsernameParameter("ajaxUsername"); + setPasswordParameter("ajaxPassword"); + setPostOnly(true); + setFilterProcessesUrl("/ajaxLogin"); + + setAuthenticationSuccessHandler(new AjaxLoginAuthSuccessHandler()); + } + + /** + * Removes temporary authentication-related data which may have been stored + * in the session during the authentication process. + */ + protected final void clearAuthenticationAttributes(HttpServletRequest request) { + HttpSession session = request.getSession(false); + + if (session == null) + return; + + session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); + } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AnonAuthentication.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AnonAuthentication.java new file mode 100644 index 0000000..f1c266f --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AnonAuthentication.java @@ -0,0 +1,45 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import org.springframework.security.authentication.AbstractAuthenticationToken; + +public class AnonAuthentication extends AbstractAuthenticationToken { + private static final long serialVersionUID = 1L; + + public AnonAuthentication() { + super( null ); + } + + @Override + public boolean equals( Object obj ) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + return true; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return null; + } + + @Override + public int hashCode() { + int hash = 7; + return hash; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AuthenticationFailureHandler.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AuthenticationFailureHandler.java new file mode 100644 index 0000000..08bb8b4 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AuthenticationFailureHandler.java @@ -0,0 +1,20 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; + +public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + + super.onAuthenticationFailure(request, response, exception); + } +} \ No newline at end of file diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AuthenticationSuccessHandler.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AuthenticationSuccessHandler.java new file mode 100644 index 0000000..6634870 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/AuthenticationSuccessHandler.java @@ -0,0 +1,35 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; + +//@Component +//@ConditionalOnProperty(prefix = "quartz-manager.security.login-model", name = "form-login-enabled", havingValue = "true", matchIfMissing = true) +public class AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private final JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler; + + // @Autowired + public AuthenticationSuccessHandler(JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler) { + super(); + this.jwtAuthenticationSuccessHandler = jwtAuthenticationSuccessHandler; + } + + public String cookieMustBeDeletedAtLogout() { + return jwtAuthenticationSuccessHandler.cookieMustBeDeletedAtLogout(); + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication ) throws IOException, ServletException { + clearAuthenticationAttributes(request); + jwtAuthenticationSuccessHandler.onLoginSuccess(authentication, response); + } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/ComboEntryPoint.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/ComboEntryPoint.java new file mode 100644 index 0000000..7ab933d --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/ComboEntryPoint.java @@ -0,0 +1,33 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +@Component +public class ComboEntryPoint extends LoginUrlAuthenticationEntryPoint { + + private static final String LOGIN_FORM_URL = "/login"; + + public ComboEntryPoint() { + super(LOGIN_FORM_URL); + } + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + + if (RESTRequestMatcher.isRestRequest(request) + || WebsocketRequestMatcher.isWebsocketConnectionRequest(request)) + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + else + super.commence(request, response, authException); + } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/FormLoginConfig.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/FormLoginConfig.java new file mode 100644 index 0000000..206f969 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/FormLoginConfig.java @@ -0,0 +1,75 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; + +import it.fabioformosa.quartzmanager.security.helpers.LoginConfigurer; + +/** + * It delegates form to @FormLoginConfigurer of the httpSecurity. + * + */ +public class FormLoginConfig implements LoginConfigurer { + + private static final Logger log = LoggerFactory.getLogger(FormLoginConfig.class); + + private final AuthenticationSuccessHandler authenticationSuccessHandler; + + private final AuthenticationFailureHandler authenticationFailureHandler; + + + public FormLoginConfig() { + super(); + authenticationSuccessHandler = null; + authenticationFailureHandler = null; + } + + public FormLoginConfig(AuthenticationFailureHandler authenticationFailureHandler) { + super(); + authenticationSuccessHandler = null; + this.authenticationFailureHandler = authenticationFailureHandler; + } + + public FormLoginConfig(AuthenticationSuccessHandler authenticationSuccessHandler) { + super(); + this.authenticationSuccessHandler = authenticationSuccessHandler; + authenticationFailureHandler = null; + } + + public FormLoginConfig(AuthenticationSuccessHandler authenticationSuccessHandler, + AuthenticationFailureHandler authenticationFailureHandler) { + super(); + this.authenticationSuccessHandler = authenticationSuccessHandler; + this.authenticationFailureHandler = authenticationFailureHandler; + } + + @Override + public String cookieMustBeDeletedAtLogout() { + return authenticationSuccessHandler.cookieMustBeDeletedAtLogout(); + } + + @Override + public HttpSecurity login(String loginPath, + HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { + log.debug("Configuring login through FormLoginConfigurer..."); + + FormLoginConfigurer login = http.formLogin().loginPage(loginPath); + + if(authenticationSuccessHandler != null) { + log.debug("Setting an authenticationSuccessHandler"); + login = login.successHandler(authenticationSuccessHandler); + } + + if(authenticationFailureHandler != null) { + log.debug("Setting an authenticationFailureHandler"); + login = login.failureHandler(authenticationFailureHandler); + } + + HttpSecurity httpSecurity = login.and(); + return httpSecurity; + } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationFilter.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationFilter.java new file mode 100644 index 0000000..c15c0ac --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationFilter.java @@ -0,0 +1,34 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import lombok.SneakyThrows; + +/** + * It extends the @UsernamePasswordAuthenticationFilter and it overrides the successfulAuthentication method to put jwtToken in the response + * + */ +public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + private JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler; + + public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler) { + this.jwtAuthenticationSuccessHandler = jwtAuthenticationSuccessHandler; + setAuthenticationManager(authenticationManager); + } + + @SneakyThrows + @Override + protected void successfulAuthentication(HttpServletRequest req, + HttpServletResponse res, + FilterChain chain, + Authentication auth) { + jwtAuthenticationSuccessHandler.onLoginSuccess(auth, res); + } +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationSuccessHandler.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationSuccessHandler.java new file mode 100644 index 0000000..6a956c9 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationSuccessHandler.java @@ -0,0 +1,14 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; + +public interface JwtAuthenticationSuccessHandler { + + String cookieMustBeDeletedAtLogout(); + + void onLoginSuccess(Authentication authentication, HttpServletResponse response) throws IOException; +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationSuccessHandlerImpl.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationSuccessHandlerImpl.java new file mode 100644 index 0000000..ca5de80 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtAuthenticationSuccessHandlerImpl.java @@ -0,0 +1,78 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import java.io.IOException; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.User; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import it.fabioformosa.quartzmanager.security.configuration.properties.JwtSecurityProperties; +import it.fabioformosa.quartzmanager.security.models.UserTokenState; + +/** + * It depends on @JwtTokenHelper to generate the jwtToken. + * On login success, it generates the jwtToken and it returns it to the login according to possible strategies: cookie, response header. + * You can choice the strategy through @JwtSecurityProperties + * + */ +public class JwtAuthenticationSuccessHandlerImpl implements JwtAuthenticationSuccessHandler { + + private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationSuccessHandlerImpl.class); + + private final JwtSecurityProperties jwtSecurityProps; + + private final JwtTokenHelper jwtTokenHelper; + + private final ObjectMapper objectMapper; + + private final String contextPath; + + @Autowired + public JwtAuthenticationSuccessHandlerImpl(String contextPath, JwtSecurityProperties jwtSecurityProps, JwtTokenHelper jwtTokenHelper, ObjectMapper objectMapper) { + this.contextPath = contextPath; + this.jwtSecurityProps = jwtSecurityProps; + this.jwtTokenHelper = jwtTokenHelper; + this.objectMapper = objectMapper; + } + + @Override + public String cookieMustBeDeletedAtLogout() { + if(!jwtSecurityProps.getCookieStrategy().isEnabled()) + return null; + return jwtSecurityProps.getCookieStrategy().getCookie(); + } + + @Override + public void onLoginSuccess(Authentication authentication, HttpServletResponse response) throws IOException { + log.debug("Login successed, generating jwtToken..."); + + User user = (User) authentication.getPrincipal(); + String jwtToken = jwtTokenHelper.generateToken(user.getUsername()); + + if(jwtSecurityProps.getCookieStrategy().isEnabled()) { + Cookie authCookie = new Cookie(jwtSecurityProps.getCookieStrategy().getCookie(), jwtToken); + authCookie.setHttpOnly(true); + authCookie.setMaxAge((int) jwtSecurityProps.getExpirationInSec()); + authCookie.setPath(contextPath); + response.addCookie(authCookie); + log.debug("Set jwtToken into the cookie {}", jwtSecurityProps.getCookieStrategy().getCookie()); + } + + if(jwtSecurityProps.getHeaderStrategy().isEnabled()) { + jwtTokenHelper.setHeader(response, jwtToken); + log.debug("Set jwtToken into the response header {}", jwtSecurityProps.getHeaderStrategy().getHeader()); + } + + UserTokenState userTokenState = new UserTokenState(jwtToken, jwtSecurityProps.getExpirationInSec()); + String jwtResponse = objectMapper.writeValueAsString(userTokenState); + response.setContentType("application/json"); + response.getWriter().write(jwtResponse); + } +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenAuthenticationFilter.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenAuthenticationFilter.java new file mode 100644 index 0000000..346bb2f --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenAuthenticationFilter.java @@ -0,0 +1,105 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.web.filter.OncePerRequestFilter; + + +/** + * It finds the jwtToken into the request, it validates it and sets an @Authentication into the @SecurityContextHolder. + * If the request has a path included into the paths that must be skipped, it sets an anonymous authentication + * + * It delegates the jwtToken retrieve to the @JwtTokenHelper that applies several strategies. + * + */ +public class JwtTokenAuthenticationFilter extends OncePerRequestFilter { + + private static final Logger log = LoggerFactory.getLogger(JwtTokenAuthenticationFilter.class); + + private static final String ROOT_MATCHER = "/"; + private static final String FAVICON_MATCHER = "/favicon.ico"; + private static final String HTML_MATCHER = "/**/*.html"; + private static final String CSS_MATCHER = "/**/*.css"; + private static final String JS_MATCHER = "/**/*.js"; + private static final String IMG_MATCHER = "/images/*"; + private static final String LOGIN_MATCHER = "/api/login"; + private static final String LOGOUT_MATCHER = "/api/logout"; + + private static List PATH_TO_SKIP = Arrays.asList( + ROOT_MATCHER, + HTML_MATCHER, + FAVICON_MATCHER, + CSS_MATCHER, + JS_MATCHER, + IMG_MATCHER, + LOGIN_MATCHER, + LOGOUT_MATCHER + ); + + private final JwtTokenHelper jwtTokenHelper; + private final UserDetailsService userDetailsService; + + + public JwtTokenAuthenticationFilter(JwtTokenHelper jwtTokenHelper, UserDetailsService userDetailsService) { + super(); + this.jwtTokenHelper = jwtTokenHelper; + this.userDetailsService = userDetailsService; + } + + @Override + public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + + String jwtToken = jwtTokenHelper.retrieveToken(request); + if (jwtToken != null) { + log.debug("Found a jwtToken into the request {}", request.getPathInfo()); + try { + String username = jwtTokenHelper.getUsernameFromToken(jwtToken); + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + JwtTokenBasedAuthentication authentication = new JwtTokenBasedAuthentication(userDetails); + authentication.setToken(jwtToken); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (Exception e) { + log.error("Authentication failed! an expected error occurred authenticating the request {}", request.getRequestURL()); + // SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication()); + // log.error("Switched to Anonymous Authentication, " + // + "because an error occurred setting authentication in security context holder due to " + e.getMessage(), e); + } + } + else if(skipPathRequest(request, PATH_TO_SKIP)) { + log.debug("Detected a path to be skipped from authentication, so activated anonymous auth for {}", request.getRequestURL()); + SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication()); + } + else + log.debug("Not found any jwtToken and the request hasn't a path to be skipped from auth. Path: {}", request.getRequestURL()); + + chain.doFilter(request, response); + } + + private boolean skipPathRequest(HttpServletRequest request, List pathsToSkip ) { + if(pathsToSkip == null) + pathsToSkip = new ArrayList(); + List matchers = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList()); + OrRequestMatcher compositeMatchers = new OrRequestMatcher(matchers); + return compositeMatchers.matches(request); + } + +} \ No newline at end of file diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenBasedAuthentication.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenBasedAuthentication.java new file mode 100644 index 0000000..904ed07 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenBasedAuthentication.java @@ -0,0 +1,42 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; + + +public class JwtTokenBasedAuthentication extends AbstractAuthenticationToken { + + private static final long serialVersionUID = 1L; + + private String token; + private final UserDetails principle; + + public JwtTokenBasedAuthentication(UserDetails principle) { + super(principle.getAuthorities()); + this.principle = principle; + } + + @Override + public Object getCredentials() { + return token; + } + + @Override + public UserDetails getPrincipal() { + return principle; + } + + public String getToken() { + return token; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + public void setToken( String token ) { + this.token = token; + } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenHelper.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenHelper.java new file mode 100644 index 0000000..679eb57 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtTokenHelper.java @@ -0,0 +1,163 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Base64; +import java.util.Date; +import java.util.Map; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import it.fabioformosa.quartzmanager.security.configuration.properties.JwtSecurityProperties; + +/** + * + * @author Fabio.Formosa + * + */ + +public class JwtTokenHelper { + + private static final Logger log = LoggerFactory.getLogger(JwtTokenHelper.class); + + private static String base64EncodeSecretKey(String secretKey) { + return Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + // @Value("${app.name}") + private final String appName; + + // @Autowired + private final JwtSecurityProperties jwtSecurityProps; + + private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512; + + // @Autowired + public JwtTokenHelper(String appName, JwtSecurityProperties jwtSecurityProps) { + super(); + this.appName = appName; + this.jwtSecurityProps = jwtSecurityProps; + } + + public Boolean canTokenBeRefreshed(String token) { + try { + final Date expirationDate = getClaimsFromToken(token).getExpiration(); + // String username = getUsernameFromToken(token); + // UserDetails userDetails = userDetailsService.loadUserByUsername(username); + return expirationDate.compareTo(generateCurrentDate()) > 0; + } catch (Exception e) { + return false; + } + } + + private Date generateCurrentDate() { + return new Date(getCurrentTimeMillis()); + } + + private Date generateExpirationDate() { + return new Date(getCurrentTimeMillis() + jwtSecurityProps.getExpirationInSec() * 1000); + } + + private String generateToken(Map claims) { + return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()) + .signWith(SIGNATURE_ALGORITHM, base64EncodeSecretKey(jwtSecurityProps.getSecret())).compact(); + } + + public String generateToken(String username) { + return Jwts.builder().setIssuer(appName).setSubject(username).setIssuedAt(generateCurrentDate()) + .setExpiration(generateExpirationDate()) + .signWith(SIGNATURE_ALGORITHM, base64EncodeSecretKey(jwtSecurityProps.getSecret())).compact(); + } + + private Claims getClaimsFromToken(String token) { + Claims claims; + try { + claims = Jwts.parser().setSigningKey(base64EncodeSecretKey(jwtSecurityProps.getSecret())) + .parseClaimsJws(token).getBody(); + } catch (Exception e) { + claims = null; + log.error("Error getting claims from jwt token due to " + e.getMessage(), e); + } + return claims; + } + + /** + * Find a specific HTTP cookie in a request. + * + * @param request + * The HTTP request object. + * @param name + * The cookie name to look for. + * @return The cookie, or null if not found. + */ + public Cookie getCookieValueByName(HttpServletRequest request, String name) { + if (request.getCookies() == null) + return null; + for (int i = 0; i < request.getCookies().length; i++) + if (request.getCookies()[i].getName().equals(name)) + return request.getCookies()[i]; + return null; + } + + private long getCurrentTimeMillis() { + return LocalDateTime.now().atZone(ZoneId.of("Europe/Rome")).toInstant().toEpochMilli(); + } + + public String getUsernameFromToken(String token) { + String username; + try { + final Claims claims = getClaimsFromToken(token); + username = claims.getSubject(); + } catch (Exception e) { + username = null; + log.error("Error getting claims from jwt token due to " + e.getMessage(), e); + throw e; + } + return username; + } + + public String refreshToken(String token) { + String refreshedToken; + try { + final Claims claims = getClaimsFromToken(token); + claims.setIssuedAt(generateCurrentDate()); + refreshedToken = generateToken(claims); + } catch (Exception e) { + log.error("Error refreshing jwt token due to " + e.getMessage(), e); + refreshedToken = null; + } + return refreshedToken; + } + + public String retrieveToken(HttpServletRequest request) { + if (jwtSecurityProps.getCookieStrategy().isEnabled() == true) { + Cookie authCookie = getCookieValueByName(request, jwtSecurityProps.getCookieStrategy().getCookie()); + if (authCookie != null) + return authCookie.getValue(); + } + + if (jwtSecurityProps.getHeaderStrategy().isEnabled()) { + String authHeader = request.getHeader(jwtSecurityProps.getHeaderStrategy().getHeader()); + if (authHeader != null && authHeader.startsWith("Bearer ")) + return authHeader.substring(7); + } + + if(request.getParameter("access_token") != null) + return request.getParameter("access_token"); + + return null; + } + + public void setHeader(HttpServletResponse response, String token) { + response.addHeader(jwtSecurityProps.getHeaderStrategy().getHeader(), "Bearer " + token); + } +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtUsernamePasswordFiterLoginConfig.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtUsernamePasswordFiterLoginConfig.java new file mode 100644 index 0000000..6f6991c --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/JwtUsernamePasswordFiterLoginConfig.java @@ -0,0 +1,46 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; +import org.springframework.security.web.util.matcher.RegexRequestMatcher; +import org.springframework.web.filter.GenericFilterBean; + +import it.fabioformosa.quartzmanager.security.helpers.LoginConfigurer; + +/** + * It adds a new filter @JwtAuthenticationFilter after @AbstractPreAuthenticatedProcessingFilter that match login path + * + */ +public class JwtUsernamePasswordFiterLoginConfig implements LoginConfigurer { + + private static final Logger log = LoggerFactory.getLogger(JwtUsernamePasswordFiterLoginConfig.class); + + private final JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler; + + public JwtUsernamePasswordFiterLoginConfig(JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler) { + super(); + this.jwtAuthenticationSuccessHandler = jwtAuthenticationSuccessHandler; + } + + public GenericFilterBean authenticationProcessingFilter(String loginPath, AuthenticationManager authenticationManager) throws Exception { + JwtAuthenticationFilter authenticationProcessingFilter = new JwtAuthenticationFilter(authenticationManager, jwtAuthenticationSuccessHandler); + authenticationProcessingFilter.setRequiresAuthenticationRequestMatcher(new RegexRequestMatcher(loginPath, HttpMethod.POST.name(), false)); + return authenticationProcessingFilter; + } + + @Override + public String cookieMustBeDeletedAtLogout() { + return jwtAuthenticationSuccessHandler.cookieMustBeDeletedAtLogout(); + } + + @Override + public HttpSecurity login(String loginPath, HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { + log.debug("Configuring login through JwtAuthenticationFilter..."); + return http.addFilterAfter(authenticationProcessingFilter(loginPath, authenticationManager), AbstractPreAuthenticatedProcessingFilter.class); + } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/LogoutSuccess.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/LogoutSuccess.java new file mode 100644 index 0000000..5f0eaa0 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/LogoutSuccess.java @@ -0,0 +1,36 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class LogoutSuccess implements LogoutSuccessHandler { + + private final ObjectMapper objectMapper; + + public LogoutSuccess(ObjectMapper objectMapper) { + super(); + this.objectMapper = objectMapper; + } + + @Override + public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + Map result = new HashMap<>(); + result.put( "result", "success" ); + response.setContentType("application/json"); + response.getWriter().write(objectMapper.writeValueAsString(result)); + response.setStatus(HttpServletResponse.SC_OK); + + } + +} \ No newline at end of file diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/QuartzManagerHttpSecurity.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/QuartzManagerHttpSecurity.java new file mode 100644 index 0000000..55b9edb --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/QuartzManagerHttpSecurity.java @@ -0,0 +1,57 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import it.fabioformosa.quartzmanager.security.helpers.LoginConfigurer; + +/** + * It wraps the httpSecurity to provide new function as login and logout + * + */ +public class QuartzManagerHttpSecurity extends SecurityConfigurerAdapter { + + public static QuartzManagerHttpSecurity from(HttpSecurity httpSecurity){ + QuartzManagerHttpSecurity newInstance = new QuartzManagerHttpSecurity(httpSecurity); + newInstance.setBuilder(httpSecurity); + return newInstance; + } + + private HttpSecurity httpSecurity; + + private LoginConfigurer loginConfigurer; + + private LogoutSuccess logoutSuccess; + + public QuartzManagerHttpSecurity(HttpSecurity httpSecurity) { + this.httpSecurity = httpSecurity; + // applicationContext = httpSecurity.getSharedObject(ApplicationContext.class); + } + + public QuartzManagerHttpSecurity login(String loginPath, AuthenticationManager authenticationManager) throws Exception { + if(loginConfigurer == null || logoutSuccess == null) + throw new IllegalStateException("QuartzManagerHttpSecurity requires to be set loginConfigurer and logoutSuccess!"); + httpSecurity = loginConfigurer.login(loginPath, httpSecurity, authenticationManager); + return this; + } + + + public LogoutConfigurer logout(String logoutPath) throws Exception { + LogoutConfigurer logoutConfigurer = httpSecurity.logout().logoutRequestMatcher(new AntPathRequestMatcher(logoutPath)) + .logoutSuccessHandler(logoutSuccess); + String cookie = loginConfigurer.cookieMustBeDeletedAtLogout(); + if(cookie != null) + logoutConfigurer.deleteCookies(cookie); + return logoutConfigurer; + } + + public QuartzManagerHttpSecurity withLoginConfigurer(LoginConfigurer loginConfigurer, LogoutSuccess logoutSuccess) { + this.loginConfigurer = loginConfigurer; + this.logoutSuccess = logoutSuccess; + return this; + } +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/RESTRequestMatcher.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/RESTRequestMatcher.java new file mode 100644 index 0000000..1fb2d2b --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/RESTRequestMatcher.java @@ -0,0 +1,26 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.web.util.matcher.ELRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +public class RESTRequestMatcher { + + static private final Logger log = LoggerFactory.getLogger(RESTRequestMatcher.class); + + static public RequestMatcher matcherRequestedWith = new ELRequestMatcher( + "hasHeader('X-Requested-With','XMLHttpRequest')"); + static public RequestMatcher matcherAccept = new ELRequestMatcher( + "hasHeader('accept','application/json, text/plain, */*')"); + + static public boolean isRestRequest(HttpServletRequest request) { + log.trace("Detecting if it's an AJAX Request: " + request.getRequestURL() + " accept: " + + request.getHeader("accept") + " " + " X-Requested-With: " + + request.getHeader("X-Requested-With")); + return matcherRequestedWith.matches(request) || matcherAccept.matches(request); + } + +} \ No newline at end of file diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/RestAuthenticationEntryPoint.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/RestAuthenticationEntryPoint.java new file mode 100644 index 0000000..3615a2e --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/RestAuthenticationEntryPoint.java @@ -0,0 +1,23 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +@Component +public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) throws IOException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); + } +} + diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/WebsocketRequestMatcher.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/WebsocketRequestMatcher.java new file mode 100644 index 0000000..5b42424 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/helpers/impl/WebsocketRequestMatcher.java @@ -0,0 +1,18 @@ +package it.fabioformosa.quartzmanager.security.helpers.impl; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WebsocketRequestMatcher { + + static private final Logger log = LoggerFactory.getLogger(WebsocketRequestMatcher.class); + + static public boolean isWebsocketConnectionRequest(HttpServletRequest request) { + log.trace("Detecting if it's a Websocket Connection Request: " + request.getRequestURL()); + return request.getServletPath().equals("/progress/info") + || request.getServletPath().equals("/logs/info"); + } + +} \ No newline at end of file diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/Authority.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/Authority.java new file mode 100644 index 0000000..32a6284 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/Authority.java @@ -0,0 +1,53 @@ +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 { + + @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; + } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/User.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/User.java new file mode 100644 index 0000000..ba0d58a --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/User.java @@ -0,0 +1,133 @@ +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 { + @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 authorities; + + @Override + public Collection 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 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; + } +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/UserRequest.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/UserRequest.java new file mode 100644 index 0000000..469df73 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/UserRequest.java @@ -0,0 +1,56 @@ +package it.fabioformosa.quartzmanager.security.models; + + +public class UserRequest { + + private Long id; + + private String username; + + private String password; + + private String firstname; + + private String lastname; + + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/UserTokenState.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/UserTokenState.java new file mode 100644 index 0000000..e39cd6b --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/models/UserTokenState.java @@ -0,0 +1,32 @@ +package it.fabioformosa.quartzmanager.security.models; + +public class UserTokenState { + private String access_token; + private Long expires_in_sec; + + public UserTokenState() { + this.access_token = null; + this.expires_in_sec = null; + } + + public UserTokenState(String access_token, long expires_in_sec) { + this.access_token = access_token; + this.expires_in_sec = expires_in_sec; + } + + public String getAccess_token() { + return access_token; + } + + public Long getExpires_in_sec() { + return expires_in_sec; + } + + public void setAccess_token(String access_token) { + this.access_token = access_token; + } + + public void setExpires_in_sec(Long expires_in_sec) { + this.expires_in_sec = expires_in_sec; + } +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/repositories/AuthorityRepository.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/repositories/AuthorityRepository.java new file mode 100644 index 0000000..5d736c1 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/repositories/AuthorityRepository.java @@ -0,0 +1,13 @@ +package it.fabioformosa.quartzmanager.security.repositories; + +/** + * Temporary disabled + * + * @author Fabio + * + */ +//public interface AuthorityRepository extends JpaRepository { +// Authority findByName(String name); +//} +public interface AuthorityRepository { +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/repositories/UserRepository.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/repositories/UserRepository.java new file mode 100644 index 0000000..6ae1804 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/repositories/UserRepository.java @@ -0,0 +1,11 @@ +package it.fabioformosa.quartzmanager.security.repositories; + +import it.fabioformosa.quartzmanager.security.models.User; + +public interface UserRepository { + User findByUsername( String username ); +} +//public interface UserRepository extends JpaRepository { +// User findByUsername( String username ); +//} + diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/AuthorityService.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/AuthorityService.java new file mode 100644 index 0000000..5eca666 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/AuthorityService.java @@ -0,0 +1,13 @@ +package it.fabioformosa.quartzmanager.security.services; + +/** + * temporary disabled + * @author Fabio + * + */ +public interface AuthorityService { + // List findById(Long id); + // + // List findByname(String name); + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/UserService.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/UserService.java new file mode 100644 index 0000000..5b66879 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/UserService.java @@ -0,0 +1,18 @@ +package it.fabioformosa.quartzmanager.security.services; + +import java.util.List; + +import it.fabioformosa.quartzmanager.security.models.User; +import it.fabioformosa.quartzmanager.security.models.UserRequest; + +public interface UserService { + List findAll(); + + User findById(Long id); + + User findByUsername(String username); + + void resetCredentials(); + + User save(UserRequest user); +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/AuthorityServiceImpl.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/AuthorityServiceImpl.java new file mode 100644 index 0000000..78ebaf4 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/AuthorityServiceImpl.java @@ -0,0 +1,33 @@ +package it.fabioformosa.quartzmanager.security.services.impl; + +import it.fabioformosa.quartzmanager.security.services.AuthorityService; + +/** + * Temporary disabled + * @author Fabio + * + */ + +//@Service +public class AuthorityServiceImpl implements AuthorityService { + + // @Autowired + // private AuthorityRepository authorityRepository; + // + // @Override + // public List findById(Long id) { + // Authority auth = this.authorityRepository.getOne(id); + // List auths = new ArrayList<>(); + // auths.add(auth); + // return auths; + // } + // + // @Override + // public List findByname(String name) { + // Authority auth = this.authorityRepository.findByName(name); + // List auths = new ArrayList<>(); + // auths.add(auth); + // return auths; + // } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/CustomUserDetailsService.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/CustomUserDetailsService.java new file mode 100644 index 0000000..f4b7adc --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/CustomUserDetailsService.java @@ -0,0 +1,67 @@ +package it.fabioformosa.quartzmanager.security.services.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; + +import it.fabioformosa.quartzmanager.security.models.User; +import it.fabioformosa.quartzmanager.security.repositories.UserRepository; + +/** + * Temporary disabled + * @author Fabio + * + */ +//@Service +public class CustomUserDetailsService implements UserDetailsService { + + protected final Log LOGGER = LogFactory.getLog(getClass()); + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private AuthenticationManager authenticationManager; + + public void changePassword(String oldPassword, String newPassword) { + + // Authentication currentUser = SecurityContextHolder.getContext().getAuthentication(); + // String username = currentUser.getName(); + // + // if (authenticationManager != null) { + // LOGGER.debug("Re-authenticating user '"+ username + "' for password change request."); + // + // authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, oldPassword)); + // } else { + // LOGGER.debug("No authentication manager set. can't change Password!"); + // + // return; + // } + // + // LOGGER.debug("Changing password for user '"+ username + "'"); + // + // User user = (User) loadUserByUsername(username); + // + // user.setPassword(passwordEncoder.encode(newPassword)); + // userRepository.save(user); + + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username); + if (user == null) + throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username)); + else + return user; + } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/UserServiceImpl.java b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/UserServiceImpl.java new file mode 100644 index 0000000..9831e6e --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/java/it/fabioformosa/quartzmanager/security/services/impl/UserServiceImpl.java @@ -0,0 +1,79 @@ +package it.fabioformosa.quartzmanager.security.services.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; + +import it.fabioformosa.quartzmanager.security.models.User; +import it.fabioformosa.quartzmanager.security.models.UserRequest; +import it.fabioformosa.quartzmanager.security.repositories.UserRepository; +import it.fabioformosa.quartzmanager.security.services.AuthorityService; +import it.fabioformosa.quartzmanager.security.services.UserService; + +/** + * Temporary disabled + * @author Fabio + * + */ +//@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private AuthorityService authService; + + @Override + @PreAuthorize("hasRole('ADMIN')") + public List findAll() throws AccessDeniedException { + // List result = userRepository.findAll(); + // return result; + return null; + } + + @Override + @PreAuthorize("hasRole('ADMIN')") + public User findById(Long id) throws AccessDeniedException { + // User u = userRepository.getOne(id); + // return u; + return null; + } + + @Override + // @PreAuthorize("hasRole('USER')") + public User findByUsername(String username) throws UsernameNotFoundException { + User u = userRepository.findByUsername(username); + return u; + } + + @Override + public void resetCredentials() { + // List users = userRepository.findAll(); + // for (User user : users) { + // user.setPassword(passwordEncoder.encode("123")); + // userRepository.save(user); + // } + } + + @Override + public User save(UserRequest userRequest) { + User user = new User(); + // user.setUsername(userRequest.getUsername()); + // user.setPassword(passwordEncoder.encode(userRequest.getPassword())); + // user.setFirstname(userRequest.getFirstname()); + // user.setLastname(userRequest.getLastname()); + // List auth = authService.findByname("ROLE_USER"); + // user.setAuthorities(auth); + // this.userRepository.save(user); + return user; + } + +} diff --git a/quartz-manager-parent/quartz-manager-security/src/main/resources/META-INF/spring.factories b/quartz-manager-parent/quartz-manager-security/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..9e6be3c --- /dev/null +++ b/quartz-manager-parent/quartz-manager-security/src/main/resources/META-INF/spring.factories @@ -0,0 +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 \ No newline at end of file diff --git a/quartz-manager-parent/quartz-manager-ui-webjar/pom.xml b/quartz-manager-parent/quartz-manager-ui-webjar/pom.xml index fb3f625..68aeee1 100644 --- a/quartz-manager-parent/quartz-manager-ui-webjar/pom.xml +++ b/quartz-manager-parent/quartz-manager-ui-webjar/pom.xml @@ -26,110 +26,114 @@ - - - - - - - org.apache.maven.plugins - maven-resources-plugin - 2.6 - - - copy-resources - generate-resources - - copy-resources - - - ${basedir}/target/tmp - - - ../../${frontend.folderName} - - static/** - dist/** - node_modules/** - - - - - - - - - - - com.github.eirslett - frontend-maven-plugin - 1.11.0 - - target/tmp - - + + + build-webjar + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + + copy-resources + generate-resources + + copy-resources + + + ${basedir}/target/tmp + + + ../../${frontend.folderName} + + static/** + dist/** + node_modules/** + + + + + + + - - install node and npm - - install-node-and-npm - - generate-resources + + + com.github.eirslett + frontend-maven-plugin + 1.11.0 - ${node.version} - ${npm.version} + target/tmp - + + + + install node and npm + + install-node-and-npm + + generate-resources + + ${node.version} + ${npm.version} + + + + + npm install + + npm + + process-resources + + install + + + + + npm run build + + npm + + process-resources + + run build + + + + + - - npm install - - npm - - process-resources - - install - - - - - npm run build - - npm - - process-resources - - run build - - - - - - - - - maven-antrun-plugin - 1.8 - - - clean build files - process-resources - - - - - - - - - - - run - - - - - - + + + maven-antrun-plugin + 1.8 + + + clean build files + process-resources + + + + + + + + + + + run + + + + + + + + diff --git a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml index 0a83c7e..1c353c1 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml +++ b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml @@ -29,10 +29,10 @@ quartz-manager-api - - it.fabioformosa.quartz-manager - quartz-manager-ui-webjar - + + + +