一、背景

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

二、需求

1、给客户端使用的api

  1. 拦截 /api/**所有的请求。

  2. /api/**的所有请求都需要ROLE_ADMIN的角色。

  3. 从请求头中获取 token,只要获取到token的值,就认为认证成功,并赋予ROLE_ADMIN到角色。

  4. 如果没有权限,则给前端返回JSON对象 {message:"您无权限访问"}

  5. 访问 /api/userInfo端点

    1. 请求头携带 token 可以访问。
    2. 请求头不携带token不可以访问。

2、给网站使用的api

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

三、实现方案

方案一:

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

方案二

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

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

扩展:

1、Spring Security 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

2、app 无权限访问 api

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

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

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

六、完整代码

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

Spring Security 多过滤链的使用的更多相关文章

  1. Spring Security(2):过滤器链(filter chain)的介绍

    上一节中,主要讲了Spring Security认证和授权的核心组件及核心方法.但是,什么时候调用这些方法呢?答案就是Filter和AOP.Spring Security在我们进行用户认证以及授予权限 ...

  2. Spring Security 学习总结

    Spring Security Spring Security是基于Spring提供声明式安全保护的安全性框架.Spring Security提供了完整的安全性解决方案,能够在Web请求级别和方法调用 ...

  3. spring security基本知识(一)

    spring security基本知识(一) Spring Security是为基于Spring的应用程序提供声明式安全保护的安全 性框架.Spring Security提供了完整的安全性解决方案,它 ...

  4. springBoot整合spring security+JWT实现单点登录与权限管理--筑基中期

    写在前面 在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与 ...

  5. Spring Security:Servlet 过滤器(三)

    3)Servlet 过滤器 Spring Security 过滤器链是一个非常复杂且灵活的引擎.Spring Security 的 Servlet 支持基于 Servlet 过滤器,因此通常首先了解过 ...

  6. Spring学习日志之Spring Security配置

    依赖引入 <dependency> <groupId>org.springframework.security</groupId> <artifactId&g ...

  7. Spring Security OAuth 2.0

    续·前一篇<OAuth 2.0> OAuth 2.0 Provider 实现 在OAuth 2.0中,provider角色事实上是把授权服务和资源服务分开,有时候它们也可能在同一个应用中, ...

  8. Spring Security OAuth 2开发者指南译

    Spring Security OAuth 2开发者指南译 介绍 这是用户指南的支持OAuth 2.0.对于OAuth 1.0,一切都是不同的,所以看到它的用户指南. 本用户指南分为两部分,第一部分为 ...

  9. Spring Security 快速了解

    在Spring Security之前 我曾经使用 Interceptor 实现了一个简单网站Demo的登录拦截和Session处理工作,虽然能够实现相应的功能,但是无疑Spring Security提 ...

随机推荐

  1. Postman 根据nginx日志查账号

    1) GET:http://fwm.le-yao.com/api/backend/profile 2) Headers中,在KEY中添加 Content-Type ,对应的VALUE为 applica ...

  2. MacOS安装和卸载Java

    ​ 安装java 下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 设 ...

  3. Vue状态管理Vuex简单使用

    状态管理保存在store\index.js中,简单说明如下 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export def ...

  4. js简单化技巧

    1.交换两个变量而没有第三个 let x = 1;let y = 2;[x, y] = [y, x];console.log(x, y); 输出: 2 1 2.将数字转换为字符串 const num  ...

  5. 微信小程序函数间传递url的参数丢失问题

    可以使用encodeURIComponent():函数可把字符串作为 URI 组件进行编码. 可以使用decodeURIComponent():函数可把字符串作为 URI 组件进行解码.  

  6. 带你掌握Vue过滤器filters及时间戳转换

    摘要:Vue的filters过滤器是比较常见的一个知识点,下面我将结合时间戳转换的例子带你快速了解filters的用法. 本文分享自华为云社区<三分钟掌握Vue过滤器filters及时间戳转换& ...

  7. Shell系列(30)- 单分支if语句判断分区使用率

    需求 监控分区已用空间,超过80%,抛出警告 脚本 #!/bin/bash #给tets赋值用于接收参数,传递给if进行判断 #申明变量test并赋值,由于赋的值是系统变量的结果,所以得用$()引用 ...

  8. Fiddler抓包工具-全网最全教程,没有之一

    初识Fiddler fiddler,译为骗子 是位于客户端.服务器端的HTTP代理,是Web调试的利器. 是c#编写的程序 Fiddler主要功能: 监控http.https流量 查看.分析请求内容细 ...

  9. python对象引用和垃圾回收

    变量="标签" 变量a和变量b引用同一个列表: >>> a = [1, 2, 3] >>> b = a >>> a.appen ...

  10. photoshop CS6打不开提示请卸载并重新安装该程序

    运行Photoshop.exe时,提示错误: 解决方案: 以管理员身份运行该exe文件