Spring Security 多过滤链的使用详解

 更新时间:2021年07月15日 08:52:29   作者:huan_1993  
本文主要介绍了Spring Security 多过滤链的使用,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧

一、背景

在我们实际的开发过程中,有些时候可能存在这么一些情况,某些api 比如: /api/** 这些是给App端使用的,数据的返回都是以JSON的格式返回,且这些API的认证方式都是使用的TOKEN进行认证。而除了 /api/** 这些API之外,都是给网页端使用的,需要使用表单认证,给前端返回的
都是某个页面。

二、需求

1、给客户端使用的api

  • 拦截 /api/**所有的请求。
  • /api/**的所有请求都需要ROLE_ADMIN的角色。
  • 从请求头中获取 token,只要获取到token的值,就认为认证成功,并赋予ROLE_ADMIN到角色。
  • 如果没有权限,则给前端返回JSON对象 {message:"您无权限访问"}
  • 访问 /api/userInfo端点
    • 请求头携带 token 可以访问。
    • 请求头不携带token不可以访问。

2、给网站使用的api

  • 拦截 所有的请求,但是不处理/api/**开头的请求。
  • 所有的请求需要ROLE_ADMIN的权限。
  • 没有权限,需要使用表单登录。
  • 登录成功后,访问了无权限的请求,直接跳转到百度去。
  • 构建2个内建的用户
    • 用户一: admin/admin 拥有 ROLE_ADMIN 角色
    • 用户二:dev/dev 拥有 ROLE_DEV 角色
  • 访问 /index 端点
    • admin 用户访问,可以访问。
    • dev 用户访问,不可以访问,权限不够。

三、实现方案

方案一:

直接拆成多个服务,其中 /api/** 的成为一个服务。非/api/**的拆成另外一个服务。各个服务使用自己的配置,互不影响。

方案二

在同一个服务中编写。不同的请求使用不同的SecurityFilterChain来实现。

经过考虑,此处采用方案二来实现,因为方案一简单,使用方案二实现,也可以记录下在同一个项目中 通过使用多条过滤器链,因为并不是所有的时候,都是可以分成多个项目的。

扩展:

1、Spring Security SecurityFilterChain 的结构

SecurityFilterChain的结构

2、控制 SecurityFilterChain 的执行顺序

使用 org.springframework.core.annotation.Order 注解。

3、查看是怎样选择那个 SecurityFilterChain

查看 org.springframework.web.filter.DelegatingFilterProxy#doFilter方法

四、实现

1、app 端 Spring Security 的配置

package com.huan.study.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;

/**
 * 给 app 端用的 Security 配置
 *
 * @author huan.fu 2021/7/13 - 下午9:06
 */
@Configuration
public class AppSecurityConfig {

    /**
     * 处理 给 app(前后端分离) 端使用的过滤链
     * 以 json 的数据格式返回给前端
     */
    @Bean
    @Order(1)
    public SecurityFilterChain appSecurityFilterChain(HttpSecurity http) throws Exception {
        // 只处理 /api 开头的请求
        return http.antMatcher("/api/**")
                .authorizeRequests()
                // 所有以 /api 开头的请求都需要 ADMIN 的权限
                    .antMatchers("/api/**")
                    .hasRole("ADMIN")
                    .and()
                // 捕获到异常,直接给前端返回 json 串
                .exceptionHandling()
                    .authenticationEntryPoint((request, response, authException) -> {
                        response.setStatus(HttpStatus.UNAUTHORIZED.value());
                        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                        response.setContentType(MediaType.APPLICATION_JSON.toString());
                        response.getWriter().write("{\"message:\":\"您无权访问01\"}");
                    })
                    .accessDeniedHandler((request, response, accessDeniedException) -> {
                        response.setStatus(HttpStatus.UNAUTHORIZED.value());
                        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                        response.setContentType(MediaType.APPLICATION_JSON.toString());
                        response.getWriter().write("{\"message:\":\"您无权访问02\"}");
                    })
                    .and()
                // 用户认证
                .addFilterBefore((request, response, chain) -> {
                    // 此处可以模拟从 token 中解析出用户名、权限等
                    String token = ((HttpServletRequest) request).getHeader("token");
                    if (!StringUtils.hasText(token)) {
                        chain.doFilter(request, response);
                        return;
                    }
                    Authentication authentication = new TestingAuthenticationToken(token, null,
                            AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    chain.doFilter(request, response);
                }, UsernamePasswordAuthenticationFilter.class)
                .build();
    }
}

2、网站端 Spring Secuirty 的配置

package com.huan.study.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

/**
 * 给 网站 应用的安全配置
 *
 * @author huan.fu 2021/7/14 - 上午9:09
 */
@Configuration
public class WebSiteSecurityFilterChainConfig {
    /**
     * 处理 给 webSite(非前后端分离) 端使用的过滤链
     * 以 页面 的格式返回给前端
     */
    @Bean
    @Order(2)
    public SecurityFilterChain webSiteSecurityFilterChain(HttpSecurity http) throws Exception {

        AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);

