SpringBoot+SpringSecurity+jwt实现验证

下面我会提供一个基于Spring Boot、Spring Security 和 JSON Web Token(JWT)的认证示例。

一、什么是JWT

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种简单的、自包含的方式,用于在通过网络进行传输的两个实体之间安全传递信息。它被称为自包含是因为JWT包含了所有身份验证(Authentication)相关的信息,如用户id、用户名等,并且它被设计为在不需调用1次身份验证API的情况下即可用于多个操作(Authorization)。通常情况下,JWT在前端生成、存储并传输给后端进行校验,从而实现身份认证和授权。

JWT 的数据格式是一个由点(.)分割的三部分,这三部分是:

头部(Header):包含JWT的元数据,例如签名算法。
载荷(Payload):包含需要传输的数据。Payload 也被称为声明(claims)。
签名(Signature):由前两个部分的组合生成,从而确保信息在传输过程中没有被篡改。签名算法通常使用HMAC SHA256或RSA SHA256等加密算法。

对于JWT的生成和验证可以通过第三方库实现,例如Java中的jjwt、Python中的pyjwt等。

二、实现步骤

  1. 项目搭建并添加所需依赖

首先,我们在项目中添加 Spring Boot、Spring Security 和 jjwt 依赖:

<!-- SpringBoot -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.4.0</version>
</dependency>

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.4.0</version>
</dependency>

<!-- jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
  1. 创建用户实体类和DAO

我们需要有一个 User 实体类来存储用户相关信息,包括用户名、密码等。此外,还需要一个 UserDao 接口来实现用户操作的方法,在示例中,我们采用了基于 MySQL 数据库的数据存储方式。这里就不展开讲解实体类和DAO的实现了。

  1. 对密码进行加密

我们需要在用户注册时就对密码进行加密存储,而不是明文存储。在 Spring Security 中,我们可以通过实现PasswordEncoder接口来完成密码加密的工作。这里我们演示一下BCryptPasswordEncoder的使用方法:

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
  1. 创建JWT的Token生成工具类

创建一个 JWT 的 token 生成工具类,用于生成和解析 JWT 的 Token。这里采用JJWT库的API实现,上面我们已经在项目中添加了所需依赖。

示例代码如下(请注意,这里的 SECRET_KEY 的值需要根据实际情况设置):

@Component
public class JwtTokenUtil {
    private static final String SECRET_KEY = "12345678";

    public String generateToken(User user) {
        Claims claims = Jwts.claims().setSubject(user.getUsername()); // 将用户名称放进去
        claims.put("password", user.getPassword()); // 放入用户密码

        return Jwts.builder()
                .setClaims(claims) // 放入自定义信息
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) // 设置过期时间为1天
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 使用签名算法HS256生成签名
                .compact();
    }

    public Claims parseToken(String token) {
        try {
            return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            return null;
        }
    }
}
  1. 对用户进行身份验证

在Spring Security中,通常需要实现一个 UserDetailsService 接口以指定如何从数据库中获取用户信息,这样 Spring Security 就可以使用此接口来验证用户身份。

@Service
public class UserServiceImpl implements UserDetailsService {
    @Autowired
    private UserDao userDao;

    // 实现接口方法
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<User> user = userDao.findByUsername(username);
        if (user.isPresent()) {
            return new org.springframework.security.core.userdetails.User(
                    user.get().getUsername(),
                    user.get().getPassword(),
                    Collections.emptyList());
        }
        throw new UsernameNotFoundException("用户名或密码不正确");
    }
}
  1. 创建登录接口及登录成功后生成 Token

处理用户登录的请求,并在登录成功后生成 JWT Token 并返回给客户端。在示例中,我们采用了表单提交的方式进行登录验证。

这里是一个基本的登录控制器的实现:

