From: abhijit_onap Date: Wed, 17 Jun 2020 11:40:40 +0000 (+0530) Subject: Added Authentication Component X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=commitdiff_plain;ds=inline;h=7e4a1bc460881fb10ea8993da83f0956f8cf3463;p=eliot.git Added Authentication Component Added JWT Authentication Files. Updated with License information. Signed-off-by: abhijit_onap Change-Id: Ice1df8511e36a2c8e39f7f2fea0781ce398730cd --- diff --git a/blueprints/common/eliot-ui/be/src/eliotk8sclient/pom.xml b/blueprints/common/eliot-ui/be/src/eliotk8sclient/pom.xml index 66d3f19..e33a861 100644 --- a/blueprints/common/eliot-ui/be/src/eliotk8sclient/pom.xml +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/pom.xml @@ -29,12 +29,27 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-security + + + io.jsonwebtoken + jjwt + 0.9.1 + + + javax.xml.bind + jaxb-api + 2.3.0 + io.kubernetes client-java 5.0.0 compile + diff --git a/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/config/JwtAuthenticationEntryPoint.java b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/config/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..069a239 --- /dev/null +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/config/JwtAuthenticationEntryPoint.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Huawei Technologies Co., Ltd. + * + * 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. + */ +package com.eliot.eliotbe.eliotk8sclient.config; + +import java.io.IOException; +import java.io.Serializable; + +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 JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { + + private static final long serialVersionUID = -7858869558953243875L; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + } +} diff --git a/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/config/JwtRequestFilter.java b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/config/JwtRequestFilter.java new file mode 100644 index 0000000..6244072 --- /dev/null +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/config/JwtRequestFilter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2020 Huawei Technologies Co., Ltd. + * + * 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. + */ +package com.eliot.eliotbe.eliotk8sclient.config; + +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.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.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.eliot.eliotbe.eliotk8sclient.service.JwtUserDetailsService; +import com.eliot.eliotbe.eliotk8sclient.util.*; +import io.jsonwebtoken.ExpiredJwtException; + +@Component +public class JwtRequestFilter extends OncePerRequestFilter { + + @Autowired + private JwtUserDetailsService jwtUserDetailsService; + + @Autowired + private JwtTokenUtil jwtTokenUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + final String requestTokenHeader = request.getHeader("Authorization"); + + String username = null; + String jwtToken = null; + // JWT Token is in the form "Bearer token". Remove Bearer word and get + // only the Token + if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { + jwtToken = requestTokenHeader.substring(7); + try { + username = jwtTokenUtil.getUsernameFromToken(jwtToken); + } catch (IllegalArgumentException e) { + System.out.println("Unable to get JWT Token"); + } catch (ExpiredJwtException e) { + System.out.println("JWT Token has expired"); + } + } else { + logger.warn("JWT Token does not begin with Bearer String"); + } + + // Once we get the token validate it. + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + + UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username); + + // if token is valid configure Spring Security to manually set + // authentication + if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { + + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + usernamePasswordAuthenticationToken + .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + // After setting the Authentication in the context, we specify + // that the current user is authenticated. So it passes the + // Spring Security Configurations successfully. + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + } + } + chain.doFilter(request, response); + } + +} diff --git a/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/config/WebSecurityConfig.java b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/config/WebSecurityConfig.java new file mode 100644 index 0000000..b6904b0 --- /dev/null +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/config/WebSecurityConfig.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 Huawei Technologies Co., Ltd. + * + * 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. + */ +package com.eliot.eliotbe.eliotk8sclient.config; + +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.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + + @Autowired + private UserDetailsService jwtUserDetailsService; + + @Autowired + private JwtRequestFilter jwtRequestFilter; + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // configure AuthenticationManager so that it knows from where to load + // user for matching credentials + // Use BCryptPasswordEncoder + auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder()); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception { + // We don't need CSRF for this example + httpSecurity.csrf().disable() + // dont authenticate this particular request + .authorizeRequests().antMatchers("/authenticate").permitAll(). + // all other requests need to be authenticated + anyRequest().authenticated().and(). + // make sure we use stateless session; session won't be used to + // store user's state. + exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + // Add a filter to validate the tokens with every request + httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/controller/AuthenticationController.java b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/controller/AuthenticationController.java new file mode 100644 index 0000000..8ece21a --- /dev/null +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/controller/AuthenticationController.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Huawei Technologies Co., Ltd. + * + * 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. + */ +package com.eliot.eliotbe.eliotk8sclient.controller; + +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import com.eliot.eliotbe.eliotk8sclient.service.JwtUserDetailsService; + + +import com.eliot.eliotbe.eliotk8sclient.util.JwtTokenUtil; +import com.eliot.eliotbe.eliotk8sclient.model.jwt.JwtRequest; +import com.eliot.eliotbe.eliotk8sclient.model.jwt.JwtResponse; + +@RestController +@CrossOrigin +public class AuthenticationController { + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private JwtTokenUtil jwtTokenUtil; + + @Autowired + private JwtUserDetailsService userDetailsService; + + @RequestMapping(value = "/authenticate", method = RequestMethod.POST) + public ResponseEntity createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception { + + authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); + + final UserDetails userDetails = userDetailsService + .loadUserByUsername(authenticationRequest.getUsername()); + + final String token = jwtTokenUtil.generateToken(userDetails); + + return ResponseEntity.ok(new JwtResponse(token)); + } + + private void authenticate(String username, String password) throws Exception { + try { + authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); + } catch (DisabledException e) { + throw new Exception("USER_DISABLED", e); + } catch (BadCredentialsException e) { + throw new Exception("INVALID_CREDENTIALS", e); + } + } +} \ No newline at end of file diff --git a/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/model/jwt/JwtRequest.java b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/model/jwt/JwtRequest.java new file mode 100644 index 0000000..ea330a0 --- /dev/null +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/model/jwt/JwtRequest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Huawei Technologies Co., Ltd. + * + * 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. + */ +package com.eliot.eliotbe.eliotk8sclient.model.jwt; + +import java.io.Serializable; + +public class JwtRequest implements Serializable { + + private static final long serialVersionUID = 5926468583005150707L; + + private String username; + private String password; + + //need default constructor for JSON Parsing + public JwtRequest() + { + + } + + public JwtRequest(String username, String password) { + this.setUsername(username); + this.setPassword(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/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/model/jwt/JwtResponse.java b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/model/jwt/JwtResponse.java new file mode 100644 index 0000000..3e71cf2 --- /dev/null +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/model/jwt/JwtResponse.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Huawei Technologies Co., Ltd. + * + * 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. + */ +package com.eliot.eliotbe.eliotk8sclient.model.jwt; + +import java.io.Serializable; + +public class JwtResponse implements Serializable { + + private static final long serialVersionUID = -8091879091924046844L; + private final String jwttoken; + + public JwtResponse(String jwttoken) { + this.jwttoken = jwttoken; + } + + public String getToken() { + return this.jwttoken; + } +} diff --git a/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/service/JwtUserDetailsService.java b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/service/JwtUserDetailsService.java new file mode 100644 index 0000000..7c7ad87 --- /dev/null +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/service/JwtUserDetailsService.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Huawei Technologies Co., Ltd. + * + * 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. + */ +package com.eliot.eliotbe.eliotk8sclient.service; + +import java.util.ArrayList; + +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; +import org.springframework.stereotype.Service; +@Service +public class JwtUserDetailsService implements UserDetailsService { + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + if ("admin".equals(username)) { + return new User("admin", "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6", + new ArrayList<>()); + } else { + throw new UsernameNotFoundException("User not found with username: " + username); + } + } +} diff --git a/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/util/JwtTokenUtil.java b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/util/JwtTokenUtil.java new file mode 100644 index 0000000..2f04a37 --- /dev/null +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/java/com/eliot/eliotbe/eliotk8sclient/util/JwtTokenUtil.java @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Huawei Technologies Co., Ltd. + * + * 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. + */ +package com.eliot.eliotbe.eliotk8sclient.util; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +@Component +public class JwtTokenUtil implements Serializable { + + private static final long serialVersionUID = -2550185165626007488L; + + public static final long JWT_TOKEN_VALIDITY = 1 * 60 * 60; + + @Value("${jwt.secret}") + private String secret; + + //retrieve username from jwt token + public String getUsernameFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + + //retrieve expiration date from jwt token + public Date getExpirationDateFromToken(String token) { + return getClaimFromToken(token, Claims::getExpiration); + } + + public T getClaimFromToken(String token, Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + //for retrieveing any information from token we will need the secret key + private Claims getAllClaimsFromToken(String token) { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + } + + //check if the token has expired + private Boolean isTokenExpired(String token) { + final Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } + + //generate token for user + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + return doGenerateToken(claims, userDetails.getUsername()); + } + + private String doGenerateToken(Map claims, String subject) { + + return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + } + + //validate token + public Boolean validateToken(String token, UserDetails userDetails) { + final String username = getUsernameFromToken(token); + return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } +} diff --git a/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/resources/application.properties b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/resources/application.properties index c1a46e5..2262d61 100644 --- a/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/resources/application.properties +++ b/blueprints/common/eliot-ui/be/src/eliotk8sclient/src/main/resources/application.properties @@ -1 +1,2 @@ server.port = 8080 +jwt.secret = eliotblueprint