        // 创建用户
        authenticationManagerBuilder.inMemoryAuthentication()
                .withUser("admin")
                    .password(new BCryptPasswordEncoder().encode("admin"))
                    .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"))
                    .and()
                .withUser("dev")
                    .password(new BCryptPasswordEncoder().encode("dev"))
                    .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_DEV"))
                    .and()
                .passwordEncoder(new BCryptPasswordEncoder());

        // 只处理 所有 开头的请求
        return http.antMatcher("/**")
                .authorizeRequests()
                // 所有请求都必须要认证才可以访问
                    .anyRequest()
                    .hasRole("ADMIN")
                    .and()
                // 禁用csrf
                .csrf()
                    .disable()
                // 启用表单登录
                .formLogin()
                    .permitAll()
                    .and()
                // 捕获成功认证后无权限访问异常,直接跳转到 百度
                .exceptionHandling()
                    .accessDeniedHandler((request, response, exception) -> {
                        response.sendRedirect("http://www.baidu.com");
                    })
                    .and()
                .build();
    }

    /**
     * 忽略静态资源
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer( ){
        return web -> web.ignoring()
                .antMatchers("/**/js/**")
                .antMatchers("/**/css/**");

    }
}

3、控制器写法

/**
 * 资源控制器
 *
 * @author huan.fu 2021/7/13 - 下午9:33
 */
@Controller
public class ResourceController {

    /**
     * 返回用户信息
     */
    @GetMapping("/api/userInfo")
    @ResponseBody
    public Authentication showUserInfoApi() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    @GetMapping("/index")
    public String index(Model model){
        model.addAttribute("username","张三");
        return "index";
    }
}

4、引入jar包

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

五、实现效果

1、app 有权限访问 api

app 有权限访问 api

2、app 无权限访问 api

app 无权限访问 api

3、admin 用户有权限访问 网站 api

admin 用户有权限访问 网站 api

4、dev 用户无权限访问 网站 api

dev 用户无权限访问 网站 api

访问无权限的API直接跳转到 百度 首页。

六、完整代码

https://gitee.com/huan1993/Spring-Security/tree/master/multi-security-filter-chain

到此这篇关于Spring Security 多过滤链的使用详解的文章就介绍到这了,更多相关Spring Security 多过滤链 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JavaWEB中Servlet的生命周期详解

    JavaWEB中Servlet的生命周期详解

    大家好,本篇文章主要讲的是JavaWEB中Servlet的生命周期详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • 如何使用两个栈实现队列Java

    如何使用两个栈实现队列Java

    这篇文章主要介绍了如何使用两个栈实现队列Java,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java基础第二篇方法与数据成员

    Java基础第二篇方法与数据成员

    在上一篇文章中介绍了Java基础 从HelloWorld到面向对象,我们初步了解了对象(object)。对象中的数据成员表示对象的状态。对象可以执行方法,表示特定的动作。这篇文章我们进一步深入到对象。了解Java中方法与数据成员的一些细节。
    2021-09-09
  • Spring纯注解开发模式让开发简化更简化

    Spring纯注解开发模式让开发简化更简化

    Spring3.0引入了纯注解开发的模式,框架的诞生是为了简化开发,那注解开发就是简化再简化。Spring的特性在整合MyBatis方面体现的淋漓尽致哦
    2022-08-08
  • Spring超详细讲解事务和事务传播机制

    Spring超详细讲解事务和事务传播机制

    Spring事务的本质就是对数据库事务的支持,没有数据库事务,Spring是无法提供事务功能的。Spring只提供统一的事务管理接口,具体实现都是由数据库自己实现的,Spring会在事务开始时,根据当前设置的隔离级别,调整数据库的隔离级别,由此保持一致
    2022-06-06
  • SpringBoot中如何统一接口返回与全局异常处理详解

    SpringBoot中如何统一接口返回与全局异常处理详解

    全局异常处理是个比较重要的功能,一般在项目里都会用到,这篇文章主要给大家介绍了关于SpringBoot中如何统一接口返回与全局异常处理的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-09-09
  • 轻松学会使用JavaMail API发送邮件

    轻松学会使用JavaMail API发送邮件

    想要轻松学会使用JavaMail API发送邮件吗?本指南将带你快速掌握这一技能,让你能够轻松发送电子邮件,无论是个人还是工作需求,跟着我们的步骤,很快你就可以在Java应用程序中自如地处理邮件通信了!
    2023-12-12
  • springboot 定时任务@Scheduled实现解析

    springboot 定时任务@Scheduled实现解析

    这篇文章主要介绍了springboot 定时任务@Scheduled实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Java程序去调用并执行shell脚本及问题总结(推荐)

    Java程序去调用并执行shell脚本及问题总结(推荐)

    这篇文章主要介绍了Java程序去调用并执行shell脚本及问题总结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • 解决Java原生压缩组件不支持中文文件名乱码的问题

    解决Java原生压缩组件不支持中文文件名乱码的问题

    本篇文章主要介绍了解决Java原生压缩组件不支持中文文件名乱码的问题,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03

最新评论