Spring Boot - OAuth2 với JWT

Trong chương này, bạn sẽ tìm hiểu chi tiết về các cơ chế Spring Boot Security và OAuth2 với JWT.

Máy chủ ủy quyền

Máy chủ ủy quyền là một thành phần kiến ​​trúc tối cao cho Bảo mật API Web. Máy chủ ủy quyền hoạt động một điểm ủy quyền tập trung cho phép các ứng dụng và điểm cuối HTTP của bạn xác định các tính năng của ứng dụng của bạn.

Máy chủ tài nguyên

Máy chủ tài nguyên là ứng dụng cung cấp mã thông báo truy cập cho máy khách để truy cập Điểm cuối HTTP của Máy chủ tài nguyên. Nó là tập hợp các thư viện chứa các Điểm cuối HTTP, tài nguyên tĩnh và các trang web Động.

OAuth2

OAuth2 là một khung ủy quyền cho phép ứng dụng Web Security truy cập các tài nguyên từ máy khách. Để xây dựng ứng dụng OAuth2, chúng ta cần tập trung vào Loại cấp quyền (Mã ủy quyền), ID khách hàng và bí mật của ứng dụng khách.

 

Mã thông báo JWT

Mã thông báo JWT là Mã thông báo web JSON, được sử dụng để đại diện cho các tuyên bố được bảo mật giữa hai bên. Bạn có thể tìm hiểu thêm về mã thông báo JWT tại www.jwt.io/ .

Bây giờ, chúng ta sẽ xây dựng một ứng dụng OAuth2 cho phép sử dụng Máy chủ ủy quyền, Máy chủ tài nguyên với sự trợ giúp của Mã thông báo JWT.

Bạn có thể sử dụng các bước sau để triển khai Spring Boot Security với mã thông báo JWT bằng cách truy cập cơ sở dữ liệu.

Đầu tiên, chúng ta cần thêm các phần phụ thuộc sau vào tệp cấu hình bản dựng của mình.

Người dùng Maven có thể thêm các phần phụ thuộc sau vào tệp pom.xml của bạn.

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

<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>org.springframework.security.oauth</groupId>
   <artifactId>spring-security-oauth2</artifactId>
</dependency>

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

<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</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>

Người dùng Gradle có thể thêm các phần phụ thuộc sau vào tệp build.gradle.

compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')

compile("org.springframework.security.oauth:spring-security-oauth2")
compile('org.springframework.security:spring-security-jwt')
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("com.h2database:h2:1.4.191")

Ở đâu,

  • Spring Boot Starter Security - Thực hiện An ninh mùa xuân

  • Spring Security OAuth2 - Triển khai cấu trúc OAUTH2 để kích hoạt Máy chủ Ủy quyền và Máy chủ Tài nguyên.

  • Spring Security JWT - Tạo mã thông báo JWT để bảo mật web

  • Spring Boot Starter JDBC - Truy cập cơ sở dữ liệu để đảm bảo người dùng có sẵn sàng hay không.

  • Spring Boot Starter Web - Viết các điểm cuối HTTP.

  • H2 Database - Lưu trữ thông tin người dùng để xác thực và ủy quyền.

Tệp cấu hình bản dựng hoàn chỉnh được cung cấp bên dưới.

<?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.tutorialspoint</groupId>
   <artifactId>websecurityapp</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>websecurityapp</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.9.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-jdbc</artifactId>
      </dependency>
      
      <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>org.springframework.security.oauth</groupId>
         <artifactId>spring-security-oauth2</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.security</groupId>
         <artifactId>spring-security-jwt</artifactId>
      </dependency>
      
      <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</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>

Gradle – build.gradle

