首页 > Java > java教程 > 正文

使用 Spring Security、JWT 和 JDBC 模板在 Spring Boot 中实现基于令牌的身份验证

Susan Sarandon
发布: 2024-10-29 02:22:30
原创
926 人浏览过

代码参考的 Github 链接

介绍

在现代 Web 应用程序中,安全的用户身份验证至关重要。传统上,基于会话的身份验证已被广泛使用,但随着应用程序变得更加分布式和可扩展,基于令牌的身份验证提供了多种优势。

基于令牌的身份验证允许应用程序无状态,这意味着服务器不需要存储任何会话数据,使其非常适合可扩展的 RESTful API。

本教程将指导您使用 Spring Security 和 JDBC 模板在 Spring Boot 应用程序中实现 JWT(JSON Web Token)身份验证。

JWT(JSON Web Token)是一种紧凑的、URL 安全的方式来表示在两方之间传输的声明。它通常用于无状态身份验证,其中每个请求都使用签名令牌进行身份验证。

为什么选择 JWT 和基于令牌的身份验证?

无状态身份验证
JWT 令牌是独立的,直接在令牌有效负载中携带用户的身份验证信息,这减少了服务器内存使用并提高了可扩展性。

跨平台支持
令牌很容易在移动和 Web 应用程序中使用,因为它们可以安全地存储在客户端中(例如,本地存储或 cookie)。

安全
每个令牌都经过数字签名,确保其完整性并允许服务器验证它,而无需在每个请求上查询数据库。

你将学到什么

在本教程中,您将学习如何:

  1. 使用 Spring Security 设置 Spring Boot 应用程序。
  2. 使用 JDBC 模板实施基于 JWT 令牌的身份验证,以安全地管理用户和存储刷新令牌。
  3. 设置用于登录、访问令牌生成和刷新令牌处理的端点。

在本教程结束时,您将拥有一个安全、无状态的身份验证系统,该系统利用 Spring Boot 和 JWT 为您的应用程序提供无缝且可扩展的访问控制。


API 流程:

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template


技术要求:

  • Java 17 / 11 / 8
  • Spring Boot 3 / 2(带有 Spring Security、Spring Web)
  • jjwt-api 0.11.5
  • PostgreSQL/MySQL
  • Maven

1. 设置您的 Spring Boot 项目

使用 Spring Web 工具或您的开发工具(STS、Intellij 或任何 IDE)创建 Spring Boot 项目。

打开 pom.xml 并添加 Spring Security、JWT 和 JDBC 模板的依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
登录后复制
登录后复制
登录后复制
登录后复制

2.配置数据库、应用程序属性

在 src/main/resources 文件夹下,打开 application.properties 并添加以下配置。我将在本教程中使用 postgres 数据库。

spring.application.name= authmanager

server.port= 1001
servlet.context-path= /authmanager

database.username= postgres 
database.password= admin@123
database.driverClassName= org.postgresql.Driver
database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres
database.maxActive= 5
database.minIdle= 5
database.poolName= Authmanager Postgres Datasource

app.jwtSecret= ###############ib-Spring###############
app.jwtExpirationMs= 3600000
app.jwtRefreshExpirationMs= 86400000
登录后复制
登录后复制
登录后复制
登录后复制

3. 设置数据库表

为用户信息、角色、用户角色映射和刷新令牌定义一个简单的表结构:

CREATE SCHEMA IB;
-------------------------------------------------------------------------

create sequence users_uniqueid_seq START 1;

create table ib.users(
        uniqueid bigint not null default nextval('users_uniqueid_seq') PRIMARY KEY,
        email varchar(75),
        password varchar(200),
        username varchar(20)
);

insert into ib.users(email,password,username) values ('admin@ib.com','a$VcdzH8Q.o4KEo6df.XesdOmXdXQwT5ugNQvu1Pl0390rmfOeA1bhS','admin');

#(password = 12345678)
-------------------------------------------------------------------------

create sequence roles_id_seq START 1;

create table ib.roles(
        id int not null default nextval('roles_id_seq') PRIMARY KEY,
        name varchar(20)
);

