Spring Security 介绍
Spring Security 是基于 Spring 框架的权限管理框架
Spring Security 的前身是 Acegi Security
Acegi Security 以配置繁琐而被诟病,投入 Spring 怀抱后,随着 SpringBoot 的崛起,Spring Security 的易用性得到了极大的提升,经常被用于 SpringBoot 及 SpringCloud 项目
Spring Security 的基本功能
认证:提供多种常见的认证方式
授权:提供基于 URL 的请求授权、支持方法访问授权以及对象访问授权
基本原理
Spring Security 是通过一层层 Filter 来处理 web 请求的
在 Filter 组成的链条中,逐步完成认证和授权,发现异常则抛给异常处理器处理
过滤器链中的核心概念
springSecurityFilterChain
Spring Security 的核心过滤器叫 springSecurityFilterChain,类型是 FilterChainProxy
WebSecurity、HttpSecurity
WebSecurity 构建了 FilterChainProxy 对象
HttpSecurity 构建了 FilterChainProxy 中的一个 SecurityFilterChain
WebSecurityConfiguration
@EnableWebSecurity 注解,导入了 WebSecurityConfiguration 类
WebSecurityConfiguration 中创建了建造者对象 WebSecurity 和核心过滤器 FilterChainProxy
Spring Security 常用组件
Authentication:认证接口,定义了认证对象的数据形式。
AuthenticationManager:用于校验 Authentication,返回一个认证完成后的
SecurityContext:上下文对象,用来存储 Authentication
SecurityContextHolder:用来访问 SecurityContext
GrantedAuthority:代表权限
UserDetails:代表用户信息
UserDetailsService:获取用户信息
简单使用
引入 Spring Security 依赖
<!--引入SpringSecurity--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
引入依赖后,不做任何配置,Spring Security 会自动生效,请求将跳转登录页面
默认用户名、密码和权限可在 application.yaml 中配置
spring:security:user:name:mingpassword:123456roles:admin
基于内存的认证
@Configuration@EnableWebSecurity//开启注解设置权限@EnableGlobalMethodSecurity(prePostEnabled=true)publicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{//配置密码加密器@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}//配置认证管理器@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123")).roles("admin").and().withUser("user").password(passwordEncoder().encode("456")).roles("user");}//配置安全策略@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{//设置路径及要求的权限,支持ant风格路径写法http.authorizeRequests()//设置OPTIONS尝试请求直接通过.antMatchers(HttpMethod.OPTIONS,"/**").permitAll().antMatchers("/api/demo/user").hasAnyRole("user","admin")//注意使用hasAnyAuthority角色需要以ROLE_开头.antMatchers("/api/demo/admin").hasAnyAuthority("ROLE_admin").antMatchers("/api/demo/hello").permitAll().and()//开启表单登录.formLogin().permitAll().and()//开启注销.logout().permitAll();}}
前后端分离
关闭 CSRF 防御和会话管理
CSRF 防御要求表单登录时携带 CSRF Token,前后端分离时不需要开启
会话管理设置为 STATELESS,使用无状态的 JWT 进行鉴权
@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{//关闭csrf防御http.csrf().disable();//关闭会话管理http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//...}
自定义登录逻辑
Spring Security 默认使用表单登录,若要支持 JSON 请求,可继承UsernamePasswordAnthenticationFilter
,并使用HttpSecurity
的addFilterAt
替换原有
publicclassCustomAuthenticationFilterextendsUsernamePasswordAuthenticationFilter{@OverridepublicAuthenticationattemptAuthentication(HttpServletRequestrequest,HttpServletResponseresponse)throwsAuthenticationException{//判断是否为JSON格式请求if(request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){//...}else{returnsuper.attemptAuthentication(request,response);}}}
通过配置 AuthenticationManagerBuilder,设置自定义的 UserDetailsService
@AutowiredprivateCustomUserDetailsServicecustomUserDetailsService@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());}
实现 UserDetailsService 的 loadUserByUsername 方法
publicclassCustomUserDetailsServiceimplementsUserDetailsService{@OverridepublicUserDetailsloadUserByUsername(Strings)throwsUsernameNotFoundException{//根据username查询用户Useruser=userMapper.getUserByUsername(s);if(user==null){//...}//查询角色或权限List<SimpleGrantedAuthority>authorities=userMapper.listRolesByUsername(s).stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());//构造UserDetails实例并返回}}
自定义登录成功处理器
通过配置 HttpSecurity,设置自定义的 successHandler
@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.formLogin().permitAll().loginProcessingUrl("/login").successHandler(customLoginSuccessHandler)}
CustomLoginSuccessHandler,以 JSON 形式返回前端,携带生成的 Token
@Component@RequiredArgsConstructorpublicclassCustomLoginSuccessHandlerimplementsAuthenticationSuccessHandler{privatefinalJwtUtiljwtUtil;@OverridepublicvoidonAuthenticationSuccess(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication)throwsIOException{//构造一个统一返回格式对象Map<String,Object>res=newHashMap<>();res.put("code",200);res.put("message":"认证成功");res.put("path":"login");Objectprincipal=authentication.getPrincipal();if(principalinstanceofUser){//根据用户信息,使用JWT工具类构建Token//...//存到返回内容中res.put("data","xxxxxx")}//以JSON格式写入responseresponse.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");PrintWriterwriter=response.getWriter();writer.print(JsonUtil.Obj2Str(res));writer.flush();}}
自定义登录失败处理器
通过配置 HttpSecurity,设置自定义的 failureHandler
@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.formLogin().permitAll().loginProcessingUrl("/login").failureHandler(customLoginFailureHandler)}
CustomLoginFailureHandler,返回认证失败和失败信息
spring:security:user:name:mingpassword:123456roles:admin0
自定义未登录处理器
配置 authenticationEntryPoint
spring:security:user:name:mingpassword:123456roles:admin1
CustomAuthenticationEntryPoint
spring:security:user:name:mingpassword:123456roles:admin2
自定义权限不足处理器
配置 accessDeniedHandler
spring:security:user:name:mingpassword:123456roles:admin3
CustomAccessDeniedHandler
spring:security:user:name:mingpassword:123456roles:admin4
自定义注销成功逻辑
配置 logoutSuccessHandler
spring:security:user:name:mingpassword:123456roles:admin5
CustomLogoutSuccessHandler
spring:security:user:name:mingpassword:123456roles:admin6
也可以使用 HttpSecurity 的 addLogoutHandler,配置注销的处理逻辑
自定义 JWT 过滤器
添加 JWT 过滤器到过滤器链
spring:security:user:name:mingpassword:123456roles:admin7
JwtAuthenticationTokenFilter
spring:security:user:name:mingpassword:123456roles:admin8
动态配置 URL 权限
Spring Security 的过滤器链中包含了许多过滤器,其中 FilterSecurityInterceptor 非常重要,完成了主要的鉴权逻辑
beforeInvocation 方法
attemptAuthorization
从源码可以看出,动态配置 URL 权限有两种途径
自定义 SecurityMetadataSource,从数据源加载 ConfigAttribute
spring:security:user:name:mingpassword:123456roles:admin9
由于 SecurityConfig.createList 返回的是 SecurityConfig 类型的 ConfigAttribute,默认使用的 WebExpressionVoter 投票器用于验证 WebExpressionConfigAttribute 类型,因此还需要配置一个 RoleVoter
WebExpressionConfigAttribute 是指在配置类中通过 HttpSecurity 配置的权限
配置 HttpSecurity
@Configuration@EnableWebSecurity//开启注解设置权限@EnableGlobalMethodSecurity(prePostEnabled=true)publicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{//配置密码加密器@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}//配置认证管理器@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123")).roles("admin").and().withUser("user").password(passwordEncoder().encode("456")).roles("user");}//配置安全策略@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{//设置路径及要求的权限,支持ant风格路径写法http.authorizeRequests()//设置OPTIONS尝试请求直接通过.antMatchers(HttpMethod.OPTIONS,"/**").permitAll().antMatchers("/api/demo/user").hasAnyRole("user","admin")//注意使用hasAnyAuthority角色需要以ROLE_开头.antMatchers("/api/demo/admin").hasAnyAuthority("ROLE_admin").antMatchers("/api/demo/hello").permitAll().and()//开启表单登录.formLogin().permitAll().and()//开启注销.logout().permitAll();}}0
自定义一个投票器,在投票器中可以获取 URL,动态加载权限,可参考 RoleVoter
@Configuration@EnableWebSecurity//开启注解设置权限@EnableGlobalMethodSecurity(prePostEnabled=true)publicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{//配置密码加密器@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}//配置认证管理器@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123")).roles("admin").and().withUser("user").password(passwordEncoder().encode("456")).roles("user");}//配置安全策略@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{//设置路径及要求的权限,支持ant风格路径写法http.authorizeRequests()//设置OPTIONS尝试请求直接通过.antMatchers(HttpMethod.OPTIONS,"/**").permitAll().antMatchers("/api/demo/user").hasAnyRole("user","admin")//注意使用hasAnyAuthority角色需要以ROLE_开头.antMatchers("/api/demo/admin").hasAnyAuthority("ROLE_admin").antMatchers("/api/demo/hello").permitAll().and()//开启表单登录.formLogin().permitAll().and()//开启注销.logout().permitAll();}}1
配置 HttpSecurity
@Configuration@EnableWebSecurity//开启注解设置权限@EnableGlobalMethodSecurity(prePostEnabled=true)publicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{//配置密码加密器@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}//配置认证管理器@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123")).roles("admin").and().withUser("user").password(passwordEncoder().encode("456")).roles("user");}//配置安全策略@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{//设置路径及要求的权限,支持ant风格路径写法http.authorizeRequests()//设置OPTIONS尝试请求直接通过.antMatchers(HttpMethod.OPTIONS,"/**").permitAll().antMatchers("/api/demo/user").hasAnyRole("user","admin")//注意使用hasAnyAuthority角色需要以ROLE_开头.antMatchers("/api/demo/admin").hasAnyAuthority("ROLE_admin").antMatchers("/api/demo/hello").permitAll().and()//开启表单登录.formLogin().permitAll().and()//开启注销.logout().permitAll();}}2