Sicherer Spring Boot RESTful Service mit Auth0 JWT
View more Tutorials:
Angenommen, Sie haben ein RESTful API , das auf Spring Boot geschrieben wird. Die Client (die andere Applikation) kann auf Ihr RESTful API rufen und das Ergebnis erhalten
Allerdings können nicht alle RESTful API wegen ihrer Sentivitität public sein. Deshalb müssen Sie sie sichern. Es gibt einige Technik für Sie um Ihr RESTful API zu sichern:
- RESTful API mit Basic Authentication sichern.
- RESTful API mit JWT (JSON Web Token) sichern.
Es gibt einige Technik, die ich hier nicht listen. Aber grundsätzlich wenn Client auf einem gesicherten RESTful API rufen möchten, soll es ein Bedarf mit der beigefügten Informationen schicken (es kann username/password sein).
Mehr sehen
- TODO Link!
Der Aufruf vom Client auf einem public REST API ist einfach. Es ist gleich wie die folgende Illustration:

Falls REST API durch Basic Authentication gesichert wird, muss Client das String "username:password" mit dem Algorithmus Base64 verschlüsseln um ein Array byte zu erhalten und das Array auf Request Headers in jedem Aufruf auf REST API beifügen.

Für mit Auth0 gesicherten REST API ist der Aufruf ein bisschen mehr kompliziert.
- Der Schritt 1: Sie müssen request (die Anforderung) zur Anmeldung mit username/password schicken und eine Rückantwort bekommen, die ein auf Response Header beigefügten "Authorization String" ist.
- Der Schritt 2: Nachdem "Authorization String" verfügbar ist, füg es auf Request Header bei um nach REST API aufzurufen.

Auf Eclipse erstellen Sie das Projekt Spring Boot.


Deklarieren Sie die Bibliotheke Auth0 um das Projekt zu verwenden.
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.auth0/auth0 --> <dependency> <groupId>com.auth0</groupId> <artifactId>auth0</artifactId> <version>1.5.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.auth0/auth0-spring-security-api --> <dependency> <groupId>com.auth0</groupId> <artifactId>auth0-spring-security-api</artifactId> <version>1.0.0</version> </dependency>
Die volle Inhalt der File pom.xml:
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>com.example</groupId> <artifactId>SpringBootJWT</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>SpringBootJWT</name> <description>Spring Boot + Rest + JWT</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> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.auth0/auth0 --> <dependency> <groupId>com.auth0</groupId> <artifactId>auth0</artifactId> <version>1.5.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.auth0/auth0-spring-security-api --> <dependency> <groupId>com.auth0</groupId> <artifactId>auth0-spring-security-api</artifactId> <version>1.0.0</version> </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>
SpringBootJwtApplication.java
package org.o7planning.sbjwt; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBootJwtApplication { public static void main(String[] args) { SpringApplication.run(SpringBootJwtApplication.class, args); } }

Employee.java
package org.o7planning.sbjwt.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; } }
EmployeeDAO.java
package org.o7planning.sbjwt.dao; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.o7planning.sbjwt.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; } }
MainRESTController.java
package org.o7planning.sbjwt.controller; import java.util.List; import org.o7planning.sbjwt.dao.EmployeeDAO; import org.o7planning.sbjwt.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 Spring Boot + REST + JWT Example."; } @RequestMapping("/test") @ResponseBody public String test() { return "{greeting: 'Hello'}"; } // URL: // http://localhost:8080/employees @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/employee/{empNo} @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/employee @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/employee @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/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); } }
Die Applikation hat die Anmeldung-Funktion. Client kann eine Anmeldungsanforderung mit der Methode POST senden. Deshalb ist die Erstellung einer Anmeldungsseite nicht nötig. Stattdessen haben wir einen Filter . Wenn ein request mit einem Pfad /login wird es durch Filter behandelt.
Die request , die zum Controller gehen möchten, müssen sie diese Filter überwinden :

In dieser Unterricht werde ich für die Einfachheit 2 User (Benutzer) in die Speicherung erstellen. Client kann mit einem der 2 folgenden User anmelden:
- tom/123
- jerry/123

WebSecurityConfig.java
package org.o7planning.sbjwt.config; import org.o7planning.sbjwt.filter.JWTAuthenticationFilter; import org.o7planning.sbjwt.filter.JWTLoginFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; 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.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.authentication.UsernamePasswordAuthenticationFilter; @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() // No need authentication. .antMatchers("/").permitAll() // .antMatchers(HttpMethod.POST, "/login").permitAll() // .antMatchers(HttpMethod.GET, "/login").permitAll() // For Test on Browser // Need authentication. .anyRequest().authenticated() // .and() // // Add Filter 1 - JWTLoginFilter // .addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class) // // Add Filter 2 - JWTAuthenticationFilter // .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public BCryptPasswordEncoder passwordEncoder() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; } @Override protected void configure(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"); } }
Wenn ein Request mit dem Pfad /login nach Server geschickt, wird es durch JWTLoginFilter behandelt. Die Klasse wird username/password prüfen. Wenn gültig, wird ein Authorization String in Response Header zur Rückgabe für Client beigefügt.