INSERT INTO ib.roles(name) VALUES('ROLE_USER');
INSERT INTO ib.roles(name) VALUES('ROLE_MODERATOR');
INSERT INTO ib.roles(name) VALUES('ROLE_ADMIN');

-------------------------------------------------------------------------

create table ib.user_roles(
        user_uniqueid bigint not null,
        role_id int not null,
        primary key(user_uniqueid,role_id)
);
insert into ib.user_roles (user_uniqueid,role_id) values (1,3);
-------------------------------------------------------------------------

create sequence refresh_tokens_id_seq START 1;

create table ib.refresh_tokens(
        id bigint not null default nextval('refresh_tokens_id_seq') PRIMARY KEY,
        uniqueid bigint,
        token varchar(500) not null,
        expiryDate TIMESTAMP WITH TIME ZONE not null
);

-------------------------------------------------------------------------
登录后复制
登录后复制
登录后复制

4. 创建模型

让我们定义以下模型。
在 models 包中,创建以下 4 个文件:

模型/ERole.java

package com.security.authmanager.model;

public enum ERole {
    ROLE_USER,
    ROLE_MODERATOR,
    ROLE_ADMIN
}
登录后复制
登录后复制
登录后复制

model/Role.java

package com.security.authmanager.model;

public class Role {
    private Integer id;
    private ERole name;

    public Role() {
    }

    public Role(ERole name) {
        this.name = name;
    }

    //generate getters and setters
}
登录后复制
登录后复制
登录后复制

model/User.java

package com.security.authmanager.model;

import java.util.HashSet;
import java.util.Set;

public class User {
    private Long id;
    private String username;
    private String email;
    private String password;

    private Set<Role> roles = new HashSet<>();

    public User() {
    }

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

    //generate getters and setters
}

登录后复制
登录后复制
登录后复制
登录后复制

model/RefreshToken.java

package com.security.authmanager.model;

import java.util.HashSet;
import java.util.Set;

public class User {
    private Long id;
    private String username;
    private String email;
    private String password;

    private Set<Role> roles = new HashSet<>();

    public User() {
    }

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

    //generate getters and setters
}

登录后复制
登录后复制
登录后复制
登录后复制

5. 实现 CustomUserDetailsRepository 类

CustomUserDetailsRepository 类是一个 Spring @Repository,用于处理与 User 和 Role 实体相关的自定义数据库操作。它使用 JdbcTemplate 执行 SQL 查询来执行诸如获取用户、通过用户名或电子邮件检查用户是否存在、创建新用户以及获取角色等任务的 SQL 查询。

package com.security.authmanager.repository;

import com.security.authmanager.common.QueryConstants;
import com.security.authmanager.model.ERole;
import com.security.authmanager.model.Role;
import com.security.authmanager.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

@Repository
public class CustomUserDetailsRepository {

