SpringBoot中SpringSecurity安全框架的基本配置与使用方式

 更新时间:2026年03月13日 15:30:17   作者:胡思乱想罢了~  
Spring Security是Spring框架的安全性解决方案,提供了身份验证、授权、认证流程安全控制等全面安全功能,广泛应用于Java应用程序中

Spring Security是一个基于Spring框架的安全性解决方案,提供了全面的安全功能和集成能力,用于保护Java应用程序的身份验证、授权和其他安全需求。

Spring Security的主要功能包括

  • 身份验证(Authentication):Spring Security提供了多种身份验证机制。
  • 授权(Authorization):Spring Security支持基于角色和权限的授权机制,可以精确控制用户对系统资源的访问权限。它提供了注解和标签,使开发人员可以在代码中灵活定义和配置授权规则。
  • 认证流程的安全控制:Spring Security提供了很多机制来确保认证流程的安全性。
  • Web安全:Spring Security可以通过过滤器链的方式保护Web应用程序的安全性。
  • 方法级安全:Spring Security允许在方法级别上进行安全控制,通过注解或XML配置来限制对特定方法的访问权限。
  • 安全事件和审计:Spring Security提供了安全事件机制,可以记录和处理安全相关的事件,例如登录成功、权限拒绝等。它还支持审计功能,可用于记录和追踪用户的操作行为。

Spring Security是一个功能强大、灵活可扩展的安全框架,可以帮助开发人员在Java应用程序中实现全面的身份验证和授权功能,提高应用程序的安全性和可信度。

在SpringBoot中使用首先需要导入依赖

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

配置SecurityConfig和Jwt工具类

在SecurityConfig中可以对访问权限进行设置,将登录以及注册接口设置为开放(不然系统就无法访问),也可以将不需要进行权限认证的接口也再此设置为开发;同时也可以通过注解的方式对需要进行权限认证的接口进行设置。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Autowired
    private JwtAuthenticationTokenFilter filter;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private AccessDeniedHandler accessDeniedHandler;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()

                // 对于登录接口 允许匿名访问 未登录状态也可以访问
                .antMatchers("/login/login").anonymous()
                .antMatchers("/login/register").anonymous()
                .antMatchers("/login/sendCode").anonymous()
                .antMatchers("/pay/notify").anonymous()
                // 需要用户带有管理员权限
//                .antMatchers("/find").hasRole("管理员")
//                // 需要用户具备这个接口的权限
//                .antMatchers("/find").hasAuthority("menu:user")

                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        //添加过滤器
        http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);

        //配置异常处理器
        http.exceptionHandling()
                //配置认证失败处理器
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);

        //允许跨域
        http.cors();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "sangeng";

    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }

    /**
     * 生成jtw
     * @param subject token中要存放的数据(json格式)
     * @return
     */
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成jtw
     * @param subject token中要存放的数据(json格式)
     * @param ttlMillis token超时时间
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer("sg")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);
    }

    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
        return builder.compact();
    }

    public static void main(String[] args) throws Exception {
        String jwt = createJWT("1234");
        Claims claims = parseJWT(jwt);
        String subject = claims.getSubject();

        System.out.println(subject);
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }


}

定义用户类

实现UserDetails接口,对用户权限进行封装。

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    private List<String> permissions;

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }


    //返回权限信息

    @JSONField(serialize = false)  //不需要存到redis中,进行序列化忽略
    private List<SimpleGrantedAuthority> authorities;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        if (authorities != null){
            return authorities;
        }
       authorities = permissions.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());

        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getUserPwd();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    //判断是否没过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

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

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

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

实现UserDetailsService接口

重写loadUserByUsername方法查询用户权限信息

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

    //查询用户信息
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(User::getUserName, username);
    User user = userMapper.selectOne(wrapper);
    //如果没有查询到用户就抛出异常
    if (Objects.isNull(user)){
        throw new RuntimeException("用户名或密码错误");
    }
    List<String> list = menuMapper.selectPermsByUserId(user.getId());

    return new LoginUser(user,list);
}

配置过滤器

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("token");

        if (!StringUtils.hasText(token)) {
            //token为空,放行
            filterChain.doFilter(request, response);
            return;
        }
        //解析token
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException("token非法");
        }
        //从redis中获取用户信息
        String redisKey = "login:" + userId;

        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if (Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        //存入securityContextHolder
        // 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());

        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

配置认证失败和权限不足的返回类

这两个方法,在SecurityConfig中进行添加。当对于清空出现时,调用方法,返回给前端状态码,以及自定义信息。

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        Result result = new Result(HttpStatus.UNAUTHORIZED.value(), "认证失败");
        String json = JSON.toJSONString(result);
        //处理异常
        WebUtils.renderString(response, json);

    }
}
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        Result result = new Result(HttpStatus.FORBIDDEN.value(), "权限不足");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response, json);
    }
}

