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

随笔1天前发布 张优雅
5 0 0

文章目录

前言完整代码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>


123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051

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

123456789

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;
    }
}


12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061

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);
    }
}

12345678

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;
}


1234567891011121314151617

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);
}

1234567891011

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);
    }
}


123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960

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";
    }
}


123456789101112131415161718192021

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);
    }
}


12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849

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();
    }
}


123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263

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;
    }
}


12345678910111213141516171819

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();
    }
}


123456789101112131415161718192021222324252627282930

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();
    }
}


1234567891011121314151617181920212223242526272829303132333435363738394041

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();
    }
}


123456789101112131415161718192021222324252627282930313233343536373839404142434445464748

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();
    }
}


12345678910111213141516171819202122232425262728293031323334

效果检验

以下均使用postman测试

1.测试用户注册功能

代码中设置了用户不存在自动注册的逻辑
user注册成功
SpringSecurity + JWT 2024最新版 SpringSecurity角色权限控制 SpringSecurity6
admin注册成功
SpringSecurity + JWT 2024最新版 SpringSecurity角色权限控制 SpringSecurity6
去数据库修改admin用户的权限用于测试
SpringSecurity + JWT 2024最新版 SpringSecurity角色权限控制 SpringSecurity6

2.测试角色user功能

登录user用户
SpringSecurity + JWT 2024最新版 SpringSecurity角色权限控制 SpringSecurity6
将返回的jwt(data中的字符串)放到Headers中的Authorization中,并添加前缀((Bearer )有个空格)。
有user权限
SpringSecurity + JWT 2024最新版 SpringSecurity角色权限控制 SpringSecurity6
没有admin权限
SpringSecurity + JWT 2024最新版 SpringSecurity角色权限控制 SpringSecurity6

3.测试角色admin功能

admin登录
SpringSecurity + JWT 2024最新版 SpringSecurity角色权限控制 SpringSecurity6
有admin权限
SpringSecurity + JWT 2024最新版 SpringSecurity角色权限控制 SpringSecurity6
没有user权限
SpringSecurity + JWT 2024最新版 SpringSecurity角色权限控制 SpringSecurity6

4.测试超时处理

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

5.测试未登录用户

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

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...