SpringBoot3 整合SpringSecurity

Maven

xml 复制代码
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

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

JwtFilter

java 复制代码
package com.system.security;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.system.common.exception.ServerException;
import com.system.common.utlis.jwt.JwtUtils;
import com.system.common.utlis.result.Prefix;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * @author kuaiting
 */
@Component
public class JwtFilter extends OncePerRequestFilter {
    @Resource
    private JwtUtils jwtUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // 实现过滤逻辑
        String authorization = request.getHeader(Prefix.TOKEN_KEY);
        System.out.println(authorization);
        if (authorization != null && authorization.startsWith(Prefix.TOKEN_BEARER)) {
            // 验证JWT并设置用户信息到SecurityContextHolder中
            DecodedJWT jwt = jwtUtils.resolveToken(authorization);
            if (jwt != null) {
                Long uid = jwtUtils.getUid(jwt);
                SecurityUser details = jwtUtils.getUserDetails(jwt);
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(details, null, details.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
                request.setAttribute("uid", uid);
            }
        }
        filterChain.doFilter(request, response);
    }


    public void validate(HttpServletRequest request) throws ServerException {
        //请求中传来的验证码
        String code = request.getParameter("code");
        String sessionCode = request.getSession().getAttribute("session_code").toString();
        if (StrUtil.isEmpty(code)) {
            throw new ServerException("验证码不能为空!");
        }
        if (StrUtil.isEmpty(sessionCode)) {
            throw new ServerException("验证码已经失效!");
        }
        if (!sessionCode.equalsIgnoreCase(code)) {
            throw new ServerException("验证码输入错误!");
        }

    }

}

SecurityConfig

java 复制代码
package com.system.security;

import com.system.common.utlis.jwt.JwtUtils;
import com.system.common.utlis.result.Prefix;
import com.system.common.utlis.result.ResData;
import com.system.common.utlis.result.ResEnum;
import com.system.system.dao.RoleDao;
import com.system.system.entity.vo.AuthVO;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
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 java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author kuaiting
 */
@Configuration
public class SecurityConfig {

    @Resource
    JwtUtils jwtUtils;

    @Resource
    private JwtFilter jwtFilter;

    @Resource
    private RoleDao roleDao;



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

    private final String[] paths = {
            "/druid/**", "/system/captcha/line",
            "/druid/login.html/**",
            "/system/login", "/js/**", "/*/*.json", "/*/*.yml",
            "/prims/**", "/type/**", "/system/file/**",
            "/diagram-viewer/**", "/images/**",
            "/api/login/**", "/api/file/**",
            "/css/**", "/*/*.ico", "/swagger-resources/**",
            "/swagger/**", "/swagger-ui/**",
            "/webjars/**", "/v3/**", "/v2/**", "/doc.html/**"
    };

    @Bean
    public SecurityFilterChain securityChain(HttpSecurity http) throws Exception {

        return http.authorizeHttpRequests(conf -> conf.requestMatchers(paths).permitAll()
                .anyRequest().authenticated())
                .formLogin(conf ->
                        conf.loginProcessingUrl("/system/login")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .successHandler(this::onAuthenticationSuccess)
                        .failureHandler(this::onAuthenticationFailure))
                .exceptionHandling(conf ->
                         conf.authenticationEntryPoint(this::noLogin)
                        .accessDeniedHandler(this::noPermission))
                .logout(conf ->
                         conf.logoutUrl("/system/logout")
                        .logoutSuccessHandler(this::onLogoutSuccess))
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .build();

    }

