Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。
Spring Security的核心是提供认证(Authentication)、授权(Authorization)和攻击防护。
身份认证,指验证用户是否为使用系统的合法主体,就是说用户能否访问该系统。
Spring Security支持的认证方式有:用户名和密码、OAuth2.0登录、SAML2.0登录、中央认证服务器(CAS)、记住我、JAAS身份认证、OpenID、预身份验证方案、X509认证
身份鉴权,指验证某个用户是否具有权限使用系统的某个操作。
Spring Security支持的授权方案:基于过滤器授权、基于表达式访问控制、安全对象实现、方法安全、域对象安全(ACL)。
防止会话固定、点击劫持、跨站点请求伪造等攻击。(解决系统安全问题)
Spring Security支持的攻击防护有:CSRF、会话固定保护、安全请求头、HTTPS、HTTP防火墙
Spring Security是通过Filter来实现的,采用的是责任链的设计模式,它有一条很长的过滤器链,只有当前过滤器通过,才能进入下一个过滤器。
Spring Boot 自动装配通过WebSecurityConfiguration类,Spring容器中注入一个名为SpringSecurityFilterChain的Servlet过滤器,类型为FilterChainProxy。它实现了javax.servlet.Filter,因此外部的请求都会经过这个类。FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时,这些Filter都已经注入到Spring容器中,他们是Spring Security的核心,各有各的职责。但是他们并不直接处理用户的认证和授权,而是把他们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理。
FilterChainProxy类图:
过滤器图:
除了默认过滤器,我们也可以自定义多个过滤器,通过configure(HttpSecurity http)方法中配置。认证的过程围绕图中过滤链的橙色部分(过滤器Filter),动态鉴权主要是围绕其绿色部分(拦截器Interceptor)。
内置的过滤器:
序号 | 名称 | 描述 |
1 | WebAsyncManagerIntegrationFilter | 根据请求封装获取WebAsyncManager,从WebAsyncManager获取/注册的安全上下文可调 用处理拦截器 |
2 | SecurityContextPersistenceFilter | SecurityContextPersistenceFilter主要是使用SecurityContextRepository在session中保存 或更新一个SecurityContext,并将SecurityContext给以后的过滤器使用,来为后续fifilter 建立所需的上下文。SecurityContext中存储了当前用户的认证以及权限信息。 |
3 | HeaderWriterFilter | 向请求的Header中添加相应的信息,可在http标签内部使用security:headers来控制 |
4 | CsrfFilter | csrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的csrf的 token信息,如果不包含,则报错。起到防止csrf攻击的效果。 |
5 | LogoutFilter | 匹配URL为/logout的请求,实现用户退出,清除认证信息。 |
6 | UsernamePasswordAuthenticationFilter | 认证过滤器,表单认证操作全靠这个过滤器,默认匹配URL为/login且必须为POST请求 |
7 | DefaultLoginPageGeneratingFilter | 如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。 |
8 | DefaultLogoutPageGeneratingFilter | 由此过滤器可以生产一个默认的退出登录页面 |
9 | BasicAuthenticationFilter | 此过滤器会自动解析HTTP请求中头部名字为Authentication,且以Basic开头的头信息 |
10 | RequestCacheAwareFilter | 通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存 HttpServletRequest |
11 | SecurityContextHolderAwareRequestFilter | 针对ServletRequest进行了一次包装,使得request具有更加丰富的API |
12 | AnonymousAuthenticationFilter | 当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到 SecurityContextHolder中。spring security为了兼容未登录的访问,也走了一套认证流程, 只不过是一个匿名的身份。 |
13 | SessionManagementFilter | securityContextRepository限制同一用户开启多个会话的数量 |
14 | ExceptionTranslationFilter | 异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异 常 |
15 | FilterSecurityInterceptor | 授权过滤器,获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其 是否有权限。 |
Spring Security有两种认证方式,一种是 HttpBasic认证和表单认证。
具体流程:
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/hello", "/loginalert", "/mylogin").permitAll()
.antMatchers("/helloadmin").hasRole("admin")
.antMatchers("/hellouser").hasAuthority("query")
.anyRequest().authenticated()
.and().httpBasic();
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/hello", "/loginalert", "/mylogin").permitAll()
.antMatchers("/helloadmin").hasRole("admin")
.antMatchers("/hellouser").hasAuthority("query")
.anyRequest().authenticated()
.and().formLogin().loginPage("/mylogin.html")
.usernameParameter("uname").passwordParameter("passwd")
.permitAll()
.loginProcessingUrl("/doLogin")
.failureHandler((request, response, exception) -> {
String message = "login fail:" + exception.getMessage();
response.setContentType("text/html");
response.getWriter().write("");
}).successHandler(((request, response, authentication) -> {
String message = "login success:" + authentication.getName();
response.setContentType("text/html");
response.getWriter().write("");
}));
http.exceptionHandling().accessDeniedHandler(((request, response, accessDeniedException) -> {
String message = "access fail:" + accessDeniedException.getMessage();
response.setContentType("text/html");
response.getWriter().write("");
}));
}
授权是在用户认证通过后,对访问资源的权限进行检查的过程。Spring Security可以通过http.authorizeRequests()对web请求进行授权保护。Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。
权限配置有两种方式一种在SecurityFilterChain中配置,另一种在接口上增加注解。
权限表达式(方法):
1. SecurityFilterChain配置
.antMatchers("/toUserAdd").hasAnyRole("admin");
.antMatchers("/toUserAdd").hasRole("admin");
.antMatchers("/toUserEdit").hasAuthority("edit")
.antMatchers("/toUserEdit").hasAnyAuthority("edit")
2.注解方式
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public String hello() {
return "hello";
}
@PreAuthorize("hasRole('ADMIN') and authentication.name=='lglbc'")
public String hello2() {
return "hello";
}
@PreAuthorize("hasRole('ADMIN') and authentication.name==#name")
public String hello3(String name) {
return "hello";
}
@PreFilter(value = "filterObject.id%2!=0",filterTarget = "users")
public void addUsers(List users, Integer other) {
System.out.println("users = " + JSON.toJSONString(users));
}
@PostFilter("filterObject.id%2==0")
public List getAll() {
List users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(i, "lglbc_:" + i));
}
return users;
}
@Secured({"ROLE_ADMIN","ROLE_USER"})
public User getUserByUsername(String username) {
return new User(99, username);
}
@DenyAll
public String denyAll() {
return "DenyAll";
}
@PermitAll
public String permitAll() {
return "PermitAll";
}
@RolesAllowed({"ADMIN","USER"})
public String rolesAllowed() {
return "RolesAllowed";
}
}
通过原来分析,hasRole 的处理逻辑和 hasAuthority 似乎是一样的,只是hasRole 这里会自动给传入的字符串前缀(默认是ROLE_
),使用 hasAuthority
更具有一致性,不用考虑要不要加 ROLE_
前缀,在UserDetailsService类的loadUserByUsername中查询权限,也不需要手动增加。在SecurityExpressionRoot 类中hasAuthority 和 hasRole 最终都是调用了 hasAnyAuthorityName 方法。
异常处理设计到两个 Handler 进行处理 ,一个是处理认证异常的Handler处理器 AuthenticationEntryPoint,一个是授权异常的Handler处理器 AccessDeniedHandler。异常类主要分为两大类:AuthenticationException认证异常和AccessDeniedException授权异常。
AuthenticationException异常:
异常类型 | 备注 |
AuthenticationException | 认证异常的父类,抽象类 |
BadCredentialsException | 登录凭证(密码)异常 |
InsufficientAuthenticationException | 登录凭证不够充分而抛出的异常 |
SessionAuthenticationException | 会话并发管理时抛出的异常,例如会话总数超出最大限制数 |
UsernameNotFoundException | 用户名不存在异常 |
PreAuthenticatedCredentialsNotFoundException | 身份预认证失败异常 |
ProviderNotFoundException | 未配置AuthenticationProvider 异常 |
AuthenticationServiceException | 由于系统问题而无法处理认证请求异常。 |
InternalAuthenticationServiceException | 由于系统问题而无法处理认证请求异常。和AuthenticationServiceException 不同之处在于,如果外部系统出错,则不会抛出该异常 |
AuthenticationCredentialsNotFoundException | SecurityContext中不存在认证主体时抛出的异常 |
NonceExpiredException | HTTP摘要认证时随机数过期异常 |
RememberMeAuthenticationException | RememberMe认证异常 |
CookieTheftException | RememberMe认证时Cookie 被盗窃异常 |
InvalidCookieException | RememberMe认证时无效的Cookie异常 |
AccountStatusException | 账户状态异常 |
LockedException | 账户被锁定异常 |
DisabledException | 账户被禁用异常 |
CredentialsExpiredException | 登录凭证(密码)过期异常 |
AccountExpiredException | 账户过期异常 |
AccessDeniedException异常:
异常类型 | 备注 |
AccessDeniedException | 权限异常的父类 |
AuthorizationServiceException | 由于系统问题而无法处理权限时抛出异常 |
CsrfException | Csrf令牌异常 |
MissingCsrfTokenException | Csrf令牌缺失异常 |
InvalidCsrfTokenException | Csrf令牌无效异常 |
可以通过SecurityFilterChain进行配置
http.exceptionHandling().accessDeniedHandler(((request, response, accessDeniedException) -> {
String message = "access fail:" + accessDeniedException.getMessage();
response.setContentType("text/html");
response.getWriter().write("");
}));
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
System.out.println("error:" + authException);
});
参考:
SpringBoot Security工作原理_springbootsecurity原理_StrangerIt的博客-CSDN博客
springSecurity源码之鉴权原理_凯歌的博客的博客-CSDN博客
SpringSecurity认证基本原理与认证2种方式_spring security 多种认证_程序小黑马的博客-CSDN博客