    private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsRepository.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public User fetchUserByUserName(String userName){
        try{
            return jdbcTemplate.query((conn) ->{
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_USER);
                ps.setString(1, userName.toUpperCase());
                return ps;
            },rs->{
                User user = null;
                Set<Role> roles = new HashSet<>();
                while (rs.next()) {
                    if (user == null) {
                        user = new User();
                        user.setEmail(rs.getString("email"));
                        user.setId(rs.getLong("uniqueid"));
                        user.setPassword(rs.getString("password"));
                        user.setUsername(rs.getString("username"));
                    }
                    Role role = new Role();
                    role.setId(rs.getInt("id"));
                    role.setName(ERole.valueOf(rs.getString("name")));
                    roles.add(role);
                }
                if (user != null) {
                    user.setRoles(roles);
                }
                return user;
            });
        }catch(Exception e){
            logger.error("Exception in fetchUserByUserName()",e);
            throw new RuntimeException(e);
        }
    }

    public boolean existsByUsername(String userName) {
        try{
              return jdbcTemplate.query((conn) -> {
                 final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_USERNAME);
                 ps.setString(1, userName.toUpperCase());
                 return ps;
             }, (rs,rownum) -> rs.getInt("count")).get(0)>0;
        }catch(Exception e){
            logger.error("Exception in existsByUsername()",e);
            throw new RuntimeException(e);
        }
    }

    public boolean existsByEmail(String email) {
        try{
            return jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_EMAIL);
                ps.setString(1, email.toUpperCase());
                return ps;
            }, (rs,rownum) -> rs.getInt("count")).get(0)>0;
        }catch(Exception e){
            logger.error("Exception in existsByEmail()",e);
            throw new RuntimeException(e);
        }
    }

    public Role findRoleByName(ERole eRole) {
        try{
            return jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_ROLE_BY_NAME);
                ps.setString(1, String.valueOf(eRole));
                return ps;
            }, rs -> {
                Role role=null;
                while(rs.next()){
                    role = new Role();
                    role.setName(ERole.valueOf(rs.getString("name")));
                    role.setId(rs.getInt("id"));
                }
                return role;
            });
        }catch(Exception e){
            logger.error("Exception in findRoleByName()",e);
            throw new RuntimeException(e);
        }
    }

    public void createUser(User user) {
        try(Connection conn = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection()){
            try (PreparedStatement userStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USERS,Statement.RETURN_GENERATED_KEYS)) {
                userStatement.setString(1, user.getEmail().toUpperCase());
                userStatement.setString(2, user.getPassword());
                userStatement.setString(3, user.getUsername().toUpperCase());
                userStatement.executeUpdate();
                // Retrieve generated userId
                try (ResultSet generatedKeys = userStatement.getGeneratedKeys()) {
                    if (generatedKeys.next()) {
                        Long userId = generatedKeys.getLong(1); // Assuming userId is of type VARCHAR
                        logger.info("gen userid {}",userId.toString());
                        user.setId(userId);
                    }
                }
            }
            if (user.getRoles() != null && !user.getRoles().isEmpty()) {
                try (PreparedStatement userRoleStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USER_ROLES)) {
                    for (Role role : user.getRoles()) {
                        userRoleStatement.setLong(1, user.getId());
                        userRoleStatement.setLong(2, role.getId());
                        userRoleStatement.executeUpdate();
                    }
                }
            }
        }catch(Exception e){
            logger.error("Exception in existsByEmail()",e);
            throw new RuntimeException(e);
        }
    }
}

登录后复制
登录后复制

此存储库执行基于 SQL 的自定义 CRUD 操作,用于管理数据库中的用户和角色数据。

主要功能:

  • 通过用户名获取用户并检索关联的角色。
  • 通过用户名或电子邮件检查用户是否存在。
  • 将新用户及其角色插入数据库。
  • 根据角色名称检索角色详细信息。

6. 实现 RefreshTokenRepository 类

RefreshTokenRepository 类是一个 Spring @Repository,用于处理与 RefreshToken 实体相关的数据库操作。它使用 Spring 的 JdbcTemplate 通过原始 SQL 查询与数据库进行交互,封装了保存、删除和检索刷新令牌的逻辑。

package com.security.authmanager.repository;

import com.security.authmanager.common.QueryConstants;
import com.security.authmanager.model.ERole;
import com.security.authmanager.model.RefreshToken;
import com.security.authmanager.model.Role;
import com.security.authmanager.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.Optional;

