Spring Security

Spring Security (https://spring.io/projects/spring-security) provides security services for Java-based web applications. The Spring Security project was started in 2003 and was previously named The Acegi Security System for Spring.

By default, Spring Security enables the following features:

  • An AuthenticationManager bean with an in-memory single user. The username is user and the password is printed to the console output.
  • Ignored paths for common static resource locations, such as /css and /images.
  • HTTP basic security for all other endpoints.
  • Security events published to Spring's ApplicationEventPublisher.
  • Common low-level features are on by default (HSTS, XSS, CSRF, and so forth).

You can include Spring Security in your application by adding the following dependency to the pom.xml file:

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

When you start your application, you can see from the console that Spring Security has created an in-memory user with the username user. The user's password can be seen in the console output:

If you make a GET request to your API endpoint, you will see that it is now secure, and you will get a 401 Unauthorized error:

To be able to make a successful GET request, we have to use basic authentication. The following screenshot shows how to do this with Postman. With authentication, we can see that the status is 200 OK and that the response has been sent:

To configure how Spring Security behaves, we have to add a new configuration class that extends WebSecurityConfigurerAdapter. Create a new class called SecurityConfig in your application root package. The following source code shows the structure of the security configuration class. The @Configuration and @EnableWebSecurity annotations switch off the default web security configuration, and we can define our own configuration in this class. Inside the configure(HttpSecurity http) method, we can define which endpoints in our application are secure and which are not. We actually don't need this method yet because we can use the default settings where all the endpoints are secured:

package com.packt.cardatabase;

import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {

}

}

We can also add in-memory users to our application by adding the userDetailsService() method to our SecurityConfig class. The following is the source code of the method, and it will create an in-memory user with the username user and the password password:

  @Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();

return new InMemoryUserDetailsManager(user);
}

The use of in-memory users is good in the development phase, but the real application should save the users in the database. To save the users to the database, you have to create a user entity class and repository. Passwords shouldn't be saved to the database in plain text format. Spring Security provides multiple hashing algorithms, such as BCrypt, that you can use to hash passwords. The following steps show you how to implement this:

  1. Create a new class called User in the domain package. Activate the domain package and right-click it. Select New | Class from the menu and give the name User to a new class. After that, your project structure should look like the following screenshot:
  1. Annotate the User class with the @Entity annotation. Add the id, username, password, and role class fields. Finally, add the constructors, getters, and setters. We will set all the fields to be nullable, and specify that the username must be unique by using the @Column annotation. Refer to the following User.java source code of the fields and constructors:
package com.packt.cardatabase.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false, updatable = false)
private Long id;

@Column(nullable = false, unique = true)
private String username;

@Column(nullable = false)
private String password;

@Column(nullable = false)
private String role;

public User() {
}

public User(String username, String password, String role) {
super();
this.username = username;
this.password = password;
this.role = role;
}

The following is the rest of the User.java source code with the getters and setters:

  public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getRole() {
return role;
}

public void setRole(String role) {
this.role = role;
}
}
  1. Create a new class called UserRepository in the domain package. Activate the domain package and right-click it. Select NewClass from the menu and give the name UserRepository to the new class. 
  2. The source code of the repository class is similar to what we made in the previous chapter, but there is one query method, findByUsernamethat we need for the steps that follow. Refer to the following UserRepository source code:
package com.packt.cardatabase.domain;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUsername(String username);
}
  1. Next, we will create a class that implements the UserDetailsService interface that's provided by Spring Security. Spring Security uses this for user authentication and authorization. Create a new package in the root package called serviceActivate the root package and right-click it. Select New | Package from the menu and give the name service to a new package:
  1. Create a new class called UserDetailServiceImpl in the service package we just created. Now, your project structure should look like the following:
  1. We have to inject the UserRepository class into the UserDetailServiceImpl class because that is needed to fetch the user from the database when Spring Security handles authentication. The loadByUsername method returns the UserDetails object, which is required for authentication. The following is the source code of UserDetailServiceImpl.java:
package com.packt.cardatabase.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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;

import com.packt.cardatabase.domain.User;
import com.packt.cardatabase.domain.UserRepository;

@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserRepository repository;


@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
User currentUser = repository.findByUsername(username);
UserDetails user = new org.springframework.security.core
.userdetails.User(username, currentUser.getPassword()
, true, true, true, true,
AuthorityUtils.createAuthorityList(currentUser.getRole()));
return user;
}

}
  1. In our security configuration class, we have to stipulate that Spring Security should use users from the database instead of in-memory users. Delete the userDetailsService() method from the SecurityConfig class to disable in-memory users. Add a new configureGlobal method to enable users from the database. We should never save the password as plain text to the database. Therefore, we will define a password hashing algorithm in the configureGlobal method. In this example, we are using the BCrypt algorithm. This can be easily implemented with the Spring Security BCryptPasswordEncoder class. The following is the SecurityConfig.java source code. Now, the password must be hashed using BCrypt before it's saved to the database:
package com.packt.cardatabase;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;

import com.packt.cardatabase.service.UserDetailServiceImpl;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailServiceImpl userDetailsService;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
  1. Finally, we can save a couple of test users to the database in our CommandLineRunner. Open the CardatabaseApplication.java file and add the following code at the beginning of the class to inject UserRepository into the main class:
@Autowired 
private UserRepository urepository;
  1. Save the users to the database with hashed passwords. You can use any BCrypt calculator on the internet to do so:
  @Bean
CommandLineRunner runner() {
return args -> {
Owner owner1 = new Owner("John" , "Johnson");
Owner owner2 = new Owner("Mary" , "Robinson");
orepository.save(owner1);
orepository.save(owner2);

repository.save(new Car("Ford", "Mustang", "Red", "ADF-1121",
2017, 59000, owner1));
repository.save(new Car("Nissan", "Leaf", "White", "SSJ-3002",
2014, 29000, owner2));
repository.save(new Car("Toyota", "Prius", "Silver", "KKO-0212",
2018, 39000, owner2));

// username: user password: user
urepository.save(new User("user",
"$2a$04$1.YhMIgNX/8TkCKGFUONWO1waedKhQ5KrnB30fl0Q01QKqmzLf.Zi",
"USER"));

// username: admin password: admin
urepository.save(new User("admin",
"$2a$04$KNLUwOWHVQZVpXyMBNc7JOzbLiBjb9Tk9bP7KNcPI12ICuvzXQQKG",
"ADMIN"));

};
}

After running your application, you will see that there is now a user table in the database and that two user records are saved:

Now, you will get a 401 Unauthorized error if you try to send a GET request to the /api endpoint without authentication. You should authenticate to be able to send a successful request. The difference, when compared with the previous example, is that we are using the users from the database to authenticate. 

You can see a GET request to the /api endpoint using the admin user in the following screenshot:

Next, we will start to implement authentication using JSON web tokens.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.1.239