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();
    }
}
相关推荐
lang201509282 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
刘一说3 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
lang201509284 小时前
Spring Boot缓存机制全解析
spring boot·后端·缓存
摇滚侠5 小时前
Spring Boot 3零基础教程,WEB 开发 默认页签图标 Favicon 笔记29
java·spring boot·笔记
lang201509285 小时前
Spring Boot SQL数据库全攻略
数据库·spring boot·sql
是梦终空6 小时前
计算机毕业设计241—基于Java+Springboot+vue的爱心公益服务系统(源代码+数据库+11000字文档)
java·spring boot·vue·毕业设计·课程设计·毕业论文·爱心公益系统
泉城老铁10 小时前
springboot 对接发送钉钉消息,消息内容带图片
前端·spring boot·后端
qq_124987075310 小时前
基于Spring Boot的高校实习实践管理系统(源码+论文+部署+安装)
java·spring boot·后端·毕业设计
韩宁羽10 小时前
SpringBoot开发双11商品服务系统[完结19章]
spring boot
5pace12 小时前
【JavaWeb|第二篇】SpringBoot篇
java·spring boot·后端