SpringSecurity + JWT 2025最新版 SpringSecurity角色权限控制 SpringSecurity6

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

文章目录

  • 前言
  • 完整代码
    • pom.xml(引入项目所需依赖)
    • application.properties(配置数据库连接)
    • JwtUtil.java(生成、校验、解析JWT)
    • JsonUtil.java(对象转JSON)
    • User.java(用户实体)
    • UserRepository.java(用户数据接口)
    • UserService(实现UserDetailsService)
    • UserController.java(测试权限)
    • JwtAuthenticationFilter(验证JWT是否合法)
    • SecurityConfig.java(SpringSecurity配置类)
    • Result.java(封装返回信息)
    • JwtAccessDeniedHandler.java(处理请求路径无权限)
    • JwtAuthenticationEntryPoint.java(处理无效JWT/未认证用户)
    • LoginFailureHandler.java(处理登录失败逻辑)
    • LoginSuccessHandler.java(处理登录成功逻辑)
  • 效果检验
    • 1.测试用户注册功能
    • 2.测试角色user功能
    • 3.测试角色admin功能
    • 4.测试超时处理
    • 5.测试未登录用户

前言

本文默认读者已了解相关知识,所以只介绍代码。
本文尽量使用官方提供的方法尽量简略的实现SpringSecurity+JWT,更多自定义功能需读者扩展。
项目环境:SpringBoot3、JDK21、Mysql8、SpringSecurity6、auth0 JWT


完整代码

pom.xml(引入项目所需依赖)

		<!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!--Json-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version>
        </dependency>

        <!--Spring Starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--JPA-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- MySQL -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

        <!-- SpringSecurity -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- auth0 JWT -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.18.2</version>
        </dependency>

        <!--开发时用(自动应用程序重启,自动加载静态资源,禁用模板缓存,全局错误页面)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

application.properties(配置数据库连接)

# Mysql数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/windows?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Hibernate配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

JwtUtil.java(生成、校验、解析JWT)

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import jakarta.servlet.http.HttpServletRequest;

import java.util.*;

import org.springframework.util.StringUtils;

public class JwtUtil {
    private static final String jwtKey = "123456";
    private static final Integer expiredTimeInSeconds = 60;

    public static String generateToken(String username) {
        Algorithm algorithm = Algorithm.HMAC256(jwtKey);
        Date now = new Date();
        Date expiration = new Date(now.getTime() + expiredTimeInSeconds * 1000L);

        return JWT.create()
                .withClaim("username", username)
                .withExpiresAt(expiration)
                .withIssuedAt(now)
                .sign(algorithm);
    }

    public static Optional<Map<String, Claim>> getClaims(String token) {
        Algorithm algorithm = Algorithm.HMAC256(jwtKey);
        JWTVerifier verifier = JWT.require(algorithm).build();

        try {
            DecodedJWT decodedJWT = verifier.verify(token);
            return Optional.of(decodedJWT.getClaims());
        } catch (JWTVerificationException e) {
            return Optional.empty();
        }
    }

    public static boolean validateToken(String token) {
        Algorithm algorithm = Algorithm.HMAC256(jwtKey);
        JWTVerifier verifier = JWT.require(algorithm).build();

        try {
            verifier.verify(token);
            return true;
        } catch (JWTVerificationException e) {
            return false;
        }
    }

    public static String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }
}

JsonUtil.java(对象转JSON)

import com.google.gson.Gson;

public class JsonUtil {
    public static String ObjectToJson(Object object) {
        Gson gson = new Gson();
        return gson.toJson(object);
    }
}

User.java(用户实体)

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;

@Data
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;
    private String role;
}

UserRepository.java(用户数据接口)

import java.util.Optional;

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

import com.example.windows.windows.enity.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

UserService(实现UserDetailsService)

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

import com.example.windows.windows.enity.User;
import com.example.windows.windows.repository.UserRepository;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

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

import com.example.windows.windows.enity.User;
import com.example.windows.windows.repository.UserRepository;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

@Service
public class UserService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

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

        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(user.getRole()));

        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                authorities);
    }

    public User register(String username,String password) {
        User user = new User();
        if (userRepository.findByUsername(username).isPresent()) {
            return null;
        }
        user.setUsername(username);
        user.setPassword(new BCryptPasswordEncoder().encode(password));
        user.setRole("user");
        return userRepository.save(user);
    }
}

UserController.java(测试权限)

import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;

@RestController
@RequestMapping("/api/user")
public class UserController {
    @GetMapping("/test")
    public String test() {
        return "test";
    }

    @GetMapping("/adminTest")
    public String adminTest() {
        return "adminTest";
    }

    @GetMapping("/userTest")
    public String userTest() {
        return "userTest";
    }
}

JwtAuthenticationFilter(验证JWT是否合法)

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import com.auth0.jwt.interfaces.Claim;
import com.example.windows.windows.service.UserService;
import com.example.windows.windows.utils.JwtUtil;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

