Showing posts with label security. Show all posts
Showing posts with label security. Show all posts

Sunday, July 26, 2020

Spring Boot application - integration with Vault

Description

Vault is a very useful tool to store sensitive data in secure way. To get the data is necessary to pass thorough authentication process. Applications can get credential and certificates to DB, internal and external services,  file storages  etc. In addition Voult can encrypt data which could be store for example in DB (this case won't be checked in this post). 



In our common case we prepare simple application to grab sensitive information. We only put that data to the logger to check solution.







The Solution

Volt

Basic Vault server configuration is described at: https://spring.io/guides/gs/vault-config/. There exists important information such as Java version or path to the sources  (https://www.vaultproject.io/downloads).  It is recommended to add voult's path to the system variables. 

Lets start the Vault server:
vault server --dev --dev-root-token-id="00000000-0000-0000-0000-000000000000"  




Next lets add secrets:

vault kv put secret/artsci-vault-config artsci.username=artsciUser artsci.password=artsciPass




The same result we can see in web browser (http://localhost:8200/). It is necessary to use token we defined at the beginning
(00000000-0000-0000-0000-000000000000)



Then select 'secret' path:



And finally we can see previously created secret element.

 
As You can see everything is correct. You can manage this item. You  can create new version or delete this item.


Spring boot application

I created new application with configuration. Very important is bootstrap.properties file. That configuration is loaded at the beginning.  



 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.artsci</groupId>
  <artifactId>artsciVoultSpring</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Voult client </name>
  
  
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
    </parent>

    <dependencies>

        <!-- Vault Starter -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-vault-config</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.8.0-beta4</version>     
</dependency>
        
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    
    <pluginRepositories>
        <pluginRepository>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
            <layout>default</layout>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <updatePolicy>never</updatePolicy>
            </releases>
        </pluginRepository>
    </pluginRepositories>
    <repositories>
        <repository>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
            <layout>default</layout>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

bootstrap.properties

spring.application.name=artsci-vault-config
spring.cloud.vault.uri=http://localhost:8200
spring.cloud.vault.token=00000000-0000-0000-0000-000000000000
spring.cloud.vault.scheme=http
spring.cloud.vault.kv.enabled=true


VoltVariables .class

package artsciVoultSpring;

import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;

@ConfigurationProperties("artsci")
@Data
public class VoltVariables {
private String username;
private String password;
}


ArtsciSpringVoultApp 

package artsciVoultSpring;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;


@SpringBootApplication
@EnableConfigurationProperties(VoltVariables.class)
public class ArtsciSpringVoultApp implements CommandLineRunner  {
private final VoltVariables voltVariables;
public ArtsciSpringVoultApp (VoltVariables voltVariables) {
this.voltVariables = voltVariables;
}
public static void main(String[] args) {
SpringApplication.run(ArtsciSpringVoultApp.class, args);
}
public void run(String... args) {

    Logger logger = LoggerFactory.getLogger(ArtsciSpringVoultApp.class);

    logger.info("----------------------------------------");
    logger.info("Configuration properties");
    logger.info("Username: {}", voltVariables.getUsername());
    logger.info("Password: {}", voltVariables.getPassword());     
    logger.info("----------------------------------------");
  }
}

The Results

At the end we can compare properties in Voult and in application logs


So everything looks good. Variables are exactly the same :)

Thursday, May 28, 2020

Token JWT and Spring Boot

Objectives

Today I would like to extend my previous post (https://java-architect.blogspot.com/2020/05/spring-boot-rest-api-with-security.html) and add protection API based on JWT Token. At the beginning I would like to briefly describe the idea of tokens. Below is added BPMN process flow which shows the end-to-end path (this is a simple process flow without filters, authentication controllers or authentication managers)












The token structure
Token consists of three elements:
  • Header (algorithm and type) - {"alg":"HS256"}
  • Payload  - {"sub":"user","exp":1590655192}
  • Signature (defined secret was used to create signature)


Application

I created application based on my previous post (https://java-architect.blogspot.com/2020/05/spring-boot-rest-api-with-security.html).  The main goal is to create JWT token to protect communication between server and clients. 




I selected files which I added or replaced. Let's examine the application's code. 


pom.xml

  <dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>  
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-openid</artifactId>  
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>  
</dependency>


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>  
</dependency>


<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>



UserDTO:

@Data
public class UserDTO {
    private String username;
    private String password;
}



SecurityConfig.class


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
        public static final String prefix = "Bearer";
public static final String header = "Authorization";
        public static final String secret = Base64.getEncoder().encodeToString("artsci".getBytes());;
        public static final Long expir = new Long(3600000);

@SuppressWarnings("deprecation")
@Bean
    public UserDetailsService userDetailsService() {
        User.UserBuilder users = User.withDefaultPasswordEncoder();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(users.username("user").password("user").roles("USER").build());
        manager.createUser(users.username("admin").password("admin").roles("USER", "ADMIN").build());
        return manager;
    }

   @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
         auth.userDetailsService(userDetailsService());
}
   
    @Override
    protected void configure(HttpSecurity http) throws Exception {
     
        http.csrf().disable()   
        .authorizeRequests()
        .antMatchers(HttpMethod.POST, "/login").permitAll()
        .anyRequest().authenticated()
        .and().addFilterBefore(new LoginFilter("/login", authenticationManager()),   
                 UsernamePasswordAuthenticationFilter.class)
        .addFilterBefore(new AuthJwtFilter(), UsernamePasswordAuthenticationFilter.class);
        http.headers().cacheControl();
    }


LoginFilter.class

public class LoginFilter extends AbstractAuthenticationProcessingFilter {
    public LoginFilter(String url, AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher(url));
        setAuthenticationManager(authenticationManager);
   }

   @Override

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse
           response)
throws AuthenticationException, IOException, ServletException {
    UserDTO user = new ObjectMapper().readValue(request.getInputStream(), UserDTO.class);
     UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                                                                                                 user.getUsername(),
                                                                                                 user.getPassword(),
                                                                                                 Collections.emptyList());

     return getAuthenticationManager().authenticate(token);
}

   @Override

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
          FilterChain chain,
