在 Spring Boot 中使用 Spring Security + JWT + MySQL 实现基于 Token 的身份认证

在 Spring Boot 中使用 Spring Security + JWT + MySQL 实现基于 Token 的身份认证

一、引言

在现代Web应用中,安全是一个核心考虑因素。随着微服务架构和前后端分离模式的流行,传统的会话管理(基于Cookie和Session)已不再满足需求。基于Token的身份认证机制因其无状态、可扩展性高和适用于分布式系统等优点,逐渐成为主流。本文将介绍如何在Spring Boot应用中整合Spring Security、JWT和MySQL,实现基于Token的身份认证机制。

二、环境搭建

1、第一步:引入依赖

在Spring Boot项目中,首先需要引入必要的依赖。以下是pom.xml中需要添加的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

2、第二步:配置MySQL数据库

创建一个名为login_system的数据库,并在application.properties中配置数据库连接信息:

spring.datasource.url=jdbc:mysql://localhost:3306/login_system
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update

三、实现身份认证

三、实现身份认证

1、定义实体和数据访问层

1.1、实体类定义

首先,定义UserRole实体类,并在它们之间建立多对多关系。使用JPA注解来标注这些类和字段。

User实体:

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

import java.util.Set;

@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @Column(nullable = false, unique = true)
    private String username;
    @Column(nullable = false, unique = true)
    private String email;
    @Column(nullable = false)
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "users_roles",
               joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
               inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    private Set<Role> roles;
}

Role实体:

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Entity
@Table(name = "roles")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
1.2、数据访问层

UserRole创建相应的JPA仓库接口。

UserRepository接口:

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
    Optional<User> findByEmail(String email);
    Boolean existsByEmail(String email);
    Boolean existsByUsername(String username);
}

RoleRepository接口:

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface RoleRepository extends JpaRepository<Role, Long> {
    Optional<Role> findByName(String name);
}

2、JWT工具类

2.1、JwtTokenProvider类

创建JwtTokenProvider类,用于生成和解析JWT Token。使用java-jwt库来实现JWT的创建和验证。

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.function.Function;

@Component
public class JwtTokenProvider {

    @Value("${app.jwt-secret}")
    private String jwtSecret;

    @Value("${app.jwt-expiration-milliseconds}")
    private Long jwtExpirationInMs;

    public String generateToken(String username) {
        return generateToken(username, null);
    }

    public String generateToken(String username, Date expiration) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(expiration != null ? expiration : expirationDate())
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token).getBody();
        return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(jwtSecret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (JwtException | IllegalArgumentException e) {
            throw new IllegalArgumentException("Expired or invalid JWT token");
        }
    }

    public Boolean validateToken(String token) {
        try {
            extractAllClaims(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private Date expirationDate() {
        return new Date(System.currentTimeMillis() + jwtExpirationInMs);
    }
}

3、Spring Security配置

3.1、配置WebSecurityConfigurerAdapter

创建一个配置类,继承WebSecurityConfigurerAdapter,重写方法以配置Spring Security。

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter(authenticationManager()))
            .addFilter(new JwtAuthorizationFilter(authenticationManager()))
            .exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint());
    }
}
3.2、用户详情服务

实现UserDetailsService接口,用于从数据库加载用户信息。

import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.stream.Collectors;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList())
        );
    }
}

4、登录和认证接口

4.1、登录接口

创建一个登录接口,用户可以通过提供用户名和密码来获取JWT Token。

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @Autowired
    private AuthService authService;

    @PostMapping("/login")
    public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthRequest authenticationRequest) {
        final String jwt = authService.login(authenticationRequest.getUsername(), authenticationRequest.getPassword());

        return ResponseEntity.ok(new AuthResponse(jwt));
    }
}
4.2、AuthService类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

@Service
public class AuthService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    public String login(String username, String password) {
        Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        
        return jwtTokenProvider.generateToken(authentication.getName());
    }
}
4.3、过滤器

实现两个过滤器,JwtAuthenticationFilter用于处理登录请求,生成JWT;JwtAuthorizationFilter用于验证后续请求中的JWT。

JwtAuthenticationFilter:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = tokenProvider.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (tokenProvider.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }

        chain.doFilter(request, response);
    }
}

JwtAuthorizationFilter:

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JwtAuthorizationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = tokenProvider.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (token
Provider.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }

        chain.doFilter(request, response);
    }
}

这样,我们就完成了基于Token的身份认证机制的实现。用户登录时,系统会生成一个JWT,用户需在随后的请求中携带此JWT进行身份验证。通过这种方式,我们可以确保应用的安全性和可扩展性。

四、总结

通过上述步骤,我们成功在Spring Boot应用中整合了Spring Security、JWT和MySQL,实现了基于Token的身份认证机制。这种机制不仅提高了应用的安全性,还增强了其可扩展性和维护性。在微服务和分布式系统中,基于Token的身份认证是推荐的做法。


版权声明:本博客内容为原创,转载请保留原文链接及作者信息。

参考文章

菜单权限控制是指通过对用户的身份和角色进行鉴权,并根据其权限设置对应的菜单功能的访问权限。在使用bootsecurityjwtmysql实现菜单权限控制时,可以按照以下步骤进行: 1. 首先,使用MySQL数据库存储用户、角色和菜单信息。可以创建三张表,分别用于存储用户信息、角色信息和菜单信息,表之间通过外键关联。 2. 使用Spring Boot框架和Spring Security进行用户认证和授权。通过配置Spring Security,可以设定访问接口的权限要求,包括菜单接口。可以使用JWT(Json Web Token)来生成和验证用户的身份,将用户的角色和权限信息存储在JWT。 3. 在Spring Boot定义你的数据模型,例如User、Role和Menu,与数据库的表对应。设置它们之间的关联关系。 4. 创建相应的Controller和Service来处理用户的注册、登录、角色分配和菜单授权等操作。例如,可以创建一个UserController来处理用户相关的操作,一个RoleController来处理角色相关的操作。 5. 实现菜单权限控制。可以在角色和菜单表设置对应的权限字段,如角色的权限标识和菜单的访问路径等。在用户登录成功后,根据用户的角色查询对应的菜单权限,将权限信息返回给前端。前端根据权限信息动态生成菜单。 6. 在不同的接口加入相应的安全注解,如@PreAuthorize,用于验证用户是否具有访问该接口的权限。可以在Controller的方法上添加注解,指定需要的角色或权限。 通过上述步骤,就可以实现基于bootsecurityjwtmysql的菜单权限控制。用户登录后,根据其角色和权限,设定相应的菜单访问权限,并在后端接口进行访问权限的验证,确保用户只能访问其具有权限的菜单功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值