JWTLoginFilter.java
package org.o7planning.sbjwt.filter; import java.io.IOException; import java.util.Collections; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.o7planning.sbjwt.service.TokenAuthenticationService; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter { public JWTLoginFilter(String url, AuthenticationManager authManager) { super(new AntPathRequestMatcher(url)); setAuthenticationManager(authManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { String username = request.getParameter("username"); String password = request.getParameter("password"); System.out.printf("JWTLoginFilter.attemptAuthentication: username/password= %s,%s", username, password); System.out.println(); return getAuthenticationManager() .authenticate(new UsernamePasswordAuthenticationToken(username, password, Collections.emptyList())); } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { System.out.println("JWTLoginFilter.successfulAuthentication:"); // Write Authorization to Headers of Response. TokenAuthenticationService.addAuthentication(response, authResult.getName()); String authorizationString = response.getHeader("Authorization"); System.out.println("Authorization String=" + authorizationString); } }
Die Klasse TokenAuthenticationService ist eine Utility Klasse, die "Authorization string" in Response Header schreibt um für Client zurückzugeben. Das Authorization String funktioniert in einer kurzer Zeit (10 Tage). Das heißt , dass Client nur ein mal anmeldet (login) und "das Authorization String " hat und kann es in die obengenannte Zeitraum verwenden. Wenn "das Authorization String" ist ausgelaufen, muss Client wieder login um das neue Authorization String zu schaffen
TokenAuthenticationService.java
package org.o7planning.sbjwt.service; import java.util.Collections; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; public class TokenAuthenticationService { static final long EXPIRATIONTIME = 864_000_000; // 10 days static final String SECRET = "ThisIsASecret"; static final String TOKEN_PREFIX = "Bearer"; static final String HEADER_STRING = "Authorization"; public static void addAuthentication(HttpServletResponse res, String username) { String JWT = Jwts.builder().setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME)) .signWith(SignatureAlgorithm.HS512, SECRET).compact(); res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT); } public static Authentication getAuthentication(HttpServletRequest request) { String token = request.getHeader(HEADER_STRING); if (token != null) { // parse the token. String user = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody() .getSubject(); return user != null ? new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList()) : null; } return null; } }
Um REST API aufrufen zu können, werden die Request (Anforderungen) "Authorization string" auf Request Header beigefügt. Die Klasse JWTAuthenticationFilter wird "Authorization string" prüfen, wenn gültig, wird die Anforderung (Request) bestätigt. Er kann zum Controller weiter gehen.

JWTAuthenticationFilter.java
package org.o7planning.sbjwt.filter; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.o7planning.sbjwt.service.TokenAuthenticationService; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.GenericFilterBean; public class JWTAuthenticationFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("JWTAuthenticationFilter.doFilter"); Authentication authentication = TokenAuthenticationService .getAuthentication((HttpServletRequest) servletRequest); SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(servletRequest, servletResponse); } }
Sie können den Browser benutzen um die Funktion Login zu prüfen und die Operation der Klasse JWTLoginFilter sehen. OK!, Greifen Sie in den folgenden Pfad auf dem Browser zu

Sehen Sie die Information, die aufs Fenster Console von Eclipse geschrieben werden:

JWTLoginFilter.attemptAuthentication: username/password= tom,123 JWTLoginFilter.successfulAuthentication: Authorization String=Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0b20iLCJleHAiOjE1MjA3OTQyNjF9.GP0p5Aaj0HNMShNEAPbieHPeYxeGJ_-lB8ahHr6dJvrs_pAoSgGNCj8bNzRYpi4H7cJ1xQ_DZwV1bMw6ihK2Mw JWTAuthenticationFilter.doFilter JWTAuthenticationFilter.doFilter
Ein Beispiel: die Verwendung der Klasse RestTemplate (Spring REST Client) ruft auf ein von Auth0 gesichertes REST API auf:
JWTClientExample.java
package org.o7planning.sbjwt.restclient; import java.util.Arrays; import java.util.List; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; public class JWTClientExample { static final String URL_LOGIN = "http://localhost:8080/login"; static final String URL_EMPLOYEES = "http://localhost:8080/employees"; // POST Login // @return "Authorization string". private static String postLogin(String username, String password) { // Request Header HttpHeaders headers = new HttpHeaders(); // Request Body MultiValueMap<String, String> parametersMap = new LinkedMultiValueMap<String, String>(); parametersMap.add("username", username); parametersMap.add("password", password); // Request Entity HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(parametersMap, headers); // RestTemplate RestTemplate restTemplate = new RestTemplate(); // POST Login ResponseEntity<String> response = restTemplate.exchange(URL_LOGIN, // HttpMethod.POST, requestEntity, String.class); HttpHeaders responseHeaders = response.getHeaders(); List<String> list = responseHeaders.get("Authorization"); return list == null || list.isEmpty() ? null : list.get(0); } private static void callRESTApi(String restUrl, String authorizationString) { // HttpHeaders HttpHeaders headers = new HttpHeaders(); // // Authorization string (JWT) // headers.set("Authorization", authorizationString); // headers.setAccept(Arrays.asList(new MediaType[] { MediaType.APPLICATION_JSON })); // Request to return JSON format headers.setContentType(MediaType.APPLICATION_JSON); // HttpEntity<String>: To get result as String. HttpEntity<String> entity = new HttpEntity<String>(headers); // RestTemplate RestTemplate restTemplate = new RestTemplate(); // Send request with GET method, and Headers. ResponseEntity<String> response = restTemplate.exchange(URL_EMPLOYEES, // HttpMethod.GET, entity, String.class); String result = response.getBody(); System.out.println(result); } public static void main(String[] args) { String username = "tom"; String password = "123"; String authorizationString = postLogin(username, password); System.out.println("Authorization String=" + authorizationString); // Call REST API: callRESTApi(URL_EMPLOYEES, authorizationString); } }

Mehr sehen