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 中可以通过以下步骤来实现: 创建一个包含指定数字的列表。 使用 Collections 类的 shuffle() 方法来打乱数字的顺序。 从列表中取出前几个数字。 下面是一个示例代码,展示了如何实现随机无重复数字的方法: import java.util.ArrayList; import java.util.Coll…

    Java 2023年5月26日
    00
  • Java获取时间差(天数差,小时差,分钟差)代码示例

    Java获取时间差(天数差,小时差,分钟差)的实现需要用到Java中的日期时间类:java.util.Date和java.time.LocalDateTime。以下是详细的攻略和两条代码示例: 获取时间差代码示例1 import java.time.LocalDateTime; import java.time.Duration; public class …

    Java 2023年5月20日
    00
  • Tomcat中使用ipv6地址的示例代码

    下面是Tomcat中使用IPv6地址的示例代码的攻略: 确认Tomcat版本 首先需要确认Tomcat的版本,因为不同版本的Tomcat对IPv6的支持可能会有所不同。确保使用的Tomcat版本是7.0或更高版本,这些版本都支持IPv6地址。 配置server.xml 编辑Tomcat的配置文件server.xml,在 <Connector> 元…

    Java 2023年5月19日
    00
  • Linux环境下的Java(JDBC)连接openGauss数据库实践记录

    Linux环境下的Java(JDBC)连接openGauss数据库实践记录 在Linux环境下,我们可以使用Java程序连接openGauss数据库进行数据操作。下面给出连接openGauss数据库的完整攻略。 步骤一:获取openGauss数据库连接驱动 我们需要下载openGauss数据库的JDBC驱动 jar 包,可以从openGauss官网https…

    Java 2023年5月20日
    00
  • struts中动态方法调用使用通配符

    在Struts框架中,可以通过动态方法调用使用通配符的方式,实现对请求URL的自动解析,从而找到对应的Action类及其方法进行处理。 以下是使用通配符的完整攻略: 1. 配置struts.xml 在struts.xml中,可以配置动态方法调用的命名空间及通配符等参数。如下所示: <package name="example" na…

    Java 2023年5月20日
    00
  • Mybatis-Plus 搭建与使用入门(小结)

    Mybatis-Plus 搭建与使用入门(小结) 本文介绍了如何使用 Mybatis-Plus 搭建一个基本的 CRUD 应用,并对其进行增强功能的使用。整个过程包含以下步骤: 1. 环境准备 为了使用 Mybatis-Plus,我们需要在项目中添加相关的依赖: <dependency> <groupId>com.baomidou&l…

    Java 2023年5月20日
    00
  • JSP使用过滤器防止SQL注入的简单实现

    下面我会详细讲解“JSP使用过滤器防止SQL注入的简单实现”的完整攻略。 1. 什么是SQL注入 SQL注入攻击是指攻击者通过在应用程序的输入参数中插入恶意的SQL语句,从而达到欺骗后端数据库服务器执行恶意SQL语句的目的。SQL注入攻击的攻击方式千变万化,最终目的都是为了绕过后端应用程序的验证机制,获得非法的访问权限,甚至掌控整个后端服务器。 2. 如何使…

    Java 2023年6月15日
    00
  • Java的Struts框架报错“TokenExpiredException”的原因与解决办法

    当使用Java的Struts框架时,可能会遇到“TokenExpiredException”错误。这个错误通常由以下原因之一起: 令牌过期:如果令牌过期,则可能会出现此错误。在这种情况下,需要重新生成令牌以解决此问题。 配置错误:如果配置文件中没有正确配置,则可能会出现此错误。在这种情况下,需要检查文件以解决此问题。 以下是两个实例: 例 1 如果令牌过期,…

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