    private void noPermission(HttpServletRequest request,
                              HttpServletResponse response,
                              AccessDeniedException e) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        PrintWriter writer = response.getWriter();
        jsonWriter(writer, ResEnum.FORBIDDEN.getCode(), ResEnum.FORBIDDEN.getMsg());

    }

    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        SecurityUser user = (SecurityUser) authentication.getPrincipal();
        Long uid = user.getId();
        Set<String> permissions = new HashSet<>();
        for (GrantedAuthority authority : user.getAuthorities()) {
            String auth = authority.getAuthority();
            permissions.add(auth);
        }
        String token = jwtUtils.createToken(user, uid, user.getUsername(),user.getPassword());
        AuthVO authVo = new AuthVO();
        authVo.setRole(roleDao.getUserRoles(uid));
        authVo.setPermission(permissions);
        authVo.setKey(Prefix.TOKEN_KEY);
        authVo.setToken(token);
        authVo.setExpire(jwtUtils.expireTime().getTime());
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = response.getWriter();
        jsonWriter(writer, ResEnum.SUCCESS.getCode(), ResEnum.SUCCESS.getMsg(), authVo);
    }

    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = response.getWriter();
        jsonWriter(writer, ResEnum.UNAUTHORIZED.getCode(), exception.getMessage());
    }

    public void noLogin(HttpServletRequest request, HttpServletResponse response,
                        AuthenticationException authException) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = response.getWriter();
        jsonWriter(writer, ResEnum.UNAUTHORIZED.getCode(), ResEnum.UNAUTHORIZED.getMsg());
    }


    public void onLogoutSuccess(HttpServletRequest request,
                                HttpServletResponse response,
                                Authentication authentication) throws IOException {
        String authorization = request.getHeader("Authorization");
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = response.getWriter();
        // 登出时,删除redis中的token
        if (jwtUtils.invalidateToken(authorization)) {
            jsonWriter(writer, ResEnum.SUCCESS.getCode(), ResEnum.SUCCESS.getMsg());
        } else {
            jsonWriter(writer, ResEnum.FAIL.getCode(), ResEnum.FAIL.getMsg());
        }


    }

    private void jsonWriter(PrintWriter writer, Integer code, String message) {
        jsonWriter(writer, code, message, null);
    }

    private void jsonWriter(PrintWriter writer, Integer code, String message, Object data) {
        writer.write(ResData.asJson(code, message, data));
        writer.flush();
        writer.close();
    }


    private List<Long> getRoleIds(Long uid) {
        return roleDao.getRoleIds(uid);
    }

    private List<String> getUserRoles(Long uid) {
        return roleDao.getUserRoles(uid);
    }

    private Set<String> getPermissions(List<Long> roleIds) {
        Set<String> all = new HashSet<>();
        for (Long id : roleIds) {
            List<String> permissions = getUserPermissions(id);
            all.addAll(permissions);
        }
        return all;
    }

    private List<String> getUserPermissions(Long rid) {
        return roleDao.getPermissions(rid);
    }


}

User

java 复制代码
package com.system.security;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author kuaiting
 */
@Data
@NoArgsConstructor
public class SecurityUser implements UserDetails, Serializable {
    private Long id;
    private String username;
    private String password;
    private Integer status;
    private Set<String> permissions;
    private  Collection<? extends GrantedAuthority> authorities;


    public SecurityUser( Long id,String username, String password, Integer status, Set<String> permissions) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.status = status;
        this.permissions = permissions;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (authorities != null) {
            return authorities;
        }
        //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
        authorities = permissions.stream().
                map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return status == 0;
    }
}

JwtUtils

java 复制代码
package com.system.common.utlis.jwt;