import java.io.IOException;
import java.util.Map;
import java.util.Optional;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private UserService userService;

    public JwtAuthenticationFilter(UserService userService) {
        this.userService = userService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String jwt = JwtUtil.getTokenFromRequest(request);
        if (StringUtils.hasText(jwt) && JwtUtil.validateToken(jwt)) {
            Optional<Map<String, Claim>> claims = JwtUtil.getClaims(jwt);
            Claim usernameClaim = claims.get().get("username");
            String username = usernameClaim.asString();
            UserDetails userDetails = userService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    userDetails,
                    null,
                    userDetails.getAuthorities());

            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        filterChain.doFilter(request, response);
    }
}

SecurityConfig.java(SpringSecurity配置类)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.example.windows.windows.filter.JwtAuthenticationFilter;
import com.example.windows.windows.handle.JwtAccessDeniedHandler;
import com.example.windows.windows.handle.JwtAuthenticationEntryPoint;
import com.example.windows.windows.handle.LoginFailureHandler;
import com.example.windows.windows.handle.LoginSuccessHandler;
import com.example.windows.windows.service.UserService;

import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
    private final UserService userService;
    private final LoginFailureHandler loginFailureHandler;
    private final LoginSuccessHandler loginSuccessHandler;

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration)
            throws Exception {
        return configuration.getAuthenticationManager();
    }

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

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable())
                .addFilterBefore(new JwtAuthenticationFilter(userService), UsernamePasswordAuthenticationFilter.class)
               	.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.NEVER))
                .formLogin(login -> login
                        .loginProcessingUrl("/api/user/login")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .successHandler(loginSuccessHandler)
                        .failureHandler(loginFailureHandler))
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/api/user/userTest").hasAnyAuthority("user")
                        .requestMatchers("/api/user/adminTest").hasAnyAuthority("admin")
                        .anyRequest().authenticated())
                .exceptionHandling(exception -> exception
                        .accessDeniedHandler(jwtAccessDeniedHandler)
                        .authenticationEntryPoint(jwtAuthenticationEntryPoint));
        return http.build();
    }
}

Result.java(封装返回信息)

package com.example.windows.windows.enity;

import java.io.Serializable;
import lombok.Data;

@Data
public class Result implements Serializable {
    private int code;
    private String message;
    private Object data;

    public Result() {}

    public Result(int code, String message, Object data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }
}

JwtAccessDeniedHandler.java(处理请求路径无权限)

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import com.example.windows.windows.enity.Result;
import com.example.windows.windows.utils.JsonUtil;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_OK);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        Result result = new Result(401, "权限不足", null);
        outputStream.write(JsonUtil.ObjectToJson(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

JwtAuthenticationEntryPoint.java(处理无效JWT/未认证用户)

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import com.example.windows.windows.enity.Result;
import com.example.windows.windows.utils.JsonUtil;
import com.example.windows.windows.utils.JwtUtil;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            AuthenticationException authenticationException) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_OK);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        String jwt = JwtUtil.getTokenFromRequest(httpServletRequest);
        Result result = new Result();
        if (StringUtils.hasText(jwt) && JwtUtil.validateToken(jwt)){
            result.setCode(401);
            result.setMessage("JWT无效");
        }
        else{
            result.setCode(401);
            result.setMessage("请先登录");
        }

        outputStream.write(JsonUtil.ObjectToJson(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

LoginFailureHandler.java(处理登录失败逻辑)

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import com.example.windows.windows.enity.Result;
import com.example.windows.windows.service.UserService;
import com.example.windows.windows.utils.JsonUtil;
import com.example.windows.windows.utils.JwtUtil;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class LoginFailureHandler implements AuthenticationFailureHandler {
    private final UserService userService;
    Logger logger = LoggerFactory.getLogger(LoginFailureHandler.class);
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            AuthenticationException authenticationException) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_OK);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        String username = httpServletRequest.getParameter("username");
        String password = httpServletRequest.getParameter("password");
        Result result = new Result();
        if (userService.register(username,password) == null) {
            result.setCode(401);
            result.setMessage("用户名或密码错误");
        }
        else{
            String jwt = JwtUtil.generateToken(username);
            result.setCode(200);
            result.setMessage("注册成功");
            result.setData(jwt);
        }
        
        outputStream.write(JsonUtil.ObjectToJson(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

LoginSuccessHandler.java(处理登录成功逻辑)

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import com.example.windows.windows.enity.Result;
import com.example.windows.windows.utils.JsonUtil;
import com.example.windows.windows.utils.JwtUtil;

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_OK);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        String username = httpServletRequest.getParameter("username");
        String jwt = JwtUtil.generateToken(username);
        Result result = new Result(200, "登录成功", jwt);

        outputStream.write(JsonUtil.ObjectToJson(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

效果检验

以下均使用postman测试

1.测试用户注册功能

代码中设置了用户不存在自动注册的逻辑
user注册成功
在这里插入图片描述
admin注册成功
在这里插入图片描述
去数据库修改admin用户的权限用于测试
在这里插入图片描述

2.测试角色user功能

登录user用户
在这里插入图片描述
将返回的jwt(data中的字符串)放到Headers中的Authorization中,并添加前缀((Bearer )有个空格)。
有user权限
在这里插入图片描述
没有admin权限
在这里插入图片描述

3.测试角色admin功能

admin登录
在这里插入图片描述
有admin权限
在这里插入图片描述
没有user权限
在这里插入图片描述

4.测试超时处理

在这里插入图片描述

5.测试未登录用户

在这里插入图片描述

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值