codestory

Sichere Spring Boot RESTful Service mit Basic Authentication

1- Das Zweck des Beispiel

Der Unterlagen wird nach ... geschrieben
  • Spring Boot 2.x (Or >= 1.5.9)

  • Eclipse 4.7 Oxygen

Mehr sehen
Im Artikel leite ich Sie bei der Erstellung einer Appkikation RESTful Web Service an und sichere sie mit Basic Authentication. D.h bietet Ihre Applikation die Ressources aber wenn der Benutzer die Ressources benutzen möchte, soll er mit der Basic Authentication bestätigt werden (authenticate)

Basic Authentication (die Basisbestätigung)

Um den durch Basic Authentication gesicherten Ressourcen zuzugreifen, soll der Benutzer ein request schicken und im request gibt es die Information von username/password , die auf Header angefügt wird.
Sie können den Browser zum Zugang einer durch Basic Authentication gesicherten Ressourcen verwenden. In diesem Fall wird ein Dialog angezeigt damit Sie sername/password eingeben können. Diese Information wird im request zum Schicken nach REST Server ​​​​​​​angefügt.

2- Spring Boot Projekt erstellen

Auf Eclipse erstellen Sie ein Projekt Spring Boot.
Geben Sie ein
  • Name: SbRestBasicAuthentication
  • Group: org.o7planning
  • Package: org.o7planning.sbrestbasicauth
Zunächst sollen sie die Technologie zum Verwendung wählen 
SbRestBasicAuthenticationApplication.java
package org.o7planning.sbrestbasicauth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SbRestBasicAuthenticationApplication {

    public static void main(String[] args) {
        SpringApplication.run(SbRestBasicAuthenticationApplication.class, args);
    }
}
 

3- pom.xml konfigurieren

In diesem Beispiel brauchen Sie eine Bibliothek um  vom XML zu Java umzuwandeln (convert) und umgekehrt und eine Bibliothek umvom JSON zu Java umzuwandeln und umgekehrt

JSON <==> Java

spring-boot-starter-web hat die Bibliothek jackson-databind schon integriert. Diese Bibliothek hilft bei der Umwandlung von JSON zu Java  und umgekehrt

XML <==> Java

Spring Boot verwendet JAXB (in JDK verfügbar) als eine Standard-Bibliothek um vom XML und Java umzuwandeln. Allerdings brauchen die Klasse von Ihrem Java durch @XmlRootElement,... annotiert zu werden. Deshalb sollen Sie meiner Meinung nach jackson-dataformat-xml wie eine Bibliothek der Umwandlung vom  XML und Java verwenden. Um jackson-dataformat-xml zu benutzen sollen Sie es in die File pom.xml deklarieren:
** pom.xml **
...

<dependencies>

        ...
    
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>

    ...

</dependencies>

...
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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
        http://maven.apache.org/xsd/maven-4.0.0.xsd">
        
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.o7planning</groupId>
    <artifactId>SbRestBasicAuthentication</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>SbRestBasicAuthentication</name>
    <description>Spring Boot +Rest + Basic Authentication</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
                
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
        

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

4- Security & AuthenticationEntryPoint

