一、背景
在咱们理论的开发过程中,有些时候可能存在这么一些状况,某些 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
的构造
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