buildscript {
   ext {
      springBootVersion = '1.5.9.RELEASE'
   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.tutorialspoint'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
   mavenCentral()
}

dependencies {
   compile('org.springframework.boot:spring-boot-starter-security')
   compile('org.springframework.boot:spring-boot-starter-web')
   testCompile('org.springframework.boot:spring-boot-starter-test')
   testCompile('org.springframework.security:spring-security-test')
   compile("org.springframework.security.oauth:spring-security-oauth2")
   compile('org.springframework.security:spring-security-jwt')
   compile("org.springframework.boot:spring-boot-starter-jdbc")
   compile("com.h2database:h2:1.4.191")  
}

Bây giờ, trong ứng dụng Spring Boot chính, hãy thêm chú thích @EnableAuthorizationServer và @EnableResourceServer để hoạt động như một máy chủ Auth và Máy chủ tài nguyên trong cùng một ứng dụng.

Ngoài ra, bạn có thể sử dụng mã sau để viết một điểm cuối HTTP đơn giản để truy cập API với Spring Security bằng cách sử dụng JWT Token.

package com.tutorialspoint.websecurityapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
@RestController
public class WebsecurityappApplication {
   public static void main(String[] args) {
      SpringApplication.run(WebsecurityappApplication.class, args);
   }
   @RequestMapping(value = "/products")
   public String getProductName() {
      return "Honey";   
   }
}

Sử dụng mã sau để xác định lớp POJO để lưu trữ thông tin Người dùng để xác thực.

package com.tutorialspoint.websecurityapp;

import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;

public class UserEntity {
   private String username;
   private String password;
   private Collection<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
   
   public String getPassword() {
      return password;
   }
   public void setPassword(String password) {
      this.password = password;
   }
   public Collection<GrantedAuthority> getGrantedAuthoritiesList() {
      return grantedAuthoritiesList;
   }
   public void setGrantedAuthoritiesList(Collection<GrantedAuthority> grantedAuthoritiesList) {
      this.grantedAuthoritiesList = grantedAuthoritiesList;
   }
   public String getUsername() {
      return username;
   }
   public void setUsername(String username) {
      this.username = username;
   }
}

Bây giờ, sử dụng đoạn mã sau và xác định lớp CustomUser mở rộng lớp org.springframework.security.core.userdetails.User để xác thực Spring Boot.

package com.tutorialspoint.websecurityapp;

import org.springframework.security.core.userdetails.User;

public class CustomUser extends User {
   private static final long serialVersionUID = 1L;
   public CustomUser(UserEntity user) {
      super(user.getUsername(), user.getPassword(), user.getGrantedAuthoritiesList());
   }
}

Bạn có thể tạo lớp @Repository để đọc thông tin Người dùng từ cơ sở dữ liệu và gửi thông tin đó đến Dịch vụ người dùng tùy chỉnh, đồng thời thêm quyền được cấp “ROLE_SYSTEMADMIN”.

package com.tutorialspoint.websecurityapp;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Repository;

@Repository
public class OAuthDao {
   @Autowired
   private JdbcTemplate jdbcTemplate;

   public UserEntity getUserDetails(String username) {
      Collection<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
      String userSQLQuery = "SELECT * FROM USERS WHERE USERNAME=?";
      List<UserEntity> list = jdbcTemplate.query(userSQLQuery, new String[] { username },
         (ResultSet rs, int rowNum) -> {
         
         UserEntity user = new UserEntity();
         user.setUsername(username);
         user.setPassword(rs.getString("PASSWORD"));
         return user;
      });
      if (list.size() > 0) {
         GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_SYSTEMADMIN");
         grantedAuthoritiesList.add(grantedAuthority);
         list.get(0).setGrantedAuthoritiesList(grantedAuthoritiesList);
         return list.get(0);
      }
      return null;
   }
}

Bạn có thể tạo lớp dịch vụ chi tiết Người dùng tùy chỉnh mở rộng org.springframework.security.core.userdetails.UserDetailsService để gọi lớp kho lưu trữ DAO như được hiển thị.

package com.tutorialspoint.websecurityapp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomDetailsService implements UserDetailsService {
   @Autowired
   OAuthDao oauthDao;

   @Override
   public CustomUser loadUserByUsername(final String username) throws UsernameNotFoundException {
      UserEntity userEntity = null;
      try {
         userEntity = oauthDao.getUserDetails(username);
         CustomUser customUser = new CustomUser(userEntity);
         return customUser;
      } catch (Exception e) {
         e.printStackTrace();
         throw new UsernameNotFoundException("User " + username + " was not found in the database");
      }
   }
}

Tiếp theo, tạo một lớp @configuration để kích hoạt Bảo mật Web, xác định bộ mã hóa Mật khẩu (BCryptPasswordEncoder) và xác định bean AuthenticationManager. Lớp cấu hình bảo mật nên mở rộng lớp WebSecurityConfigurerAdapter.

package com.tutorialspoint.websecurityapp;

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.builders.WebSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
   @Autowired
   private CustomDetailsService customDetailsService;

   @Bean
   public PasswordEncoder encoder() {
      return new BCryptPasswordEncoder();
   }
   @Override
   @Autowired
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(customDetailsService).passwordEncoder(encoder());
   }
   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests().anyRequest().authenticated().and().sessionManagement()
         .sessionCreationPolicy(SessionCreationPolicy.NEVER);
   }
   @Override
   public void configure(WebSecurity web) throws Exception {
      web.ignoring();
   }
   @Override
   @Bean
   public AuthenticationManager authenticationManagerBean() throws Exception {
      return super.authenticationManagerBean();
   }
}

