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日

相关文章

  • 利用Dojo和JSON建立无限级AJAX动态加载的功能模块树

    利用Dojo和JSON建立无限级AJAX动态加载的功能模块树是一项常见的Web开发技能,下面将对其进行详细讲解。 1. 什么是无限级AJAX动态加载的功能模块树 无限级AJAX动态加载的功能模块树,顾名思义,是一种可以无限级展开和收缩的树状结构。用户可以根据需要展开和收缩不同的分支,实现对模块的管理和查看。而利用AJAX技术,可以实现动态加载节点,带来更加流…

    Java 2023年5月26日
    00
  • springboot升级过程中踩坑定位分析记录 | 京东云技术团队

    作者:京东零售 李文龙 1.背景 “ 俗话说:为了修复一个小bug而引入了一个更大bug ” 因所负责的系统使用的spring框架版本5.1.5.RELEASE在线上出过一个偶发的小事故,最后定位为spring-context中的一个bug导致的。 为了修复此bug进行了spring版本的升级,最终定的版本为收银台团队使用的版本5.2.12.RELEASE,…

    Java 2023年4月30日
    00
  • JS验证URL函数 正则

    JS验证URL函数需要使用正则表达式,下面我来详细讲解一下验证URL的函数和正则表达式。 JS验证URL函数 首先,我们需要定义一个函数来验证URL是否合法。输入参数为URL字符串,返回值为布尔型,表示验证是否通过。以下是一个JavaScript函数来验证一个URL是否合法。 function isUrl(url) { /* 正则表达式 */ var re=…

    Java 2023年6月15日
    00
  • Spring框架读取property属性文件常用5种方法

    非常感谢你对Spring框架的关注。Spring框架支持多种读取属性文件的方式,其中最常用的五种方法有以下: 方法1:通过@Value注解获取property文件中的属性值 在Spring框架中,可以通过@Value注解快速获取配置文件中的属性和环境变量的值。首先要在Spring配置文件中进行配置,在标签中添加如下配置: <context:proper…

    Java 2023年5月31日
    00
  • Java线程安全性的作用是什么?

    作为一个Java网站的作者,我们需要讲解Java线程安全性的作用。Java线程安全性的作用是确保在多个线程同时访问同一资源的情况下,不会发生数据错误或竞争条件。更具体地说,线程安全是指在多个线程同时执行的情况下,程序的行为仍然是正确的。在Java中,我们可以使用不同的机制来实现线程安全,包括同步方法、同步块、volatile变量和原子类等等。 其中,同步方法…

    Java 2023年5月11日
    00
  • Java中如何动态创建接口的实现方法

    在Java中,可以使用动态代理技术来动态创建接口的实现方法。动态代理可以在运行时生成代理类,实现指定接口并将方法调用重定向到调用处理器上。 具体实现步骤如下: 步骤 1:编写接口 首先需要定义一个接口,用于指定我们需要动态实现的方法。 public interface MyInterface { void sayHello(String name); } 步…

    Java 2023年5月19日
    00
  • 一文带你弄懂Java中线程池的原理

    一文带你弄懂Java中线程池的原理 线程池的概念 线程池是指一组预先创建好的线程,可以被程序反复使用,用于执行多个任务。线程池的好处在于可以管理线程数量、重用线程以及减少线程创建和销毁的开销。 在Java中,线程池相关的类都位于java.util.concurrent包中。 线程池的组成 线程池主要由以下几个组成部分: 线程池管理器(ThreadPoolEx…

    Java 2023年5月19日
    00
  • Java Date类的使用案例详解

    Java Date类的使用案例详解 简介 Java中的Date类用于表示日期和时间。它被广泛用于处理时间和日期相关的应用程序。Date类的对象表示一个特定的瞬间,它包含了自从标准基准时间(称为“历元”)以来的毫秒数。 使用步骤 要使用Date类,需要依次进行以下步骤: 创建Date对象 使用Date对象进行操作 创建Date对象 可以使用以下方式创建Date…

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