Die Sicherheitskonfiguration werden in der Klasse WebSecurityConfig geschrieben. Im Artikel fokusiere ich auf  "Wie holen Sie den Username in der Database" nicht. Deshalb erstellen Sie 2 stabilen UserName und speichern in dem Speicher. Der Benutzer grifft in die Ressourcen vom REST Service durch die Anmeldung mit einer der 2 username zu.
WebSecurityConfig.java
package org.o7planning.sbrestbasicauth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
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.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private AuthenticationEntryPoint authEntryPoint;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable();

		// All requests send to the Web Server request must be authenticated
		http.authorizeRequests().anyRequest().authenticated();

		// Use AuthenticationEntryPoint to authenticate user/password
		http.httpBasic().authenticationEntryPoint(authEntryPoint);
	}

	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		return bCryptPasswordEncoder;
	}

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		
		String password = "123";

		String encrytedPassword = this.passwordEncoder().encode(password);
		System.out.println("Encoded password of 123=" + encrytedPassword);
		
		
		InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> //
		mngConfig = auth.inMemoryAuthentication();

		// Defines 2 users, stored in memory.
		// ** Spring BOOT >= 2.x (Spring Security 5.x)
		// Spring auto add ROLE_
		UserDetails u1 = User.withUsername("tom").password(encrytedPassword).roles("USER").build();
		UserDetails u2 = User.withUsername("jerry").password(encrytedPassword).roles("USER").build();

		mngConfig.withUser(u1);
		mngConfig.withUser(u2);

		// If Spring BOOT < 2.x (Spring Security 4.x)):
		// Spring auto add ROLE_
		// mngConfig.withUser("tom").password("123").roles("USER");
		// mngConfig.withUser("jerry").password("123").roles("USER");
	}

}
Die Klasse AuthenticationEntryPointImpl weitert aus der Klasse BasicAuthenticationEntryPoint aus, Sie wird verwendet um zu prüfen, ob username/password anbei request gültig oder nicht ist
AuthenticationEntryPointImpl.java
package org.o7planning.sbrestbasicauth.auth;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class AuthenticationEntryPointImpl extends BasicAuthenticationEntryPoint {

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
			throws IOException, ServletException {
		response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName());
		response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
		PrintWriter writer = response.getWriter();
		writer.println("HTTP Status 401 - " + authEx.getMessage());
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		// RealmName appears in the login window (Firefox).
		setRealmName("o7planning");
		super.afterPropertiesSet();
	}

}

5- Model, DAO, Controller

Die Klasse Employee vertritt einen Mitarbeiter
Employee.java
package org.o7planning.sbrestbasicauth.model;

public class Employee {

    private String empNo;
    private String empName;
    private String position;

    public Employee() {

    }

    public Employee(String empNo, String empName, String position) {
        this.empNo = empNo;
        this.empName = empName;
        this.position = position;
    }

    public String getEmpNo() {
        return empNo;
    }

    public void setEmpNo(String empNo) {
        this.empNo = empNo;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    public String getPosition() {
        return position;
    }

    public void setPosition(String position) {
        this.position = position;
    }

}
Die Klasse EmployeeDAO wird durch @Repository annotiert um mit Spring zu informieren, dass es Spring BEAN ist. Diese Klasse schließt die Methode zum Abfragen der Liste der Mitarbeiter (employee), der Erstellung des Mitarbeiter, der Änderung und der Löschung der Information ein.
EmployeeDAO.java
package org.o7planning.sbrestbasicauth.dao;
 

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.o7planning.sbrestbasicauth.model.Employee;
import org.springframework.stereotype.Repository;

@Repository
public class EmployeeDAO {

    private static final Map<String, Employee> empMap = new HashMap<String, Employee>();

    static {
        initEmps();
    }

    private static void initEmps() {
        Employee emp1 = new Employee("E01", "Smith", "Clerk");
        Employee emp2 = new Employee("E02", "Allen", "Salesman");
        Employee emp3 = new Employee("E03", "Jones", "Manager");

        empMap.put(emp1.getEmpNo(), emp1);
        empMap.put(emp2.getEmpNo(), emp2);
        empMap.put(emp3.getEmpNo(), emp3);
    }

    public Employee getEmployee(String empNo) {
        return empMap.get(empNo);
    }

    public Employee addEmployee(Employee emp) {
        empMap.put(emp.getEmpNo(), emp);
        return emp;
    }

    public Employee updateEmployee(Employee emp) {
        empMap.put(emp.getEmpNo(), emp);
        return emp;
    }

    public void deleteEmployee(String empNo) {
        empMap.remove(empNo);
    }