import com.auth0.jwt.JWT;
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 com.system.common.utlis.redis.RedisService;
import com.system.common.utlis.result.Prefix;
import com.system.security.SecurityUser;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
public class JwtUtils {
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expire}")
    private Integer expire;

    @Resource
    private RedisService redisService;

    public Boolean invalidateToken(String haeadToken) {
        String token = convertToken(haeadToken);
        if (token == null) {
            return false;
        }
        try {
            DecodedJWT jwt = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
            return deleteToken(jwt.getId(), jwt.getExpiresAt());
        } catch (JWTVerificationException e) {
            return null;
        }
    }

    // 删除redis 中的Token
    private Boolean deleteToken(String id, Date date) {
        if (isInvalidToken(id)) {
            return false;
        }
        Date now = new Date();
        long expire = Math.max(date.getTime() - now.getTime(), 0);
        redisService.set(Prefix.JWT_BLACK_LIST + id, id, expire);
        return true;
    }

    // 验证 redis 中 token 是否存在
    private Boolean isInvalidToken(String id) {
        return Boolean.TRUE.equals(redisService.hasKey(Prefix.JWT_BLACK_LIST + id));
    }


    public String createToken(UserDetails details, Long id, String username,String password) {
        Algorithm algorithm = Algorithm.HMAC256(secret);
        redisService.set(Prefix.JWT_BLACK_LIST + id, id, expire);
        return JWT.create().withJWTId(String.valueOf(id))
                .withClaim("id", id)
                .withClaim("username", username)
                .withClaim("password", password)
                .withClaim("authorities", getAuths(details))
                .withExpiresAt(expireTime())
                .sign(algorithm);
    }

    public String getAuths(UserDetails details) {
        return details.getAuthorities().stream().map(GrantedAuthority::getAuthority).
                collect(Collectors.joining(","));
    }

    // 获取过期时间
    public Date expireTime() {
        // 过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.HOUR, expire * 24); // 默认7天
        return instance.getTime();
    }

    // 解析 JWT token
    public DecodedJWT resolveToken(String haeadToken) {
        String token = convertToken(haeadToken);
        if (token == null) {
            return null;
        }
        try {
            DecodedJWT jwt = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
            if (isInvalidToken(jwt.getId())) {
                return null;
            }
            Date expires = jwt.getExpiresAt();
            return new Date().after(expires) ? null : jwt;
        } catch (JWTVerificationException e) {
            return null;
        }
    }

    // 解析 截取真正有用的token
    private String convertToken(String haeadToken) {
        if (haeadToken == null || !haeadToken.startsWith(Prefix.TOKEN_BEARER)) {
            return null;
        }
        return haeadToken.substring(7);
    }

    // 获取用户信息
    public SecurityUser getUserDetails(DecodedJWT jwt) {
        Map<String, Claim> claims = jwt.getClaims();
        String authorities = claims.get("authorities").asString();
        Set<SimpleGrantedAuthority> permissions = new HashSet<>();
        for (String auth : authorities.split(",")) {
            permissions.add(new SimpleGrantedAuthority(auth));
        }
        SecurityUser sysUser = new SecurityUser();
        sysUser.setId(jwt.getClaim("id").asLong());
        sysUser.setUsername(claims.get("username").toString());
        sysUser.setPassword(claims.get("password").toString());
        sysUser.setStatus(0);
        sysUser.setAuthorities(permissions);
        return sysUser;
    }

    // 获取用户ID
    public Long getUid(DecodedJWT jwt) {
        return jwt.getClaim("id").asLong();
    }
}
相关推荐
IT毕设实战小研19 分钟前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
一只爱撸猫的程序猿1 小时前
使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"
spring boot·aigc·ai编程
甄超锋1 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
武昌库里写JAVA4 小时前
JAVA面试汇总(四)JVM(一)
java·vue.js·spring boot·sql·学习
Pitayafruit5 小时前
Spring AI 进阶之路03:集成RAG构建高效知识库
spring boot·后端·llm
zru_96025 小时前
Spring Boot 单元测试:@SpyBean 使用教程
spring boot·单元测试·log4j
甄超锋5 小时前
Java Maven更换国内源
java·开发语言·spring boot·spring·spring cloud·tomcat·maven
还是鼠鼠6 小时前
tlias智能学习辅助系统--Maven 高级-私服介绍与资源上传下载
java·spring boot·后端·spring·maven
舒一笑11 小时前
Started TttttApplication in 0.257 seconds (没有 Web 依赖导致 JVM 正常退出)
jvm·spring boot·后端
javadaydayup12 小时前
Apollo 凭什么能 “干掉” 本地配置?
spring boot·后端·spring