@Repository
public class RefreshTokenRepository {
    private static final Logger logger = LoggerFactory.getLogger(RefreshTokenRepository.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void deleteRefreshToken(RefreshToken refreshToken) {
        try{
            jdbcTemplate.update(QueryConstants.DELETE_REFRESH_TOKEN,(final PreparedStatement ps) ->{
                ps.setString(1,refreshToken.getToken());
            });
        }catch (Exception e){
            logger.error("Exception in deleteRefreshToken()",e);
            throw new RuntimeException(e);
        }
    }

    public int deleteRefreshTokenByUser(User user) {
        return 0;
    }

    public RefreshToken saveRefreshToken(RefreshToken refreshToken) {
        try{
            jdbcTemplate.update(QueryConstants.SAVE_REFRESH_TOKEN,(final PreparedStatement ps) ->{
                ps.setLong(1,refreshToken.getUser().getId());
                ps.setString(2,refreshToken.getToken());
                ps.setTimestamp(3, Timestamp.from(refreshToken.getExpiryDate()));
            });
        }catch (Exception e){
            logger.error("Exception in saveRefreshToken()",e);
            throw new RuntimeException(e);
        }
        return refreshToken;
    }

    public Optional<RefreshToken> findByToken(String token) {
        RefreshToken refreshToken = new RefreshToken();
        try{
            return Optional.ofNullable(jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FIND_BY_TOKEN);
                ps.setString(1, token);
                return ps;
            }, rs -> {
                User user = new User();
                while (rs.next()) {
                    refreshToken.setId(rs.getLong("id"));
                    refreshToken.setToken(rs.getString("token"));
                    refreshToken.setExpiryDate(rs.getTimestamp("expiryDate").toInstant());
                    user.setId(rs.getLong("uniqueid"));
                    user.setEmail(rs.getString("email"));
                    user.setUsername(rs.getString("username"));
                    refreshToken.setUser(user);
                }
                return refreshToken;
            }));
        }catch(Exception e){
            logger.error("Exception in findByToken()",e);
            throw new RuntimeException(e);
        }
    }
}

登录后复制
登录后复制

此存储库直接与数据库交互,以对 RefreshToken 实体进行 CRUD 操作。

主要功能:

  • 保存刷新令牌。
  • 通过令牌值检索刷新令牌。
  • 按令牌或用户删除刷新令牌。

7.配置Spring Security

  • WebSecurityConfig 将 Spring Security 配置为使用基于 JWT 的令牌身份验证。

  • 它为身份验证所需的各种组件定义了 Bean,例如 AuthTokenFilter(用于处理 JWT 令牌)、DaoAuthenticationProvider(用于检索用户详细信息和验证密码)和 BCryptPasswordEncoder(用于散列和比较密码)。