Authentication authResult) throws IOException, ServletException {

     String token = Jwts.builder()
            .setSubject(authResult.getName())         
            .signWith(SignatureAlgorithm.HS256, secret)
            .setExpiration(new Date(System.currentTimeMillis() + expir))
            .compact();
     response.addHeader(header, prefix + " " + token);    
     response.setContentType("application/json");
     response.setCharacterEncoding("UTF-8");
     response.getWriter().write("{\"" + header + "\":\"" + prefix + " " + token + "\"}");     
}
}

AuthJwtFilter.class

public class AuthJwtFilter extends GenericFilterBean{
@Override
public void doFilter(ServletRequest request, ServletResponse response,
                             FilterChain chain) throws IOException, ServletException {
Authentication auth = null;
         if(!((HttpServletRequest) request).getHeader(header).isEmpty()) {
String claim = ((HttpServletRequest)request).getHeader(header) 
                             .replace(prefix,"").substring(1);
String user = Jwts.parser().setSigningKey(secret).parseClaimsJws(claim)
                        .getBody().getSubject();
auth = new UsernamePasswordAuthenticationToken(user, null,                                      
                             Collections.emptyList());

}
       
         SecurityContextHolder.getContext().setAuthentication(auth);
         chain.doFilter(request,response);
}
}

The results

So, our application should work correctly. Let's try to call protected API. At the beginning it is necessary to to generate token. To achieve this goal I attempt to use URL:  "/login" and pass my credential. Below is example of CURL's request.


curl --location --request POST http://localhost:8080/login --header "Content-Type: application/json" --data-raw "{\"username\":\"user\",\"password\":\"user\"}"
{"Authorization":"Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiZXhwIjoxNTkwNjU1MTkyfQ.lKZMYWVKd9OZu2nOwMjRxXjewQ-zYKQWWB4wIp1Zhi8"}

Fantastic, JWT Token was generated. Let's try to call "/regions" service using previously generated token.


curl -H "Authorization":"Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiZXhwIjoxNTkwNjU1MTkyfQ.lKZMYWVKd9OZu2nOwMjRxXjewQ-zYKQWWB4wIp1Zhi8" --request GET http://localhost:8080/regions/
[{"regionId":1,"name":"Europe"},{"regionId":2,"name":"Americas"},{"regionId":3,"name":"Asia"},{"regionId":4,"name":"Middle East and Africa"}]

It seems that the solution works exactly as I expected :)