一、概念
Spring Security 是一个基于 Spring 框架的安全性框架,它提供了许多针对身份验证(Authentication)
和访问控制(Authorization)方面的功能,帮助开发人员保护和管理应用程序的安全性。
二、核心思想与核心组件
1. 核心思想
Spring Security 的核心思想是通过将安全性视为应用程序的一个关注点来实现,
而不是将其视为一个单独的策略或框架。使用 Spring Security 可以轻松实现以下安全性功能:
1、身份验证和授权
2、记住我(Remember me)
3、CSRF 攻击防护
4、会话管理和过期处理
5、安全日志记录
6、与其他认证和授权机制的集成
2. 核心组件
2.1 SecurityContextHolder
SecurityContextHolder 是一个存储当前安全上下文信息的中心化存储区。
安全上下文是一个包含了认证和授权信息的对象,它通常包含了当前登录用户的信息,
例如用户名、密码和角色等。在整个应用程序中,SecurityContextHolder 对象可以被多个线程共享和使用
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/4555b0f48fce4cc48bf55fc5ed80a223.png)
2.2 AuthenticationManager
AuthenticationManager 是 Spring Security 的核心接口之一。它处理来自用户的身份验证请求,并基于这些请求返回认证对象。
AuthenticationManager 主要通过 AuthenticationProvider或者 UserDetailsService 来完成这个工作。
在一个 Web 应用中,当用户尝试登录时,通常会提交一个包含用户名和密码等信息的表单。
在使用 Spring Security 进行身份验证时,这个表单数据会被传递到 AuthenticationManager 的 authenticate() 方法中。
AuthenticationManager 将根据传入的身份验证信息,通过相应的 AuthenticationProvider 或者UserDetailsService
获取用户的详细信息,并进行身份验证。如果身份验证通过,则 AuthenticationManager
会创建一个 Authentication 对象,其中包含了该用户的身份验证信息以及相关的角色和权限等信息。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e644ef35b2d04b6c8b381ed757773a6f.png)
2.3 认证对象-Authentication
它代表了经过身份验证的用户。
在认证成功之后,认证对象会被存储在 SecurityContextHolder 中,供整个应用程序共享和使用。
通过认证对象,应用程序可以访问用户的详细信息,例如用户名、角色和权限等,并根据这些信息做出相应的授权决策.
2.4 AuthenticationProvider
AuthenticationProvider 接口实现了具体的身份验证机制,例如表单身份验证、LDAP 身份验证等。
通常情况下,每个 AuthenticationProvider 组件对应一个身份验证机制。
AuthenticationProvider 组件会从用户提供的身份验证信息中获取所需信息,并生成一个 Authentication 对象返回AuthenticationManager.
2.5 UserDetailsService
UserDetailsService 接口定义了从内存、关系数据库或其他数据源中获取用户详细信息的方法。
对于基于用户名和密码进行身份验证的应用程序-UserDetailsService 可以返回一个 UserDetails 实例,
其中包含用户名、加密后的密码和该用户拥有的权限和角色信息.
2.6 FilterChainProxy
FilterChainProxy 是 Spring Security 最重要的组件之一。它将请求传递给一个或多个 SecurityFilterChain 实例,
这些实例负责检查身份验证和授权规则,并在需要时重定向或返回错误响应。
FilterChainProxy 的主要作用是提供了一组过滤器链,这些过滤器链会被按顺序执行,直到找到能够处理请求的过滤器为止。
2.7 SecurityMetadataSource
SecurityMetadataSource 接口定义了资源路径与相应的安全元数据之间的映射。
例如,它可以指定哪些 URL 需要身份验证,哪些 URL 可以匿名访问等。
2.8 AccessDecisionManager
AccessDecisionManager 是 Spring Security 的核心组件之一,在用户进行操作时,判断用户是否有足够的权限。
其主要的方法是 decide() 方法,它包含了一个 Authentication 对象、一个要访问的资源对象和代表当前用户的权限信息。
通过配置合适的 AccessDecisionManager,可以在应用程序中实现访问控制的功能。
三、什么是 Spring Security认证和授权
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/8ceff80ae2a74127a67debed535ec7bb.png)
1.认证
验证用户身份的过程-比如登录:根据账号和密码判断当前用户是否是已存在的用户,如果不存在则跳转到注册或者登录界面.
认证成功后,Spring Security会创建一个认证对象-Authentication,并将其存储在安全上下文中,以便后续的授权和访问控制使用。
2.授权
确认当前登录用户可访问的资源权限,不同的角色可访问的资源范围不同.
在Spring Security中,授权通常基于用户的角色或权限进行。角色是一组权限的集合,用于描述用户可以执行的操作或访问的资源。
3.总结
认证关注用户的身份验证,确保用户是他们所声称的身份;而授权关注已认证用户是否有权执行特定操作或访问特定资源。
在Spring Security中,认证和授权是两个独立但相互关联的过程。认证为授权提供了基础,授权则根据认证结果和用户的角色/权限做出访问决策。
四、Spring Security过滤器链定义及其工作原理
Spring的安全性是通过Web应用程序的servlet过滤器驱动的.它在请求到达真正的接口之前对请求进行拦截。
通过token令牌完成身份认证和授权目的.
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/be4f19317b6241508184a62a1355472e.png)
五、认证流程
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/887cf0a3a41841a18c9ebc63cd9abc92.png)
1.用户在登录页面输入用户名和密码,提交表单。
2.Spring Security的UsernamePasswordAuthenticationFilter拦截表单提交的请求,并将用户名和密码封装成一个Authentication对象。
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
3.AuthenticationManager接收到Authentication对象后,会根据用户名和密码查询用户信息(从数据库或者缓存中),并将用户信息封装成一个UserDetails对象。
4.如果查询到用户信息,则将UserDetails对象封装成一个已认证的Authentication对象并返回,如果查询不到用户信息,则抛出相应的异常。(用户名或者密码错误/用户名不存在等).
5.如果认证成功,则认证结果会存储在SecurityContextHolder中。其它过滤器可以从SecurityContextHolder中获取用户信息
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
AuthenticationUtil.setAuthentication(authentication);
public static void setAuthentication( Authentication authentication ){
authenticationThreadLocal.set(authentication);
Object principal = authentication.getPrincipal();
try {
if(principal instanceof UserDetails) {
UserDetails ud = (UserDetails)principal;
String json = JsonUtil.toJson(ud);
JsonNode jsonNode = JsonUtil.toJsonNode(json);
userThreadLocal.set(jsonNode);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
六、授权流程
1.配置 Spring Security
1.自定义配置类继承该类--WebSecurityConfigurerAdapter
2.添加注解@EnableWebSecurity-开启security认证授权
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {}
2.配置过滤器、拦截器、服务器
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler).and()
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenHandler, jwtConfig.getHeader());
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity
.addFilterBefore(htFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
}
3.授权异常过滤器
配置自定义未授权异常处理器,当过滤器抛出异常时,跳转到异常处理器中
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
CommonResult<String> commonResult = new CommonResult<String>( ResponseErrorEnums.ACCESS_DENIED_EXCEPTION);
writer.write(JsonUtil.toJson(commonResult));
writer.flush();
}
}
3.授权认证过滤器
对所有请求进行认证,判断该请求是否是合法请求
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
try {
username = jwtTokenHandler.getUsernameFromToken(authToken);
} catch (Exception e) {
logger.warn("the token valid exception", e);
send401Error(response, e.getMessage());
return;
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
同时保存一份到用户信息线程变量与认证对象线程变量中
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
AuthenticationUtil.setAuthentication(authentication);
}
}
private static ThreadLocal<JsonNode> userThreadLocal = new ThreadLocal<JsonNode>();
private static ThreadLocal<Authentication> authenticationThreadLocal = new ThreadLocal<Authentication>();
4.授权拦截器
授权拦截器用于判断当前请求的权限,即当前用户所属角色是否可访问当前请求
public class HtFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Resource
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Resource
private AccessDecisionManager accessDecisionManager;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
Collection<ConfigAttribute> attributes = securityMetadataSource.getAttributes(fi);
accessDecisionManager.decide(SecurityContextHolder.getContext().getAuthentication(), null, attributes);
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
}
5.授权数据资源服务器
授权资源服务器主要用于判断当前请求可访问的角色集合
@Service
public class HtInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource, ContextThread {
private static ThreadLocal<HashMap<String, Collection<ConfigAttribute>>> mapThreadLocal = new ThreadLocal<HashMap<String, Collection<ConfigAttribute>>>();
public void loadResourceDefine() {
HashMap<String, Collection<ConfigAttribute>> map = getMapThreadLocal();
Collection<ConfigAttribute> array;
ConfigAttribute cfg;
List<HashMap<String, String>> methodAuth = methodAuthService.getMethodAuth();
if (BeanUtils.isEmpty(methodAuth)) {
return;
}
for (HashMap<String, String> mapAuth : methodAuth) {
array = new ArrayList<ConfigAttribute>();
String roleAlias = mapAuth.get("roleAlias");
String key = mapAuth.get("methodRequestUrl");
if (StringUtil.isEmpty(roleAlias) || StringUtil.isEmpty(key)) {
continue;
}
cfg = new SecurityConfig(roleAlias);
if (map.containsKey(key)) {
array = map.get(key);
}
array.add(cfg);
map.put(key, array);
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
AntPathRequestMatcher matcher;
String resUrl;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (AuthenticationUtil.isAnonymous(authentication)) return null;
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
String requestHeader = request.getHeader("Proxy-Authorization");
if (StringUtil.isNotEmpty(requestHeader) && requestHeader.startsWith("Bearer ") && jwtTokenHandler.validateFeignToken(requestHeader.substring(7))) {
return null;
}
if (object instanceof FilterInvocation) {
FilterInvocation filterInvocation = (FilterInvocation) object;
String method = filterInvocation.getRequest().getMethod();
resUrl = filterInvocation.getRequestUrl();
if (HttpMethod.OPTIONS.matches(method)) {
return null;
}
}
loadResourceDefine();
HashMap<String, Collection<ConfigAttribute>> map = getMapThreadLocal();
for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
resUrl = iter.next();
matcher = new AntPathRequestMatcher(resUrl);
if (matcher.matches(request)) {
return map.get(resUrl);
}
}
return null;
}
}
}
6.授权权限管理服务器
对认证对象与当前请求可访问的角色集合进行判断-如果角色相等,说明该方法可以访问;如果不等,说明当前用户无权访问该方法
public class HtDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
try {
if (BeanUtils.isEmpty(configAttributes))
return;
Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) authentication
.getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
if (PlatformConsts.ROLE_SUPER.equals(grantedAuthority.getAuthority())) {
return;
}
}
for (ConfigAttribute configAttribute : configAttributes) {
if (BeanUtils.isEmpty(configAttribute)) continue;
String configVal = configAttribute.toString();
if (PlatformConsts.PERMIT_All.equals(configVal)) {
return;
}
else if (PlatformConsts.AUTHENTICATED.equals(configVal)) {
if (AuthenticationUtil.isAnonymous(authentication)) {
throw new InsufficientAuthenticationException("需要提供jwt授权码");
} else {
String attribute = configAttribute.getAttribute();
if (StringUtil.isEmpty(attribute)) {
return;
}
}
}
}
for (GrantedAuthority grantedAuthority : authorities) {
for (ConfigAttribute configAttribute : configAttributes) {
if (grantedAuthority.getAuthority().equals(
configAttribute.getAttribute())) {
return;
}
}
}
throw new AccessDeniedException("您没有权限, 请联系系统管理员");
} finally {
HtInvocationSecurityMetadataSourceService.clearMapThreadLocal();
}
}
}
六、RBAC权限模型
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/58607646c03740868ac0ed98375427f9.png)