SecurityFilterChain 配置如何保护传入的 HTTP 请求:
- 允许访问某些公共路由(/auth/**、/test/**)。
- 保护所有其他路由,需要身份验证。
- 禁用会话管理(使系统无状态)。
- 配置过滤器来拦截和处理
的 JWT 令牌 用户身份验证。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
登录后复制
登录后复制
登录后复制
登录后复制

8. 实现UserDetailsImpl

该类主要在 Spring Security 中用于表示当前经过身份验证的用户。

当用户尝试登录时:

  1. Spring Security 调用 UserDetailsS​​ervice.loadUserByUsername() 来加载 数据库中的用户。
  2. 它使用用户的详细信息创建 UserDetailsImpl 的实例 (包括他们的角色)。
  3. Spring Security 使用这个 UserDetailsImpl 对象来 对用户进行身份验证并检查他们的权限 会议。

UserDetailsImpl 是您的 User 实体和 Spring Security 的内部身份验证和授权机制之间的桥梁。

spring.application.name= authmanager

server.port= 1001
servlet.context-path= /authmanager

database.username= postgres 
database.password= admin@123
database.driverClassName= org.postgresql.Driver
database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres
database.maxActive= 5
database.minIdle= 5
database.poolName= Authmanager Postgres Datasource

app.jwtSecret= ###############ib-Spring###############
app.jwtExpirationMs= 3600000
app.jwtRefreshExpirationMs= 86400000
登录后复制
登录后复制
登录后复制
登录后复制

以下是查询:(QueryConstants.java)

CREATE SCHEMA IB;
-------------------------------------------------------------------------

create sequence users_uniqueid_seq START 1;

create table ib.users(
        uniqueid bigint not null default nextval('users_uniqueid_seq') PRIMARY KEY,
        email varchar(75),
        password varchar(200),
        username varchar(20)
);

insert into ib.users(email,password,username) values ('admin@ib.com','a$VcdzH8Q.o4KEo6df.XesdOmXdXQwT5ugNQvu1Pl0390rmfOeA1bhS','admin');

#(password = 12345678)
-------------------------------------------------------------------------

create sequence roles_id_seq START 1;

create table ib.roles(
        id int not null default nextval('roles_id_seq') PRIMARY KEY,
        name varchar(20)
);

INSERT INTO ib.roles(name) VALUES('ROLE_USER');
INSERT INTO ib.roles(name) VALUES('ROLE_MODERATOR');
INSERT INTO ib.roles(name) VALUES('ROLE_ADMIN');

-------------------------------------------------------------------------

create table ib.user_roles(
        user_uniqueid bigint not null,
        role_id int not null,
        primary key(user_uniqueid,role_id)
);
insert into ib.user_roles (user_uniqueid,role_id) values (1,3);
-------------------------------------------------------------------------

create sequence refresh_tokens_id_seq START 1;

create table ib.refresh_tokens(
        id bigint not null default nextval('refresh_tokens_id_seq') PRIMARY KEY,
        uniqueid bigint,
        token varchar(500) not null,
        expiryDate TIMESTAMP WITH TIME ZONE not null
);

-------------------------------------------------------------------------
登录后复制
登录后复制
登录后复制

9. 实现UserDetailsS​​erviceImpl

UserDetailsS​​erviceImpl 类充当应用程序的数据库和 Spring Security 的身份验证过程之间的桥梁。它使用 CustomUserDetailsRepository 从数据库中获取用户详细信息,将 User 对象转换为 UserDetailsImpl (一种 Spring Security 友好的格式),并通过抛出异常来处理找不到用户的情况。该服务允许 Spring Security 根据用户的角色和权限对用户进行身份验证并管理授权。

package com.security.authmanager.model;

public enum ERole {
    ROLE_USER,
    ROLE_MODERATOR,
    ROLE_ADMIN
}
登录后复制
登录后复制
登录后复制

10. 过滤请求

AuthTokenFilter 类扩展了 Spring 的 OncePerRequestFilter,使其成为一个在请求链中处理每个 HTTP 请求一次的过滤器。它的主要作用是从请求中提取并验证 JWT(JSON Web 令牌),如果令牌有效,则在 Spring Security 的 SecurityContext 中设置用户的身份验证。

package com.security.authmanager.model;

public class Role {
    private Integer id;
    private ERole name;

    public Role() {
    }

    public Role(ERole name) {
        this.name = name;
    }

    //generate getters and setters
}
登录后复制
登录后复制
登录后复制

每次提出请求时:

  • 过滤器检查请求是否包含有效的 JWT。
  • 如果有效,它将通过在 SecurityContext 中设置 UsernamePasswordAuthenticationToken 来对用户进行身份验证。
  • 如果 JWT 无效或丢失,则不会设置身份验证,并且请求将作为未经身份验证的请求继续进行。

此过滤器可确保所有携带有效 JWT 令牌的请求都会自动进行身份验证,无需用户在登录后在后续请求中提供凭据(如用户名/密码)。

11. 实现RefreshTokenService

RefreshTokenService 类提供与在基于令牌的身份验证系统中管理刷新令牌相关的服务。刷新令牌用于在初始 JWT 过期后获取新的 JWT 令牌,而不需要用户重新进行身份验证。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
登录后复制
登录后复制
登录后复制
登录后复制

RefreshTokenService 处理刷新令牌的创建、验证和删除。它使用存储库从数据库中保存和获取令牌和用户。

此服务是身份验证系统的重要组成部分,其中刷新令牌用于使用户保持登录状态,而无需他们在 JWT 过期后再次提供凭据。

12. 实施 JWT 实用程序

JwtUtils 类是一个实用程序类,它处理 JWT(JSON Web Token)的创建、解析和验证,以在 Spring Boot 应用程序中进行身份验证。它使用 jjwt 库来处理 JWT。

spring.application.name= authmanager

server.port= 1001
servlet.context-path= /authmanager

database.username= postgres 
database.password= admin@123
database.driverClassName= org.postgresql.Driver
database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres
database.maxActive= 5
database.minIdle= 5
database.poolName= Authmanager Postgres Datasource

app.jwtSecret= ###############ib-Spring###############
app.jwtExpirationMs= 3600000
app.jwtRefreshExpirationMs= 86400000
登录后复制
登录后复制
登录后复制
登录后复制

JwtUtils 类负责生成、解析和验证 JWT 令牌。它使用密钥 (HMAC-SHA256) 安全地对令牌进行签名,并确保令牌只能由拥有正确密钥的各方读取或验证。

该类还从令牌中提取用户名,并在授予用户访问权限之前检查令牌是否有效。此实用程序对于在应用程序中维护安全、基于令牌的身份验证至关重要。

13. 处理认证异常

AuthEntryPointJwt 类实现 Spring Security 的 AuthenticationEntryPoint 接口。它处理发出未经授权的请求时发生的情况,通常是当用户尝试在没有有效身份验证的情况下访问受保护的资源时(例如,没有 JWT 或无效的 JWT)。

CREATE SCHEMA IB;
-------------------------------------------------------------------------

create sequence users_uniqueid_seq START 1;

create table ib.users(
        uniqueid bigint not null default nextval('users_uniqueid_seq') PRIMARY KEY,
        email varchar(75),
        password varchar(200),
        username varchar(20)
);

insert into ib.users(email,password,username) values ('admin@ib.com','a$VcdzH8Q.o4KEo6df.XesdOmXdXQwT5ugNQvu1Pl0390rmfOeA1bhS','admin');

#(password = 12345678)
-------------------------------------------------------------------------

create sequence roles_id_seq START 1;

create table ib.roles(
        id int not null default nextval('roles_id_seq') PRIMARY KEY,
        name varchar(20)
);

INSERT INTO ib.roles(name) VALUES('ROLE_USER');
INSERT INTO ib.roles(name) VALUES('ROLE_MODERATOR');
INSERT INTO ib.roles(name) VALUES('ROLE_ADMIN');

-------------------------------------------------------------------------

create table ib.user_roles(
        user_uniqueid bigint not null,
        role_id int not null,
        primary key(user_uniqueid,role_id)
);
insert into ib.user_roles (user_uniqueid,role_id) values (1,3);
-------------------------------------------------------------------------

create sequence refresh_tokens_id_seq START 1;

create table ib.refresh_tokens(
        id bigint not null default nextval('refresh_tokens_id_seq') PRIMARY KEY,
        uniqueid bigint,
        token varchar(500) not null,
        expiryDate TIMESTAMP WITH TIME ZONE not null
);

-------------------------------------------------------------------------
登录后复制
登录后复制
登录后复制

AuthEntryPointJwt 类是一个自定义入口点,它拦截未经授权的访问尝试并返回结构化 JSON 响应,其中包含 401 错误代码、错误消息以及有关请求的详细信息。
它会记录错误,并在身份验证失败时向客户端提供清晰、用户友好的响应。

14. 为Controller创建有效负载类

以下是我们的 RestAPI 的有效负载:

1。要求:

- LoginRequest.java :

package com.security.authmanager.model;

public enum ERole {
    ROLE_USER,
    ROLE_MODERATOR,
    ROLE_ADMIN
}
登录后复制
登录后复制
登录后复制

- SignupRequest.java :

package com.security.authmanager.model;

public class Role {
    private Integer id;
    private ERole name;

    public Role() {
    }

    public Role(ERole name) {
        this.name = name;
    }

    //generate getters and setters
}
登录后复制
登录后复制
登录后复制

- TokenRefreshRequest.java :

package com.security.authmanager.model;

import java.util.HashSet;
import java.util.Set;

public class User {
    private Long id;
    private String username;
    private String email;
    private String password;

    private Set<Role> roles = new HashSet<>();

    public User() {
    }

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

    //generate getters and setters
}

登录后复制
登录后复制
登录后复制
登录后复制

2。回复:

- JwtResponse.java

package com.security.authmanager.model;

import java.util.HashSet;
import java.util.Set;

public class User {
    private Long id;
    private String username;
    private String email;
    private String password;

    private Set<Role> roles = new HashSet<>();

    public User() {
    }

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

    //generate getters and setters
}

登录后复制
登录后复制
登录后复制
登录后复制

- MessageResponse.java

package com.security.authmanager.repository;

import com.security.authmanager.common.QueryConstants;
import com.security.authmanager.model.ERole;
import com.security.authmanager.model.Role;
import com.security.authmanager.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

@Repository
public class CustomUserDetailsRepository {

    private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsRepository.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public User fetchUserByUserName(String userName){
        try{
            return jdbcTemplate.query((conn) ->{
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_USER);
                ps.setString(1, userName.toUpperCase());
                return ps;
            },rs->{
                User user = null;
                Set<Role> roles = new HashSet<>();
                while (rs.next()) {
                    if (user == null) {
                        user = new User();
                        user.setEmail(rs.getString("email"));
                        user.setId(rs.getLong("uniqueid"));
                        user.setPassword(rs.getString("password"));
                        user.setUsername(rs.getString("username"));
                    }
                    Role role = new Role();
                    role.setId(rs.getInt("id"));
                    role.setName(ERole.valueOf(rs.getString("name")));
                    roles.add(role);
                }
                if (user != null) {
                    user.setRoles(roles);
                }
                return user;
            });
        }catch(Exception e){
            logger.error("Exception in fetchUserByUserName()",e);
            throw new RuntimeException(e);
        }
    }

    public boolean existsByUsername(String userName) {
        try{
              return jdbcTemplate.query((conn) -> {
                 final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_USERNAME);
                 ps.setString(1, userName.toUpperCase());
                 return ps;
             }, (rs,rownum) -> rs.getInt("count")).get(0)>0;
        }catch(Exception e){
            logger.error("Exception in existsByUsername()",e);
            throw new RuntimeException(e);
        }
    }

    public boolean existsByEmail(String email) {
        try{
            return jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_EMAIL);
                ps.setString(1, email.toUpperCase());
                return ps;
            }, (rs,rownum) -> rs.getInt("count")).get(0)>0;
        }catch(Exception e){
            logger.error("Exception in existsByEmail()",e);
            throw new RuntimeException(e);
        }
    }

    public Role findRoleByName(ERole eRole) {
        try{
            return jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_ROLE_BY_NAME);
                ps.setString(1, String.valueOf(eRole));
                return ps;
            }, rs -> {
                Role role=null;
                while(rs.next()){
                    role = new Role();
                    role.setName(ERole.valueOf(rs.getString("name")));
                    role.setId(rs.getInt("id"));
                }
                return role;
            });
        }catch(Exception e){
            logger.error("Exception in findRoleByName()",e);
            throw new RuntimeException(e);
        }
    }

    public void createUser(User user) {
        try(Connection conn = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection()){
            try (PreparedStatement userStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USERS,Statement.RETURN_GENERATED_KEYS)) {
                userStatement.setString(1, user.getEmail().toUpperCase());
                userStatement.setString(2, user.getPassword());
                userStatement.setString(3, user.getUsername().toUpperCase());
                userStatement.executeUpdate();
                // Retrieve generated userId
                try (ResultSet generatedKeys = userStatement.getGeneratedKeys()) {
                    if (generatedKeys.next()) {
                        Long userId = generatedKeys.getLong(1); // Assuming userId is of type VARCHAR
                        logger.info("gen userid {}",userId.toString());
                        user.setId(userId);
                    }
                }
            }
            if (user.getRoles() != null && !user.getRoles().isEmpty()) {
                try (PreparedStatement userRoleStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USER_ROLES)) {
                    for (Role role : user.getRoles()) {
                        userRoleStatement.setLong(1, user.getId());
                        userRoleStatement.setLong(2, role.getId());
                        userRoleStatement.executeUpdate();
                    }
                }
            }
        }catch(Exception e){
            logger.error("Exception in existsByEmail()",e);
            throw new RuntimeException(e);
        }
    }
}

登录后复制
登录后复制

- TokenRefreshResponse.java

package com.security.authmanager.repository;

import com.security.authmanager.common.QueryConstants;
import com.security.authmanager.model.ERole;
import com.security.authmanager.model.RefreshToken;
import com.security.authmanager.model.Role;
import com.security.authmanager.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.Optional;

@Repository
public class RefreshTokenRepository {
    private static final Logger logger = LoggerFactory.getLogger(RefreshTokenRepository.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void deleteRefreshToken(RefreshToken refreshToken) {
        try{
            jdbcTemplate.update(QueryConstants.DELETE_REFRESH_TOKEN,(final PreparedStatement ps) ->{
                ps.setString(1,refreshToken.getToken());
            });
        }catch (Exception e){
            logger.error("Exception in deleteRefreshToken()",e);
            throw new RuntimeException(e);
        }
    }

    public int deleteRefreshTokenByUser(User user) {
        return 0;
    }

    public RefreshToken saveRefreshToken(RefreshToken refreshToken) {
        try{
            jdbcTemplate.update(QueryConstants.SAVE_REFRESH_TOKEN,(final PreparedStatement ps) ->{
                ps.setLong(1,refreshToken.getUser().getId());
                ps.setString(2,refreshToken.getToken());
                ps.setTimestamp(3, Timestamp.from(refreshToken.getExpiryDate()));
            });
        }catch (Exception e){
            logger.error("Exception in saveRefreshToken()",e);
            throw new RuntimeException(e);
        }
        return refreshToken;
    }

    public Optional<RefreshToken> findByToken(String token) {
        RefreshToken refreshToken = new RefreshToken();
        try{
            return Optional.ofNullable(jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FIND_BY_TOKEN);
                ps.setString(1, token);
                return ps;
            }, rs -> {
                User user = new User();
                while (rs.next()) {
                    refreshToken.setId(rs.getLong("id"));
                    refreshToken.setToken(rs.getString("token"));
                    refreshToken.setExpiryDate(rs.getTimestamp("expiryDate").toInstant());
                    user.setId(rs.getLong("uniqueid"));
                    user.setEmail(rs.getString("email"));
                    user.setUsername(rs.getString("username"));
                    refreshToken.setUser(user);
                }
                return refreshToken;
            }));
        }catch(Exception e){
            logger.error("Exception in findByToken()",e);
            throw new RuntimeException(e);
        }
    }
}

登录后复制
登录后复制

15. 创建 Rest API 控制器类

- AuthController.java

AuthController 类是一个 Spring @RestController,负责处理应用程序中与身份验证相关的端点。它提供用户登录、注册和令牌刷新操作的端点。

主要功能:

  • 用户使用 JWT 登录并刷新令牌生成。
  • 使用有效的刷新令牌进行令牌刷新。
  • 用户注册以及验证和角色分配。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
登录后复制
登录后复制
登录后复制
登录后复制

- TestController.java

TestController 类是一个 Spring @RestController,它提供了多个端点来测试基于用户角色的访问控制。它演示了如何使用 Spring Security 基于角色的授权来限制对应用程序某些部分的访问。

主要功能:

  • 任何人都可以访问的公共访问端点。
  • 基于用户角色(用户、主持人、管理员)限制访问的特定于角色的端点。
spring.application.name= authmanager

server.port= 1001
servlet.context-path= /authmanager

database.username= postgres 
database.password= admin@123
database.driverClassName= org.postgresql.Driver
database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres
database.maxActive= 5
database.minIdle= 5
database.poolName= Authmanager Postgres Datasource

app.jwtSecret= ###############ib-Spring###############
app.jwtExpirationMs= 3600000
app.jwtRefreshExpirationMs= 86400000
登录后复制
登录后复制
登录后复制
登录后复制

16. 测试 API

1。注册为模组和用户。 (登录)

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

2。登录以获取访问令牌。

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

3。获取刷新令牌 API。

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

4。通过传递访问令牌来测试用户访问。

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

5。通过传递访问令牌来测试 mod 访问。

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

6。通过传递相同的访问令牌来测试管理员访问权限。

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

未经授权,因为该用户没有管理员访问权限(用户只有 mod 和用户角色)

快乐学习!又见面了。?

以上是使用 Spring Security、JWT 和 JDBC 模板在 Spring Boot 中实现基于令牌的身份验证的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!