@RestController
public class LoginController {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserServiceImpl userService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) throws Exception {
        // 构造 Authentication 对象
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());

        // 通过 AuthenticationManager 进行验证
        Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication); // 将用户信息保存在 SecurityContext 中

        // 生成 JWT Token
        String token = jwtTokenUtil.generateToken((User)authentication.getPrincipal());
        return ResponseEntity.ok(new LoginResponse(token));
    }
}
  1. 创建需要验证Token的API接口

创建需要进行 Token 验证的 API 接口。

示例代码如下:

@RestController
public class ApiController {
    @GetMapping("/hello")
    public ResponseEntity<?> hello() {
        return ResponseEntity.ok("Hello, 这是被身份验证保护的内容!");
    }
}
  1. 实现一个TokenAuthenticationFilter

自定义一个TokenAuthenticationFilter,用于从请求头中获取 jwt token 并进行校验。如果校验成功,则将已验证的 Authentication 对象放入 Spring Security 的 SecurityContext 中,便于后续的访问。

示例代码如下:

public class TokenAuthenticationFilter extends OncePerRequestFilter {
    private static final String HEADER = "Authorization";
    private static final String PREFIX = "Bearer ";

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserServiceImpl userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            String token = getToken(request);

            if (token != null && jwtTokenUtil.parseToken(token) != null) {
                String username = jwtTokenUtil.parseToken(token).getSubject();
                UserDetails userDetails = userService.loadUserByUsername(username);

                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                }
            }
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
        }
    }

    private String getToken(HttpServletRequest request) {
        String authHeader = request.getHeader(HEADER);

        if (authHeader != null && authHeader.startsWith(PREFIX)) {
            return authHeader.replace(PREFIX, "");
        }

        return null;
    }
}
  1. 配置Security,添加TokenAuthenticationFilter

在 Spring Security 的配置文件中添加TokenAuthenticationFilter。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserServiceImpl userService;

    @Autowired
    private TokenAuthenticationFilter tokenAuthenticationFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/login").permitAll() // 开放POST的/login接口
                .anyRequest().authenticated() // 其它接口都需要身份验证
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 不用session机制
                .and()
                .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling()
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); // 认证失败返回 401 状态码
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

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

    // 注册 TokenAuthenticationFilter
    @Bean
    public TokenAuthenticationFilter authenticationTokenFilterBean() {
        return new TokenAuthenticationFilter();
    }
}

三、实际应用

以上就是一个简单的 Spring Boot + Spring Security + JWT 实现身份验证的完整攻略。在实际应用中,可以依据需求进行安全策略的制定,加固授权机制等。

接下来给出一个基于上面实现的Token的简单API调用示例,为了方便,我们使用Postman进行调用。

示例一:调用 /login 接口并返回 Token

访问URL: http://localhost:8080/login

提交数据结构和示例:

{
    "username": "admin",
    "password": "123456"
}

返回Token:

{
    "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInBhc3N3b3JkIjoiJDJhJDA4JFlPcVV6dHVucDB5NG5qd1JCb0t0U3liWldRSlNTRkxiNG9EM3B5ZFFRUjdvU3drYWQzLlBycyIsImV4cCI6MTYwNzQzNjAzOH0.UgfjTTJFKuErdp0rJ-wVZowv0aT_3HbJ5BPn5xC2r-Y"
}

示例二:调用被保护的 /hello 接口

URL: http://localhost:8080/hello

Headers中添加下面这行:

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInBhc3N3b3JkIjoiJDJhJDA4JFlPcVV6dHVucDB5NG5qd1JCb0t0U3liWldRSlNTRkxiNG9EM3B5ZFFRUjdvU3drYWQzLlBycyIsImV4cCI6MTYwNzQzNjAzOH0.UgfjTTJFKuErdp0rJ-wVZowv0aT_3HbJ5BPn5xC2r-Y

返回内容:

Hello, 这是被身份验证保护的内容!