配置完成后,在前端页面访问后端接口,过滤器会进行拦截,判断前端请求中请求头是否携带token,携带并成功验证用户后,会将用户的权限信息封装到Authentication中,后续判断该用户是否拥有访问该接口的权限。

例如设置下面接口的访问中需要用户拥有getUserAddress权限。

@RequestMapping("/getUserAddress")
@PreAuthorize("hasAuthority('getUserAddress')")
public Result getUserAddress(Long userId) {

    return addressService.getUserAddress(userId);
}

用户、角色、权限表信息如下

可以看到用户1不存在访问getUserAddress的权限。

当使用用户1登录后,在前端对该接口进行访问时,后端会返回权限不足的状态码以及相应的信息信息。该接口的方法也不会执行。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Spring整合quartz做定时任务的示例代码

    Spring整合quartz做定时任务的示例代码

    这篇文章主要介绍了在spring项目使用quartz做定时任务,首先我这里的项目已经是一个可以跑起来的完整项目,web.xml里面的配置我就不贴出来了,具体实例代码跟随小编一起看看吧
    2022-01-01
  • Java中的synchronized重量级锁解析

    Java中的synchronized重量级锁解析

    这篇文章主要介绍了Java中的synchronized重量级锁解析,内核需要去申请这个互斥量,必须要进入内核态,也就是这里需要用户态,内核态的切换,状态的切换,开销是比较大的,这就是重型锁的一个弊端,需要的朋友可以参考下
    2024-01-01
  • springboot配置多数据源的一款框架(dynamic-datasource-spring-boot-starter)

    springboot配置多数据源的一款框架(dynamic-datasource-spring-boot-starter

    dynamic-datasource-spring-boot-starter 是一个基于 springboot 的快速集成多数据源的启动器,今天通过本文给大家分享这款框架配置springboot多数据源的方法,一起看看吧
    2021-09-09
  • SpringMVC实现前端后台交互传递数据

    SpringMVC实现前端后台交互传递数据

    本篇文章主要介绍了SpringMVC实现前端后台传递数据的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03
  • java8日期工具类封装的实战记录

    java8日期工具类封装的实战记录

    java time包中的是类是不可变且线程安全的,新的时间及日期API位于java.time中,下面这篇文章主要给大家介绍了关于java8日期工具类封装的相关资料,需要的朋友可以参考下
    2021-09-09
  • 学习Java之IO流的基础概念详解

    学习Java之IO流的基础概念详解

    这篇文章主要给大家介绍了Java中的IO流,我们首先要搞清楚一件事,就是为什么需要IO流这个东西,但在正式学习IO流的使用之前,小编有必要带大家先了解一下IO流的基本概念,需要的朋友可以参考下
    2023-09-09
  • java巧用@Convert实现表字段自动转entity

    java巧用@Convert实现表字段自动转entity

    本文主要介绍了java巧用@Convert实现表字段自动转entity,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07
  • Java中常见的语法糖分享

    Java中常见的语法糖分享

    Java语法糖是指Java编译器在编译Java源代码时所做的一些特殊处理,使得Java源代码在编译后生成的字节码更加简洁、易读、易维护,Java 中有许多常见的语法糖,本文给大家列举了一些常见的例子,需要的朋友可以参考下
    2023-10-10
  • 使用GSON库转换Java对象为JSON对象的进阶实例详解

    使用GSON库转换Java对象为JSON对象的进阶实例详解

    这篇文章主要介绍了使用GSON库转换Java对象为JSON对象的进阶实例详解,包括注册TypeAdapter及处理Enum类型等实际运用中可能遇到的一些复杂问题,需要的朋友可以参考下
    2016-06-06
  • Java中大数据推荐算法使用场景分析

    Java中大数据推荐算法使用场景分析

    在Java中实现大数据推荐算法时,通常会使用一些开源的机器学习库,如Apache Mahout、Weka、DL4J(DeepLearning4j,用于深度学习)或者Spark MLlib(用于在Spark集群上运行),这篇文章主要介绍了Java中可以用的大数据推荐算法,需要的朋友可以参考下
    2024-06-06

最新评论