From b665b37d2cf2bcc1884d491a6bc453310cbd1587 Mon Sep 17 00:00:00 2001 From: Sarmokadam Date: Thu, 17 Jan 2019 21:14:20 +0100 Subject: [PATCH] addition of module for jwt and kerberos --- modules/pom.xml | 2 + modules/security-jwt/pom.xml | 102 ++++++++++ .../security/common/AccountCredentials.java | 28 +++ .../common/JWTAuthenticationFilter.java | 31 +++ .../security/common/JWTLoginFilter.java | 52 +++++ .../security/common/JwtWebSecurityConfig.java | 85 ++++++++ .../common/TokenAuthenticationService.java | 174 ++++++++++++++++ .../security/common/api/UserProfile.java | 40 ++++ .../security/common/api/datatype/Role.java | 21 ++ .../common/api/to/UserDetailsClientTo.java | 105 ++++++++++ .../app/security/access-control-schema.xml | 67 +++++++ .../access-control-schema_corrupted.xml | 21 ++ .../security/access-control-schema_cyclic.xml | 28 +++ .../access-control-schema_groupTypes.xml | 24 +++ .../access-control-schema_illegal.xml | 8 + modules/security-kerberos/pom.xml | 166 ++++++++++++++++ .../common/DummyUserDetailsService.java | 39 ++++ .../common/KerberosConfigProperties.java | 50 +++++ .../common/KerberosWebSecurityConfig.java | 186 ++++++++++++++++++ .../common/ServiceSubjectFactory.java | 111 +++++++++++ .../module/security/access-control-schema.xsd | 53 +++++ .../app/security/access-control-schema.xml | 67 +++++++ .../access-control-schema_corrupted.xml | 20 ++ .../security/access-control-schema_cyclic.xml | 28 +++ .../access-control-schema_groupTypes.xml | 24 +++ .../access-control-schema_illegal.xml | 8 + 26 files changed, 1540 insertions(+) create mode 100644 modules/security-jwt/pom.xml create mode 100644 modules/security-jwt/src/main/java/com/devonfw/module/security/common/AccountCredentials.java create mode 100644 modules/security-jwt/src/main/java/com/devonfw/module/security/common/JWTAuthenticationFilter.java create mode 100644 modules/security-jwt/src/main/java/com/devonfw/module/security/common/JWTLoginFilter.java create mode 100644 modules/security-jwt/src/main/java/com/devonfw/module/security/common/JwtWebSecurityConfig.java create mode 100644 modules/security-jwt/src/main/java/com/devonfw/module/security/common/TokenAuthenticationService.java create mode 100644 modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/UserProfile.java create mode 100644 modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/datatype/Role.java create mode 100644 modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/to/UserDetailsClientTo.java create mode 100644 modules/security-jwt/src/test/resources/config/app/security/access-control-schema.xml create mode 100644 modules/security-jwt/src/test/resources/config/app/security/access-control-schema_corrupted.xml create mode 100644 modules/security-jwt/src/test/resources/config/app/security/access-control-schema_cyclic.xml create mode 100644 modules/security-jwt/src/test/resources/config/app/security/access-control-schema_groupTypes.xml create mode 100644 modules/security-jwt/src/test/resources/config/app/security/access-control-schema_illegal.xml create mode 100644 modules/security-kerberos/pom.xml create mode 100644 modules/security-kerberos/src/main/java/com/devonfw/module/security/common/DummyUserDetailsService.java create mode 100644 modules/security-kerberos/src/main/java/com/devonfw/module/security/common/KerberosConfigProperties.java create mode 100644 modules/security-kerberos/src/main/java/com/devonfw/module/security/common/KerberosWebSecurityConfig.java create mode 100644 modules/security-kerberos/src/main/java/com/devonfw/module/security/common/ServiceSubjectFactory.java create mode 100644 modules/security-kerberos/src/main/resources/com/devonfw/module/security/access-control-schema.xsd create mode 100644 modules/security-kerberos/src/test/resources/config/app/security/access-control-schema.xml create mode 100644 modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_corrupted.xml create mode 100644 modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_cyclic.xml create mode 100644 modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_groupTypes.xml create mode 100644 modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_illegal.xml diff --git a/modules/pom.xml b/modules/pom.xml index 6da4c965..99bc72da 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -27,6 +27,8 @@ cxf-server-rest cxf-server-ws security + security-jwt + security-kerberos jpa-basic jpa-dao jpa-envers diff --git a/modules/security-jwt/pom.xml b/modules/security-jwt/pom.xml new file mode 100644 index 00000000..a707a84b --- /dev/null +++ b/modules/security-jwt/pom.xml @@ -0,0 +1,102 @@ + + + 4.0.0 + + com.devonfw.java.dev + devon4j-modules + dev-SNAPSHOT + + com.devonfw.java.modules + security-jwt + ${devon4j.version} + jar + ${project.artifactId} + Security Module of the Open Application Standard Platform for Java (devon4j) specifically for jwt. + + + + + io.jsonwebtoken + jjwt + 0.7.0 + + + + org.springframework + spring-web + + + javax.servlet + javax.servlet-api + provided + + + + + org.springframework.security + spring-security-config + + + org.springframework.security + spring-security-web + + + + + javax.inject + javax.inject + + + javax.annotation + javax.annotation-api + + + + javax.xml.bind + jaxb-api + true + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.devonfw.java.modules + devon4j-test + test + + + ${project.groupId} + devon4j-logging + test + + + com.google.guava + guava + test + + + + + java9 + + [9,12] + + + + org.glassfish.jaxb + jaxb-runtime + + + javax.activation + javax.activation-api + + + + + \ No newline at end of file diff --git a/modules/security-jwt/src/main/java/com/devonfw/module/security/common/AccountCredentials.java b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/AccountCredentials.java new file mode 100644 index 00000000..faa2624f --- /dev/null +++ b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/AccountCredentials.java @@ -0,0 +1,28 @@ +package com.devonfw.module.security.common; + +public class AccountCredentials { + + private String username; + + private String password; + + public String getUsername() { + + return this.username; + } + + public void setUsername(String username) { + + this.username = username; + } + + public String getPassword() { + + return this.password; + } + + public void setPassword(String password) { + + this.password = password; + } +} diff --git a/modules/security-jwt/src/main/java/com/devonfw/module/security/common/JWTAuthenticationFilter.java b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/JWTAuthenticationFilter.java new file mode 100644 index 00000000..55032aea --- /dev/null +++ b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/JWTAuthenticationFilter.java @@ -0,0 +1,31 @@ +package com.devonfw.module.security.common; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.GenericFilterBean; + +/** + * Filter for Json Web Tokens Authentication + * + */ +public class JWTAuthenticationFilter extends GenericFilterBean { + + @SuppressWarnings("javadoc") + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + + Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest) request); + + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + } +} diff --git a/modules/security-jwt/src/main/java/com/devonfw/module/security/common/JWTLoginFilter.java b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/JWTLoginFilter.java new file mode 100644 index 00000000..2ae54197 --- /dev/null +++ b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/JWTLoginFilter.java @@ -0,0 +1,52 @@ +package com.devonfw.module.security.common; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Login Filter for Json Web Token + * + */ +public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter { + + /** + * The constructor. + * + * @param url the login url + * @param authManager the {@link AuthenticationManager} + */ + public JWTLoginFilter(String url, AuthenticationManager authManager) { + + super(new AntPathRequestMatcher(url)); + setAuthenticationManager(authManager); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) + throws AuthenticationException, IOException, ServletException { + + AccountCredentials creds = new ObjectMapper().readValue(req.getInputStream(), AccountCredentials.class); + return getAuthenticationManager() + .authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(), creds.getPassword())); + } + + @Override + protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, + Authentication auth) throws IOException, ServletException { + + TokenAuthenticationService.addAuthentication(res, auth); + } +} diff --git a/modules/security-jwt/src/main/java/com/devonfw/module/security/common/JwtWebSecurityConfig.java b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/JwtWebSecurityConfig.java new file mode 100644 index 00000000..4d834397 --- /dev/null +++ b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/JwtWebSecurityConfig.java @@ -0,0 +1,85 @@ +package com.devonfw.module.security.common; + +import javax.inject.Inject; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +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.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.csrf.CsrfFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * @author snsarmok + * + */ +public abstract class JwtWebSecurityConfig extends WebSecurityConfigurerAdapter { + @Value("${security.cors.enabled}") + boolean corsEnabled = true; + + @Inject + private UserDetailsService userDetailsService; + + @Inject + private PasswordEncoder passwordEncoder; + + private CorsFilter getCorsFilter() { + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("OPTIONS"); + config.addAllowedMethod("HEAD"); + config.addAllowedMethod("GET"); + config.addAllowedMethod("PUT"); + config.addAllowedMethod("POST"); + config.addAllowedMethod("DELETE"); + config.addAllowedMethod("PATCH"); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + /** + * Configure spring security to enable login with JWT. + */ + @Override + public void configure(HttpSecurity http) throws Exception { + + String[] unsecuredResources = new String[] { "/login", "/security/**", "/services/rest/login", + "/services/rest/logout" }; + + http.userDetailsService(this.userDetailsService).csrf().disable().exceptionHandling().and().sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests() + .antMatchers(unsecuredResources).permitAll().antMatchers(HttpMethod.POST, "/login").permitAll().anyRequest() + .authenticated().and() + // the api/login requests are filtered with the JWTLoginFilter + .addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), + UsernamePasswordAuthenticationFilter.class) + // other requests are filtered to check the presence of JWT in header + .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); + + if (this.corsEnabled) { + http.addFilterBefore(getCorsFilter(), CsrfFilter.class); + } + } + + @Override + @SuppressWarnings("javadoc") + public void configure(AuthenticationManagerBuilder auth) throws Exception { + + auth.inMemoryAuthentication().withUser("waiter").password(this.passwordEncoder.encode("waiter")).roles("Waiter") + .and().withUser("cook").password(this.passwordEncoder.encode("cook")).roles("Cook").and().withUser("barkeeper") + .password(this.passwordEncoder.encode("barkeeper")).roles("Barkeeper").and().withUser("chief") + .password(this.passwordEncoder.encode("chief")).roles("Chief"); + } + +} diff --git a/modules/security-jwt/src/main/java/com/devonfw/module/security/common/TokenAuthenticationService.java b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/TokenAuthenticationService.java new file mode 100644 index 00000000..bc871543 --- /dev/null +++ b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/TokenAuthenticationService.java @@ -0,0 +1,174 @@ +package com.devonfw.module.security.common; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import com.devonfw.module.security.common.api.datatype.Role; +import com.devonfw.module.security.common.api.to.UserDetailsClientTo; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +/** + * Service class for JWT token managing + * + */ +public class TokenAuthenticationService { + + /** Logger instance. */ + private static final Logger LOG = LoggerFactory.getLogger(TokenAuthenticationService.class); + + static final String ISSUER = "MyThaiStarApp"; + + static final Integer EXPIRATION_HOURS = 1; + + static final String SECRET = "ThisIsASecret"; + + static final String TOKEN_PREFIX = "Bearer"; + + static final String HEADER_STRING = "Authorization"; + + static final String EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + + static final String CLAIM_SUBJECT = "sub"; + + static final String CLAIM_ISSUER = "iss"; + + static final String CLAIM_EXPIRATION = "exp"; + + static final String CLAIM_CREATED = "iat"; + + static final String CLAIM_SCOPE = "scope"; + + static final String CLAIM_ROLES = "roles"; + + /** + * This method returns the token once the Authentication has been successful + * + * @param res the {@HttpServletResponse} + * @param auth the {@Authentication} object with the user credentials + */ + static void addAuthentication(HttpServletResponse res, Authentication auth) { + + String token = generateToken(auth); + res.addHeader(EXPOSE_HEADERS, HEADER_STRING); + res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + token); + } + + /** + * This method validates the token and returns a {@link UsernamePasswordAuthenticationToken} + * + * @param request the {@link HttpServletRequest} + * @return the {@link UsernamePasswordAuthenticationToken} + */ + static Authentication getAuthentication(HttpServletRequest request) { + + String token = request.getHeader(HEADER_STRING); + if (token != null) { + + // The JWT parser will throw an exception if the token is not well formed or the token has expired + String user = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody() + .getSubject(); + return user != null ? new UsernamePasswordAuthenticationToken(user, null, getAuthorities(token)) : null; + + } + + return null; + } + + static Collection getAuthorities(String token) { + + List roles = getRolesFromToken(token); + List authorities = new ArrayList<>(); + for (String role : roles) { + authorities.add(new SimpleGrantedAuthority(role)); + } + return authorities; + + } + + static String generateToken(Authentication auth) { + + List scopes = new ArrayList<>(); + Collection authorities = auth.getAuthorities(); + for (GrantedAuthority authority : authorities) { + scopes.add(authority.getAuthority()); + } + + Map claims = new HashMap<>(); + claims.put(CLAIM_ISSUER, ISSUER); + claims.put(CLAIM_SUBJECT, auth.getName()); + claims.put(CLAIM_SCOPE, scopes); + claims.put(CLAIM_ROLES, scopes); + claims.put(CLAIM_CREATED, generateCreationDate() / 1000); + claims.put(CLAIM_EXPIRATION, generateExpirationDate() / 1000); + LOG.info(claims.toString()); + return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, SECRET).compact(); + } + + static Long generateCreationDate() { + + return new Date().getTime(); + } + + static Long generateExpirationDate() { + + int expirationTerm = (60 * 60 * 1000) * EXPIRATION_HOURS; + return new Date(new Date().getTime() + expirationTerm).getTime(); + } + + /** + * Extracts and returns the {@link UserDetailsClientTo} from the JWT token + * + * @param token the JWT token + * @return the {@link UserDetailsClientTo} object + */ + public static UserDetailsClientTo getUserdetailsFromToken(String token) { + + UserDetailsClientTo userDetails = new UserDetailsClientTo(); + try { + String user = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody() + .getSubject(); + + List roles = getRolesFromToken(token); + if (user != null) { + userDetails.setName(user); + } + if (!roles.isEmpty()) { + + for (Role c : Role.values()) { + if (c.name().equals(roles.get(0))) { + userDetails.setRole(c); + } + } + + } + } catch (Exception e) { + LOG.error(e.getMessage()); + userDetails = null; + } + + return userDetails; + } + + static List getRolesFromToken(String token) { + + return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody() + .get(CLAIM_SCOPE, List.class); + } + +} diff --git a/modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/UserProfile.java b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/UserProfile.java new file mode 100644 index 00000000..3f7676b1 --- /dev/null +++ b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/UserProfile.java @@ -0,0 +1,40 @@ +package com.devonfw.module.security.common.api; + +import java.security.Principal; + +import com.devonfw.module.security.common.api.datatype.Role; + +/** + * This is the interface for the profile of a user interacting with this application. Currently this can only be a + * {@link io.oasp.application.mtsj.staffmanagement.dataaccess.api.StaffMemberEntity} however in the future a customer + * may login and make a booking, etc.
+ * TODO: Also an external system may access the application via some service. Then there would be no user profile or it + * would be empty... + * + */ +public interface UserProfile extends Principal { + /** + * @return the technical ID of the user for calling REST services. + */ + Long getId(); + + /** + * @return the unique login of the user for authentication and identification. + */ + String getName(); + + /** + * @return the first name of the users real name. + */ + String getFirstName(); + + /** + * @return the last name of the users real name. + */ + String getLastName(); + + /** + * @return {@link Role} of this {@link UserProfile}. + */ + Role getRole(); +} diff --git a/modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/datatype/Role.java b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/datatype/Role.java new file mode 100644 index 00000000..8b0ee81a --- /dev/null +++ b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/datatype/Role.java @@ -0,0 +1,21 @@ +package com.devonfw.module.security.common.api.datatype; + +import java.security.Principal; + +public enum Role implements Principal { + + WAITER("Waiter"), CUSTOMER("Customer"); + + private final String name; + + private Role(String name) { + + this.name = name; + } + + @Override + public String getName() { + + return "ROLE_" + this.name; + } +} diff --git a/modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/to/UserDetailsClientTo.java b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/to/UserDetailsClientTo.java new file mode 100644 index 00000000..a4fa43c7 --- /dev/null +++ b/modules/security-jwt/src/main/java/com/devonfw/module/security/common/api/to/UserDetailsClientTo.java @@ -0,0 +1,105 @@ +package com.devonfw.module.security.common.api.to; + +import com.devonfw.module.security.common.api.UserProfile; +import com.devonfw.module.security.common.api.datatype.Role; + +/** + * This is the {@link AbstractTo TO} for the client view on the user details. + * + */ +public class UserDetailsClientTo implements UserProfile { + + /** UID for serialization. */ + private static final long serialVersionUID = 1L; + + private Long id; + + private String name; + + private String firstName; + + private String lastName; + + private Role role; + + /** + * The constructor. + */ + public UserDetailsClientTo() { + + super(); + } + + @Override + public Long getId() { + + return this.id; + } + + @Override + public String getName() { + + return this.name; + } + + @Override + public String getFirstName() { + + return this.firstName; + } + + @Override + public String getLastName() { + + return this.lastName; + } + + @Override + public Role getRole() { + + return this.role; + } + + /** + * Sets the ID. + * + * @param id the ID to set + */ + public void setId(Long id) { + + this.id = id; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + + this.name = name; + } + + /** + * @param firstName the firstName to set + */ + public void setFirstName(String firstName) { + + this.firstName = firstName; + } + + /** + * @param lastName the lastName to set + */ + public void setLastName(String lastName) { + + this.lastName = lastName; + } + + /** + * @param role the role to set + */ + public void setRole(Role role) { + + this.role = role; + } + +} diff --git a/modules/security-jwt/src/test/resources/config/app/security/access-control-schema.xml b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema.xml new file mode 100644 index 00000000..fa7de0bb --- /dev/null +++ b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + ReadOnly + + + + + + + + + + + + + + + + + ReadWrite + + + + + + + + + + ReadWrite + + + + + + + + + ReadWrite + + + + + + + + + + + CustomerAdmin + ContractAdmin + SystemAdmin + + + + \ No newline at end of file diff --git a/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_corrupted.xml b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_corrupted.xml new file mode 100644 index 00000000..5398d402 --- /dev/null +++ b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_corrupted.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_cyclic.xml b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_cyclic.xml new file mode 100644 index 00000000..c6900fb0 --- /dev/null +++ b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_cyclic.xml @@ -0,0 +1,28 @@ + + + + + + + + + + Chief + + + + + Barkeeper + + + + + Waiter + Cook + + + + + + + diff --git a/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_groupTypes.xml b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_groupTypes.xml new file mode 100644 index 00000000..7dfd6f70 --- /dev/null +++ b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_groupTypes.xml @@ -0,0 +1,24 @@ + + + + + + + + + + ReadOnly + + + + + + + + ReadWrite + + + + + + \ No newline at end of file diff --git a/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_illegal.xml b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_illegal.xml new file mode 100644 index 00000000..fd4a6a9f --- /dev/null +++ b/modules/security-jwt/src/test/resources/config/app/security/access-control-schema_illegal.xml @@ -0,0 +1,8 @@ + + + + + Waiter + + + diff --git a/modules/security-kerberos/pom.xml b/modules/security-kerberos/pom.xml new file mode 100644 index 00000000..725f2fbf --- /dev/null +++ b/modules/security-kerberos/pom.xml @@ -0,0 +1,166 @@ + + + 4.0.0 + + com.devonfw.java.dev + devon4j-modules + dev-SNAPSHOT + + com.devonfw.java.modules + security-kerberos + ${devon4j.version} + jar + ${project.artifactId} + Security Module of the Open Application Standard Platform for Java (devon4j). + + + + com.devonfw.java.modules + devon4j-cxf-server-ws + + + + org.apache.directory.server + apacheds-jdbm-partition + 2.0.0.AM25 + + + + + org.apache.directory.studio + org.apache.directory.server.core + 2.0.0-M10 + + + + + + org.apache.directory.server + apacheds-ldif-partition + 2.0.0-M2 + + + + + org.apache.directory.server + apacheds-kerberos-shared + 1.5.5 + + + + + org.apache.directory.server + apacheds-core-api + 2.0.0.AM25 + + + + org.apache.directory.server + apacheds-core + 2.0.0.AM25 + + + + org.apache.directory.server + apacheds-protocol-kerberos + 2.0.0.AM25 + + + + + org.springframework.security.kerberos + spring-security-kerberos-client + 1.0.1.RELEASE + + + + org.springframework.security.kerberos + spring-security-kerberos-web + 1.0.1.RELEASE + + + + org.springframework.security.kerberos + spring-security-kerberos-core + 1.0.1.RELEASE + + + + org.springframework + spring-web + + + javax.servlet + javax.servlet-api + provided + + + + + org.springframework.security + spring-security-config + + + org.springframework.security + spring-security-web + + + + + javax.inject + javax.inject + + + javax.annotation + javax.annotation-api + + + + javax.xml.bind + jaxb-api + true + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.devonfw.java.modules + devon4j-test + test + + + ${project.groupId} + devon4j-logging + test + + + com.google.guava + guava + test + + + + + java9 + + [9,12] + + + + org.glassfish.jaxb + jaxb-runtime + + + javax.activation + javax.activation-api + + + + + \ No newline at end of file diff --git a/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/DummyUserDetailsService.java b/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/DummyUserDetailsService.java new file mode 100644 index 00000000..cb3b1c1a --- /dev/null +++ b/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/DummyUserDetailsService.java @@ -0,0 +1,39 @@ +package com.devonfw.module.security.common; + +import java.security.Principal; +import java.util.Set; + +import javax.inject.Inject; +import javax.security.auth.Subject; + +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +public class DummyUserDetailsService implements UserDetailsService { + @Inject + private KerberosConfigProperties kerbprop; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + try { + ServiceSubjectFactory fact= new ServiceSubjectFactory(kerbprop); + Subject subject=fact.login(); + Set principals=subject.getPrincipals(); + for(Principal principal:principals) { + if(principal.getName().equalsIgnoreCase(username)) { + return new User(username, "abc@123", true, true, true, true, + AuthorityUtils.createAuthorityList("ROLE_USER")); + } + } + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return null; + } +} diff --git a/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/KerberosConfigProperties.java b/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/KerberosConfigProperties.java new file mode 100644 index 00000000..64d08754 --- /dev/null +++ b/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/KerberosConfigProperties.java @@ -0,0 +1,50 @@ +package com.devonfw.module.security.common; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +/** + * + * + */ +@Configuration +public class KerberosConfigProperties { + private boolean debug = true; + + @Value("${kerberos.keytab-location}") + private String keytabLocation; + + @Value("${kerberos.service-principal}") + private String servicePrincipalName; + + public boolean isDebug() { + + return this.debug; + } + + public void setDebug(boolean debug) { + + this.debug = debug; + } + + public String getServicePrincipalName() { + + return this.servicePrincipalName; + } + + public void setServicePrincipalName(String servicePrincipalName) { + + this.servicePrincipalName = servicePrincipalName; + } + + public String getKeytabLocation() { + return keytabLocation; + } + + public void setKeytabLocation(String keytabLocation) { + this.keytabLocation = keytabLocation; + } +} diff --git a/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/KerberosWebSecurityConfig.java b/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/KerberosWebSecurityConfig.java new file mode 100644 index 00000000..fdf0dc83 --- /dev/null +++ b/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/KerberosWebSecurityConfig.java @@ -0,0 +1,186 @@ +package com.devonfw.module.security.common; + +import javax.inject.Inject; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.FileSystemResource; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +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.kerberos.authentication.KerberosAuthenticationProvider; +import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider; +import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosClient; +import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator; +import org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter; +import org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.csrf.CsrfFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + + + +/** + * Configuration and declaration of needed security settings (authorization/authentication) of the application. + **/ +//@Configuration +//@EnableWebSecurity +public class KerberosWebSecurityConfig extends WebSecurityConfigurerAdapter { + + // TODO:: generalise properties injected + // TODO: Extend SpnegoEntryPoint and create a class where you can handle errors + // TODO:: AbstractAuthenticationProvider class needs to be created + // TOD: kerberos config class + +// @Value("${security.cors.enabled}") + boolean corsEnabled = false; + +// @Inject +// private UserDetailsService dummyUserDetailsService; + + @Inject + private KerberosConfigProperties kerbprop ; + +// @Inject +// AuthenticationManager authenticationManager; + +// @Inject +// private PasswordEncoder passwordEncoder; + + private CorsFilter getCorsFilter() { + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("OPTIONS"); + config.addAllowedMethod("HEAD"); + config.addAllowedMethod("GET"); + config.addAllowedMethod("PUT"); + config.addAllowedMethod("POST"); + config.addAllowedMethod("DELETE"); + config.addAllowedMethod("PATCH"); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + + String[] unsecuredResources = new String[] { "/login", "/security/**", "/services/rest/login", + "/services/rest/logout" }; + +// http.httpBasic().authenticationEntryPoint(new SpnegoEntryPoint()).and().userDetailsService(dummyUserDetailsService()) +// .csrf().disable().authorizeRequests().antMatchers(unsecuredResources).permitAll().anyRequest().authenticated() +// .and().logout().permitAll().and().addFilterBefore( +// spnegoAuthenticationProcessingFilter(authenticationManagerBean()), BasicAuthenticationFilter.class); + + http.httpBasic().authenticationEntryPoint(new SpnegoEntryPoint()).and().userDetailsService(dummyUserDetailsService()) + .csrf().disable().authorizeRequests().antMatchers(unsecuredResources).permitAll().anyRequest().authenticated() + .and().logout().permitAll().and().addFilterBefore( + spnegoAuthenticationProcessingFilter(authenticationManagerBean()), BasicAuthenticationFilter.class); + + +// http.exceptionHandling().authenticationEntryPoint(new SpnegoEntryPoint()).and().userDetailsService(userDetailsService()) +// .csrf().disable().authorizeRequests().antMatchers(unsecuredResources).permitAll().anyRequest().authenticated() +// .and().logout().permitAll().and().addFilterBefore( +// spnegoAuthenticationProcessingFilter(authenticationManagerBean()), BasicAuthenticationFilter.class); + + +// http +// // +// .userDetailsService(this.userDetailsService).exceptionHandling().authenticationEntryPoint(new SpnegoEntryPoint()).and() +// // define all urls that are not to be secured +// .authorizeRequests().antMatchers(unsecuredResources).permitAll().anyRequest().authenticated().and() +// +// // activate crsf check for a selection of urls (but not for login & logout) +// .csrf().disable() +// +// // configure parameters for simple form login (and logout) +// .formLogin().successHandler(new SimpleUrlAuthenticationSuccessHandler()).defaultSuccessUrl("/") +// .failureUrl("/login.html?error").loginProcessingUrl("/j_spring_security_login").usernameParameter("username") +// .passwordParameter("password").and() +// // logout via POST is possible +// .logout().logoutSuccessUrl("/login.html").and(). +// +// addFilterBefore( +// spnegoAuthenticationProcessingFilter(authenticationManagerBean()), BasicAuthenticationFilter.class); + + if (this.corsEnabled) + http.addFilterBefore(getCorsFilter(), CsrfFilter.class); + } + + @Inject + protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + + auth.authenticationProvider(kerberosAuthenticationProvider()).authenticationProvider(kerberosServiceAuthenticationProvider()); + // + } + + @Bean + public KerberosAuthenticationProvider kerberosAuthenticationProvider() { + + KerberosAuthenticationProvider provider = new KerberosAuthenticationProvider(); + SunJaasKerberosClient client = new SunJaasKerberosClient(); + provider.setKerberosClient(client); + provider.setUserDetailsService(dummyUserDetailsService()); + return provider; + } + + @Bean + public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() { + + KerberosServiceAuthenticationProvider authenticationProvider = new KerberosServiceAuthenticationProvider(); + SunJaasKerberosTicketValidator ticketValidator = sunJaasKerberosTicketValidator(); + authenticationProvider.setTicketValidator(ticketValidator); + authenticationProvider.setUserDetailsService(dummyUserDetailsService()); + return authenticationProvider; + } + + @Bean + public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() { + + SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator(); + ticketValidator.setServicePrincipal(this.kerbprop.getServicePrincipalName()); + ticketValidator.setKeyTabLocation(new FileSystemResource(this.kerbprop.getKeytabLocation())); + ticketValidator.setDebug(true); + return ticketValidator; + } + +// @Bean +// public KerberosFilter kerberosFilter(AuthenticationManager authenticationManager) { +// +// KerberosFilter filter = new KerberosFilter(); +// filter.setAuthenticationManager(authenticationManager); +// return filter; +// } + + @Bean + public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter( + AuthenticationManager authenticationManager) { + + SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter(); + filter.setAuthenticationManager(authenticationManager); + return filter; + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Bean + public DummyUserDetailsService dummyUserDetailsService() { + return new DummyUserDetailsService(); + } + +} \ No newline at end of file diff --git a/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/ServiceSubjectFactory.java b/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/ServiceSubjectFactory.java new file mode 100644 index 00000000..de0e09d6 --- /dev/null +++ b/modules/security-kerberos/src/main/java/com/devonfw/module/security/common/ServiceSubjectFactory.java @@ -0,0 +1,111 @@ +package com.devonfw.module.security.common; + +import java.io.IOException; +import java.security.Principal; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.kerberos.authentication.sun.GlobalSunJaasKerberosConfig; +import org.springframework.stereotype.Component; + + +@Component +public class ServiceSubjectFactory { + + private final KerberosConfigProperties kerberosProperties; + + + private Subject subject; + + /** + * @return subject + */ + public Subject getSubject() { + + return this.subject; + } + + /** + * @param subject new value of {@link #getsubject}. + */ + public void setSubject(Subject subject) { + + this.subject = subject; + } + + + public ServiceSubjectFactory(KerberosConfigProperties kerberosProperties) throws Exception { + this.kerberosProperties = kerberosProperties; + GlobalSunJaasKerberosConfig config = new GlobalSunJaasKerberosConfig(); + config.setDebug(kerberosProperties.isDebug()); + config.afterPropertiesSet(); + System.setProperty("sun.security.krb5.rcache", "none"); + } + + // @Scheduled(fixedRateString = "${kerberos.ticketRefreshSeconds}000") + // public void refreshServiceSubject() throws LoginException, IOException { + // + // if (!this.developMode) + // this.subject = login(); + // } + + + public Subject login() throws LoginException, IOException { + + LoginConfig loginConfig = new LoginConfig(this.kerberosProperties.getKeytabLocation(), + this.kerberosProperties.getServicePrincipalName(), this.kerberosProperties.isDebug()); + Set princ = new HashSet<>(1); + princ.add(new KerberosPrincipal(this.kerberosProperties.getServicePrincipalName())); + Subject sub = new Subject(false, princ, new HashSet<>(), new HashSet<>()); + LoginContext lc = new LoginContext("", sub, null, loginConfig); + lc.login(); + return lc.getSubject(); + } + + /** + * Normally you need a JAAS config file in order to use the JAAS Kerberos Login Module, with this class it is not + * needed and you can have different configurations in one JVM. + */ + private static class LoginConfig extends Configuration { + private String keyTabLocation; + + private String servicePrincipalName; + + private boolean debug; + + public LoginConfig(String keyTabLocation, String servicePrincipalName, boolean debug) { + this.keyTabLocation = keyTabLocation; + this.servicePrincipalName = servicePrincipalName; + this.debug = debug; + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + + HashMap options = new HashMap<>(); + options.put("useKeyTab", "true"); + options.put("keyTab", this.keyTabLocation); + options.put("principal", this.servicePrincipalName); + options.put("storeKey", "true"); + options.put("renewTGT", "true"); + options.put("useTicketCache", "true"); + options.put("doNotPrompt", "true"); + if (this.debug) { + options.put("debug", "true"); + } + options.put("isInitiator", "true"); + + return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), }; + } + } +} diff --git a/modules/security-kerberos/src/main/resources/com/devonfw/module/security/access-control-schema.xsd b/modules/security-kerberos/src/main/resources/com/devonfw/module/security/access-control-schema.xsd new file mode 100644 index 00000000..4b256afb --- /dev/null +++ b/modules/security-kerberos/src/main/resources/com/devonfw/module/security/access-control-schema.xsd @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema.xml b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema.xml new file mode 100644 index 00000000..fa7de0bb --- /dev/null +++ b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + ReadOnly + + + + + + + + + + + + + + + + + ReadWrite + + + + + + + + + + ReadWrite + + + + + + + + + ReadWrite + + + + + + + + + + + CustomerAdmin + ContractAdmin + SystemAdmin + + + + \ No newline at end of file diff --git a/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_corrupted.xml b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_corrupted.xml new file mode 100644 index 00000000..3fbe7dcb --- /dev/null +++ b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_corrupted.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_cyclic.xml b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_cyclic.xml new file mode 100644 index 00000000..c6900fb0 --- /dev/null +++ b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_cyclic.xml @@ -0,0 +1,28 @@ + + + + + + + + + + Chief + + + + + Barkeeper + + + + + Waiter + Cook + + + + + + + diff --git a/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_groupTypes.xml b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_groupTypes.xml new file mode 100644 index 00000000..7dfd6f70 --- /dev/null +++ b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_groupTypes.xml @@ -0,0 +1,24 @@ + + + + + + + + + + ReadOnly + + + + + + + + ReadWrite + + + + + + \ No newline at end of file diff --git a/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_illegal.xml b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_illegal.xml new file mode 100644 index 00000000..fd4a6a9f --- /dev/null +++ b/modules/security-kerberos/src/test/resources/config/app/security/access-control-schema_illegal.xml @@ -0,0 +1,8 @@ + + + + + Waiter + + +