好了,至此,我们已经实现了一个简单的 Token 验证示例。希望对你能有所帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot+SpringSecurity+jwt实现验证 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • java实现简单学生成绩管理系统

    下面是“Java实现简单学生成绩管理系统”的完整攻略: 1. 系统简介 本学生成绩管理系统是用Java语言编写的一个简单的命令行应用程序,用于管理学生的考试成绩。系统可以实现以下功能: 添加学生信息 添加学生成绩 查询学生成绩 修改学生成绩 删除学生成绩 统计学生成绩 2. 思路分析 在实现该系统之前,需要对系统的流程进行分析和设计。系统主要分为两类数据,学…

    Java 2023年5月19日
    00
  • 使用maven shade插件解决项目版本冲突详解

    使用Maven Shade插件可以将所有的依赖包、类库和所需的资源打包到一个可执行的Jar文件中,从而避免在运行时出现项目版本冲突的问题。以下是使用Maven Shade插件解决项目版本冲突的完整攻略: 环境要求 JDK 1.8+ Maven 3.x+ 使用Maven Shade插件 在pom.xml文件中添加以下配置: <build> <…

    Java 2023年5月20日
    00
  • 举例讲解Java中Piped管道输入输出流的线程通信控制

    讲解Java中Piped管道输入输出流的线程通信控制的攻略如下: 什么是Piped管道输入输出流 Java中的Piped输入输出流是一种基于管道(pipe)的流方式。管道是一种常见的进程间通信(IPC)的方式。Piped输入输出流提供了一个可以连接线程的管道,其中一个线程通过写入实现输出流的数据传递,而另一个线程通过读取实现输入流的数据读取。 Piped的使…

    Java 2023年5月26日
    00
  • 五、读取HTTP请求头

    读取HTTP请求头是Web开发中非常重要的一步,因为HTTP请求头中包含了客户端(浏览器)访问我们网站时所发送的各种信息,如浏览器类型、语言、操作系统等,了解这些信息对于开发人员而言是非常必要的。下面以 Node.js 为例,讲解读取HTTP请求头的完整攻略。 一、获取HTTP请求头 在Node.js中,可以通过request.headers获取HTTP请求…

    Java 2023年6月15日
    00
  • 基于Java写minio客户端实现上传下载文件

    下面是基于Java写minio客户端实现上传下载文件的完整攻略。 1. 安装Minio服务器 首先,需要在本地或服务器上安装Minio服务器,具体步骤可以参考官方文档进行操作。一般来说,可以通过以下命令安装: $ wget https://dl.min.io/server/minio/release/linux-amd64/minio $ chmod +x …

    Java 2023年5月19日
    00
  • Java ForkJoin框架的原理及用法

    Java Fork/Join 框架 什么是 Java Fork/Join 框架 Java Fork/Join 框架是在 JDK7 中引入的,在 java.util.concurrent 包中,它提供了一种并行执行任务的方式,能够将一个大任务拆分成多个小任务进行处理,其中包括我们熟知的 MapReduce。 Fork/Join 的原理 Java Fork/Jo…

    Java 2023年5月26日
    00
  • Java Apache Commons报错“SAXException”的原因与解决方法

    “SAXException”是Java的Apache Commons类库中的一个异常,通常由以下原因之一引起: 无效的XML文档:如果XML文档无效,则可能会出现此错误。在这种情况下,需要检查XML文档以解决此问题。 无效的XML解析器:如果XML解析器无效,则可能会出现此错误。在这种情况下,需要检查XML解析器以解决此问题。 以下是两个实例: 例1 如果X…

    Java 2023年5月5日
    00
  • C#调用Java类的实现方法

    C#可以调用Java类的实现方法主要有以下三种: 使用Java Native Interface(JNI) JNI是Java所自带的一种机制,它提供了本地应用程序与Java虚拟机之间交互的能力。具体实现过程如下: 先编写Java类 将Java类编译成动态链接库 在C#中通过DllImport导入动态链接库(导入时需要显式指定Java虚拟机的路径) 调用Jav…

    Java 2023年5月19日
    00
合作推广
合作推广
分享本页
返回顶部