    public List<Employee> getAllEmployees() {
        Collection<Employee> c = empMap.values();
        List<Employee> list = new ArrayList<Employee>();
        list.addAll(c);
        return list;
    }

}
Die Klasse MainRESTController wird durch @RestController annotiert um mit Spring zu informieren, dass es Spring Restful Controller ist,
MainRESTController.java
package org.o7planning.sbcrudrestful.controller;

import java.util.List;

import org.o7planning.sbcrudrestful.dao.EmployeeDAO;
import org.o7planning.sbcrudrestful.model.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController

public class MainRESTController {

    @Autowired
    private EmployeeDAO employeeDAO;

    @RequestMapping("/")
    @ResponseBody
    public String welcome() {
        return "Welcome to RestTemplate Example.";
    }

    // URL:
    // http://localhost:8080/SomeContextPath/employees
    // http://localhost:8080/SomeContextPath/employees.xml
    // http://localhost:8080/SomeContextPath/employees.json
    @RequestMapping(value = "/employees", //
            method = RequestMethod.GET, //
            produces = { MediaType.APPLICATION_JSON_VALUE, //
                    MediaType.APPLICATION_XML_VALUE })
    @ResponseBody
    public List<Employee> getEmployees() {
        List<Employee> list = employeeDAO.getAllEmployees();
        return list;
    }

    // URL:
    // http://localhost:8080/SomeContextPath/employee/{empNo}
    // http://localhost:8080/SomeContextPath/employee/{empNo}.xml
    // http://localhost:8080/SomeContextPath/employee/{empNo}.json
    @RequestMapping(value = "/employee/{empNo}", //
            method = RequestMethod.GET, //
            produces = { MediaType.APPLICATION_JSON_VALUE, //
                    MediaType.APPLICATION_XML_VALUE })
    @ResponseBody
    public Employee getEmployee(@PathVariable("empNo") String empNo) {
        return employeeDAO.getEmployee(empNo);
    }

    // URL:
    // http://localhost:8080/SomeContextPath/employee
    // http://localhost:8080/SomeContextPath/employee.xml
    // http://localhost:8080/SomeContextPath/employee.json

    @RequestMapping(value = "/employee", //
            method = RequestMethod.POST, //
            produces = { MediaType.APPLICATION_JSON_VALUE, //
                    MediaType.APPLICATION_XML_VALUE })
    @ResponseBody
    public Employee addEmployee(@RequestBody Employee emp) {

        System.out.println("(Service Side) Creating employee: " + emp.getEmpNo());

        return employeeDAO.addEmployee(emp);
    }

    // URL:
    // http://localhost:8080/SomeContextPath/employee
    // http://localhost:8080/SomeContextPath/employee.xml
    // http://localhost:8080/SomeContextPath/employee.json
    @RequestMapping(value = "/employee", //
            method = RequestMethod.PUT, //
            produces = { MediaType.APPLICATION_JSON_VALUE, //
                    MediaType.APPLICATION_XML_VALUE })
    @ResponseBody
    public Employee updateEmployee(@RequestBody Employee emp) {

        System.out.println("(Service Side) Editing employee: " + emp.getEmpNo());

        return employeeDAO.updateEmployee(emp);
    }

    // URL:
    // http://localhost:8080/SomeContextPath/employee/{empNo}
    @RequestMapping(value = "/employee/{empNo}", //
            method = RequestMethod.DELETE, //
            produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
    @ResponseBody
    public void deleteEmployee(@PathVariable("empNo") String empNo) {

        System.out.println("(Service Side) Deleting employee: " + empNo);

        employeeDAO.deleteEmployee(empNo);
    }

}

Erklären:

  • produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }
  • produces = { "application/json" , "application/xml" }
Das Attribut produces wird verwendet um zu regeln, ein URL erstellt (dem Benutzer zurückgeben) nur  die Daten mit einer Format. Z.B "application/json", "application/xml".

6- Die Applikation durchführen

Um die Applikation durchzuführen, klicken Sie die Rechtmaustaste aufs Project und wählen:
  • Run As/Spring Boot App
Die Applikation mit dem Browser prüfen
Mehr sehen