diff options
Diffstat (limited to 'mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security')
6 files changed, 541 insertions, 0 deletions
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/WebSecurityConfigurer.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/WebSecurityConfigurer.java new file mode 100644 index 0000000..cfccce4 --- /dev/null +++ b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/WebSecurityConfigurer.java @@ -0,0 +1,92 @@ +/* + * + * * ============LICENSE_START======================================================= + * * org.onap.dcae + * * ================================================================================ + * * Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * * ================================================================================ + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcaegen2.platform.mod.security; + +import org.onap.dcaegen2.platform.mod.security.jwt.AuthEntryPointJwt; +import org.onap.dcaegen2.platform.mod.security.jwt.AuthTokenFilter; +import org.onap.dcaegen2.platform.mod.security.services.UserDetailsServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + + +/** + * @author + * @date 09/08/2020 + * Allows customization to the Spring WebSecurity + */ + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { + + @Autowired + UserDetailsServiceImpl userDetailsService; + + @Autowired + private AuthEntryPointJwt unauthorizedHandler; + + @Bean + public AuthTokenFilter authenticationJwtTokenFilter(){ + return new AuthTokenFilter(); + } + + @Override + protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { + authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception{ + return super.authenticationManagerBean(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.cors().and().csrf().disable() + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .authorizeRequests().antMatchers("/api/auth/**").permitAll() + .antMatchers("/api/users/**").permitAll() + .anyRequest().authenticated(); + + http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthEntryPointJwt.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthEntryPointJwt.java new file mode 100644 index 0000000..310d487 --- /dev/null +++ b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthEntryPointJwt.java @@ -0,0 +1,51 @@ +/* + * + * * ============LICENSE_START======================================================= + * * org.onap.dcae + * * ================================================================================ + * * Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * * ================================================================================ + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcaegen2.platform.mod.security.jwt; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author + * @date 09/08/2020 + * JWT Authentication Entry Point + */ + +@Slf4j +@Component +public class AuthEntryPointJwt implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + log.error("Unauthorized error: {}", authException.getMessage()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized"); + } +} diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthTokenFilter.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthTokenFilter.java new file mode 100644 index 0000000..012c333 --- /dev/null +++ b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthTokenFilter.java @@ -0,0 +1,81 @@ +/* + * + * * ============LICENSE_START======================================================= + * * org.onap.dcae + * * ================================================================================ + * * Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * * ================================================================================ + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcaegen2.platform.mod.security.jwt; + +import org.onap.dcaegen2.platform.mod.security.services.UserDetailsServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author + * @date 09/08/2020 + * Authentication Token Filter + */ +@Slf4j +public class AuthTokenFilter extends OncePerRequestFilter { + + @Autowired + private JwtUtils jwtUtils; + + @Autowired + private UserDetailsServiceImpl userDetailsService; + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { + try{ + String jwt = parseJwt(httpServletRequest); + if (jwt != null && jwtUtils.validateJwtToken(jwt)){ + String username = jwtUtils.getUserNameFromJwtToken(jwt); + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + }catch (Exception e){ + logger.error("Cannot set user authentication: {}", e); + } + filterChain.doFilter(httpServletRequest, httpServletResponse); + } + + private String parseJwt(HttpServletRequest httpServletRequest) { + String headerAuth = httpServletRequest.getHeader("Authorization"); + + if(StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")){ + return headerAuth.substring(7, headerAuth.length()); + } + return null; + } +} diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/JwtUtils.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/JwtUtils.java new file mode 100644 index 0000000..3b6d311 --- /dev/null +++ b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/JwtUtils.java @@ -0,0 +1,90 @@ +/* + * + * * ============LICENSE_START======================================================= + * * org.onap.dcae + * * ================================================================================ + * * Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * * ================================================================================ + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcaegen2.platform.mod.security.jwt; + +import org.onap.dcaegen2.platform.mod.security.services.UserDetailsImpl; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * @author + * @date 09/08/2020 + * JWT Utils + */ +@Slf4j +@Component +public class JwtUtils { + + @Value("${mod-portal.jwt.secret}") + private String jwtSecret; + + @Value("${mod-portal.jwt.jwtExpirationMs}") + private int jwtExpirationMs; + + public String generateJwtToken(Authentication authentication) { + + UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); + + return Jwts.builder() + .setSubject((userPrincipal.getUsername())) + .claim("roles", userPrincipal.getAuthoritiesAsList()) + .claim("fullName", userPrincipal.getFullName()) + .setIssuedAt(new Date()) + .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) + .signWith(SignatureAlgorithm.HS512, jwtSecret) + .compact(); + } + + public String getUserNameFromJwtToken(String token) { + return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject(); + } + + public boolean validateJwtToken(String authToken) { + try { + Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); + return true; + } catch (SignatureException e) { + log.error("Invalid JWT signature: {}", e.getMessage()); + } catch (MalformedJwtException e) { + log.error("Invalid JWT token: {}", e.getMessage()); + } catch (ExpiredJwtException e) { + log.error("JWT token is expired: {}", e.getMessage()); + } catch (UnsupportedJwtException e) { + log.error("JWT token is unsupported: {}", e.getMessage()); + } catch (IllegalArgumentException e) { + log.error("JWT claims string is empty: {}", e.getMessage()); + } + + return false; + } +} diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsImpl.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsImpl.java new file mode 100644 index 0000000..82cb577 --- /dev/null +++ b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsImpl.java @@ -0,0 +1,127 @@ +/* + * + * * ============LICENSE_START======================================================= + * * org.onap.dcae + * * ================================================================================ + * * Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * * ================================================================================ + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcaegen2.platform.mod.security.services; + +import org.onap.dcaegen2.platform.mod.models.ModUser; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.EqualsAndHashCode; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author + * @date 09/08/2020 + * User Details Implementation + */ +@EqualsAndHashCode +public class UserDetailsImpl implements UserDetails{ + + private static final long serialVersionUID = 1L; + + private String id; + + private String username; + + private String fullName; + + @JsonIgnore + private String password; + + private Collection<? extends GrantedAuthority> authorities; + + public UserDetailsImpl(String id, String username, String fullName, String password, Collection<? + extends GrantedAuthority> authorities) { + this.id = id; + this.username = username; + this.fullName = fullName; + this.password = password; + this.authorities = authorities; + } + + public static UserDetails build(ModUser user) { + List<GrantedAuthority> authorities = user.getRoles().stream() + .map(role -> new SimpleGrantedAuthority(role.getName())) + .collect(Collectors.toList()); + + return new UserDetailsImpl( + user.get_id(), + user.getUsername(), + user.getFullName(), + user.getPassword(), + authorities + ); + } + + public String getId() { + return id; + } + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return authorities; + } + + public List<String> getAuthoritiesAsList(){ + return authorities.stream().map(GrantedAuthority::getAuthority) + .collect(Collectors.toList()); + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + public String getFullName() { + return fullName; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsServiceImpl.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsServiceImpl.java new file mode 100644 index 0000000..5c13e11 --- /dev/null +++ b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsServiceImpl.java @@ -0,0 +1,100 @@ +/* + * + * * ============LICENSE_START======================================================= + * * org.onap.dcae + * * ================================================================================ + * * Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * * ================================================================================ + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcaegen2.platform.mod.security.services; + +import org.onap.dcaegen2.platform.mod.controllers.AuthController; +import org.onap.dcaegen2.platform.mod.exceptions.UserNotFoundException; +import org.onap.dcaegen2.platform.mod.exceptions.IllegalUserOperationException; +import org.onap.dcaegen2.platform.mod.models.ModUser; +import org.onap.dcaegen2.platform.mod.models.Role; +import org.onap.dcaegen2.platform.mod.models.UpdateUserRequest; +import org.onap.dcaegen2.platform.mod.repositories.UserRepository; +import org.onap.dcaegen2.platform.mod.security.jwt.JwtUtils; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +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 org.springframework.stereotype.Service; + +import java.util.Set; + +/** + * @author + * @date 09/08/2020 + * User Details Service + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + @Autowired + @Setter + UserRepository userRepository; + + @Autowired + PasswordEncoder passwordEncoder; + + @Autowired + AuthController authController; + + @Autowired + private JwtUtils jwtUtils; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + ModUser user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username)); + + return UserDetailsImpl.build(user); + } + + public ModUser adminUpdateUser(String username, UpdateUserRequest userRequest, String token) { + return updateUserProfile(username, userRequest); + } + + public ModUser userUpdateOwnProfile(String username, UpdateUserRequest userRequest, String token) { + String usernameFromToken = jwtUtils.getUserNameFromJwtToken(token.substring(7)); + if (usernameFromToken.equals(username)) { + return updateUserProfile(username, userRequest); + } else { + throw new IllegalUserOperationException("Permission denied to update user profile of " + username); + } + } + + private ModUser updateUserProfile(String username, UpdateUserRequest userRequest) { + ModUser modUser = userRepository.findByUsername(username).orElseThrow(() -> new UserNotFoundException(String.format("User %s not found", username))); + modUser = updateUserFields(modUser, userRequest); + return userRepository.save(modUser); + } + + private ModUser updateUserFields(ModUser modUser, UpdateUserRequest userRequest) { + if (userRequest.getFullName() != null) modUser.setFullName(userRequest.getFullName()); + if (userRequest.getPassword() != null) modUser.setPassword(passwordEncoder.encode(userRequest.getPassword())); + if (userRequest.getRoles() != null) { + Set<Role> roles = authController.createRoles(userRequest.getRoles()); + modUser.setRoles(roles); + } + return modUser; + } +} |