Bây giờ, xác định lớp Cấu hình OAuth2 để thêm ID ứng dụng khách, Bí mật ứng dụng khách, Xác định JwtAccessTokenConverter, khóa Riêng tư và khóa Công khai cho khóa người ký mã thông báo và khóa trình xác minh, đồng thời định cấu hình ClientDetailsServiceConfigurer để có hiệu lực Mã thông báo với phạm vi.

package com.tutorialspoint.websecurityapp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
   private String clientid = "tutorialspoint";
   private String clientSecret = "my-secret-key";
   private String privateKey = "private key";
   private String publicKey = "public key";

   @Autowired
   @Qualifier("authenticationManagerBean")
   private AuthenticationManager authenticationManager;
   
   @Bean
   public JwtAccessTokenConverter tokenEnhancer() {
      JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
      converter.setSigningKey(privateKey);
      converter.setVerifierKey(publicKey);
      return converter;
   }
   @Bean
   public JwtTokenStore tokenStore() {
      return new JwtTokenStore(tokenEnhancer());
   }
   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore())
      .accessTokenConverter(tokenEnhancer());
   }
   @Override
   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
      security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
   }
   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      clients.inMemory().withClient(clientid).secret(clientSecret).scopes("read", "write")
         .authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(20000)
         .refreshTokenValiditySeconds(20000);

   }
}

Bây giờ, hãy tạo khóa riêng và khóa công khai bằng cách sử dụng openssl.

Bạn có thể sử dụng các lệnh sau để tạo khóa cá nhân.

openssl genrsa -out jwt.pem 2048
openssl rsa -in jwt.pem

Bạn có thể sử dụng Để tạo khóa công khai, hãy sử dụng các lệnh dưới đây.

openssl rsa -in jwt.pem -pubout

Đối với phiên bản Spring Boot sau bản phát hành 1.5, hãy thêm thuộc tính dưới đây vào tệp application.properties của bạn để xác định thứ tự bộ lọc Tài nguyên OAuth2.

security.oauth2.resource.filter-order=3

Người dùng tệp YAML có thể thêm thuộc tính dưới đây vào tệp YAML.

security:
   oauth2:
      resource:
         filter-order: 3

Bây giờ, tạo tệp schema.sql và data.sql trong tài nguyên classpath src/main/resources/directory để kết nối ứng dụng với cơ sở dữ liệu H2.

Tệp schema.sql như được hiển thị:

CREATE TABLE USERS (ID INT PRIMARY KEY, USERNAME VARCHAR(45), PASSWORD VARCHAR(60));

Tệp data.sql như được hiển thị:

INSERT INTO USERS (ID, USERNAME,PASSWORD) VALUES (
   1, '[email protected]','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG');

INSERT INTO USERS (ID, USERNAME,PASSWORD) VALUES (
   2, '[email protected]','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG');

Note - Mật khẩu nên được lưu trữ ở định dạng Bcrypt Encoder trong bảng cơ sở dữ liệu.

Bạn có thể tạo tệp JAR thực thi và chạy ứng dụng Spring Boot bằng cách sử dụng các lệnh Maven hoặc Gradle sau.

Đối với Maven, bạn có thể sử dụng lệnh dưới đây:

mvn clean install

Sau khi “XÂY DỰNG THÀNH CÔNG”, bạn có thể tìm thấy tệp JAR trong thư mục đích.

Đối với Gradle, bạn có thể sử dụng lệnh như sau:

gradle clean build

Sau khi “XÂY DỰNG THÀNH CÔNG”, bạn có thể tìm thấy tệp JAR trong thư mục build / libs.

Bây giờ, chạy tệp JAR bằng cách sử dụng lệnh hiển thị ở đây -

java –jar <JARFILE>

Ứng dụng được khởi động trên cổng Tomcat 8080.

Bây giờ, hãy nhấn vào URL của phương thức POST qua POSTMAN để nhận mã thông báo OAUTH2.

http://localhost:8080/oauth/token

Bây giờ, hãy thêm Tiêu đề Yêu cầu như sau:

  • Authorization - Xác thực cơ bản với Id khách hàng và bí mật khách hàng của bạn.

  • Content Type - application / x-www-form-urlencoded

Bây giờ, thêm các Tham số Yêu cầu như sau:

  • Grant_type = mật khẩu
  • tên người dùng = tên người dùng của bạn
  • password = mật khẩu của bạn

Bây giờ, hãy nhấn vào API và nhận access_token như được hiển thị -

Bây giờ, nhấn API máy chủ tài nguyên với mã thông báo truy cập Bearer trong Tiêu đề yêu cầu như được hiển thị.

Sau đó, bạn có thể thấy kết quả như hình dưới đây: