为什么越来越多程序员使用SpringSecurity,这些原因你都知道吗?

为什么越来越多程序员使用SpringSecurity,这些原因你都知道吗?

1|2 什么是安全框架

安全框架顾名思义,就是解决系统安全问题的框架。任何应用开发的计划阶段都应该确定一组特定的安全需求,如身份验证、授权和加密方式。不使用安全框架之前,我们需要手动处理每个资源的访问控制,针对不同的项目都需要做不同的处理,此时就会显得非常麻烦,并且低效率引起的额外开销会延缓开发周期。使用安全框架,使开发团队能够选择最适合这些需求的框架,可以通过配置的方式实现对资源的访问限制,使得开发更加的高效。

1|2常用的安全框架

Spring Security: Spring 家族一员,是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC(控制反转)、DI(依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Apache Shiro:一个功能强大且易于使用的Java安全框架,提供了认证、授权、加密和会话管理功能。使用 Shiro 的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

1|3SpringSecurity 简介

Spring Security 是一个高度自定义的安全框架,利用 Spring loC、DI 和 AOP 功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大星重复代码的工作。

使用 Spring Secruity 的原因有很多,但大部分都是发现了 javaEE 的 Servlet 规范或 EJ8 规范中的安全功能缺乏典型企业应用场景。同时认识到他们在WAR 或 EAR 级别无法移植,因此如果你更换服务器环境,还有大星工作去重新配置你的应用程序,使用 Spring Security 解决了这些问题,并且提供了可定制的安全功能,比如认证和授权。

SpringBoot 没有发布之前,Shiro 应用更加广泛,因为 Shiro 是一个强大且易用的 Java 安全框架,能够非常清晰的处理身份验证、授权、管理会话以及密码加密。利用其易于理解的API,可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。但是 Shiro 只是一个框架而已,其中的内容需要自己的去构建,前后是自己的,中间是Shiro帮我们去搭建和配置好的。

SpringBoot 发布后,随着其快速发展,Spring Security 重新进入人们的视野。SpringBoot 解决了 Spring Security 各种复杂的配置,Spring Security 在我们进行用户认证以及授予权限的时候,通过各种各样的拦截器来控制权限的访问,从而实现安全,也就是说 Spring Security 除了不能脱离 Spring,Shiro 的功能它都有。

在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。1|4SpringSecurity 入门

引入依赖

org.springframework.boot

spring-boot-starter-security

导入配置

@Configuration

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity httpSecurity) throws Exception {

httpSecurity

// 禁用csrf(跨站请求伪造)

.cors().and().csrf().disable()

// 设置表单登陆以及登录页面,自动开启登录页,如果没有登录,没有权限就会来到登录页面

.formLogin().loginPage("/login.html").and()

// 过滤请求

.authorizeRequests()

// 访问此地址不需要进行身份认证,允许直接访问,防止重定向死循环

.antMatchers("/login.html").permitAll()

// 除上面外的所有请求全部需要鉴权认证,访问任何资源都需要身份认证

.anyRequest().authenticated();

}

}

登录测试

启动服务之后,如果只实现一个 WebSecurityConfigurerAdapter 然后重写一下 configure 方法,效果会默认使用Spring Security 的登录页 ,同时项目启动时后台会打印出一个默认的密码,然后使用任意账号就可以进行登录访问指定的资源。

2|0SpringSecurity 核心

2|1基本原理

Spring Security 所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源,并且采用的是责任链的设计模式。

Spring Security 对 Web 资源的保护是靠过滤器链(Filter Chain)实现的。当初始化 Spring Security 时,会创建一个名为 springSecurityFilterChain 的 Servlet 过滤器链,类型 FilterChainProxy,它实现 Filter,因此外部的请求会经过此类,下图是 Spring Security 过虑器链结构图:

FilterChainProxy 是一个代理,真正起作用的是 FilterChainProxy 中 SecurityFilterChain 所包含的各个 Filter,同时这些 Filter 作为 Bean 被 Spring 管理,它们是 Spring Security 核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理。

Spring Security 功能的实现主要是由一系列过滤器链相互配合完成,如下图:

下面介绍过滤器链中主要的几个过滤器及其作用:

WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。SecurityContextPersistenceFilter :每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除。UsernamePasswordAuthenticationFilter :用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变。HeaderWriterFilter:用于将头信息加入响应中。CsrfFilter:用于处理跨站请求伪造。LogoutFilter:用于处理退出登录。DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。BasicAuthenticationFilter:检测和处理 http basic 认证。RequestCacheAwareFilter:用来处理请求的缓存。SecurityContextHolderAwareRequestFilter:主要是包装请求对象 request。AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。SessionManagementFilter:管理 session 的过滤器ExceptionTranslationFilter:处理 AccessDeniedException 和 AuthenticationException 异常。FilterSecurityInterceptor: 是用于保护web资源的,使用 AccessDecisionManager 对当前用户进行授权访问。RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的 remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。下面看一下 Spring Security 整个执行流程图,只要把 Spring Security 的执行过程弄明白了,这个框架就会变得很简单:

流程说明:

客户端发起一个请求,进入 Security 过滤器链。当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层,否则到 AccessDeniedHandler 鉴权失败处理器处理。2|2核心配置

系统集成 Spring Security 后,通过创建配置类并继承 WebSecurityConfigurerAdapter 类,这个类里面可以完成上述流程图的所有配置,也就是认证及授权。接下来我们通过一个完整的配置类来进行详细认识:

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

/**

* 自定义用户认证逻辑

*/

@Autowired

private UserDetailsService userDetailsService;

/**

* 认证失败处理类

*/

@Autowired

private AuthenticationEntryPointImpl unauthorizedHandler;

/**

* 退出处理类

*/

@Autowired

private LogoutSuccessHandlerImpl logoutSuccessHandler;

/**

* 跨域过滤器

*/

@Autowired

private CorsFilter corsFilter;

/**

* 资源请求配置

*/

@Override

protected void configure(HttpSecurity httpSecurity) throws Exception {

// ...

}

/**

* 全局安全性配置

*/

public void configure(WebSecurity web) {

web.ignoring().antMatchers(new String[]{"/v3/api-docs", "/swagger-resources/configuration/ui",

"/swagger-resources", "/swagger-ui.html", "/swagger-ui/*", "/modeler/**", "/**/doc.html",

"/favicon.ico", "/definition/**", "/activiti/**", "/**/*.css", "/**/*.js", "/**/*.png",

"/**/*.gif", "/swagger-resources/**", "/**/*.ttf", "/upload/**", "/process/read-resource/**",

"/ueditor/**", "/**/export/**", "/**importGdCache/**", "/**/sysGlobalConfig/**", "/OAuth/**", "/v1/**"});

}

/**

* 身份认证接口

*/

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());

}

configure(AuthenticationManagerBuilder auth):身份认证接口

AuthenticationManager 的建造器,配置 AuthenticationManagerBuilder 会让 Spring Security 自动构建一个 AuthenticationManager(该类的功能参考流程图);

如果想要使用该功能你需要配置一个 UserDetailService 和 PasswordEncoder。UserDetailsService 用于在认证器中根据用户传过来的用户名查找一个用户, PasswordEncoder 用于密码的加密与比对,我们存储用户密码的时候用PasswordEncoder.encode() 加密存储,在认证器里会调用 PasswordEncoder.matches() 方法进行密码比对。

如果重写了该方法,Spring Security 会启用 DaoAuthenticationProvider 这个认证器,该认证就是先调用 UserDetailsService.loadUserByUsername 然后使用 PasswordEncoder.matches() 进行密码比对,如果认证成功成功则返回一个 Authentication 对象。

configure(WebSecurity web):全局安全性配置

此方法用于配置影响全局安全性的配置,比如配置资源,设置调试模式,通过实现自定义防火墙定义拒绝请求,一般用于配置全局的某些通用事物,比如静态资源等。

configure(HttpSecurity http):资源请求配置

这个方法是整个 Spring Security 的核心,也是最复杂的部分,通过案例简单的说明一些常用配置,详细说明会在权限控制中说明。

@Configuration

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.formLogin().loginPage("/login_page") // 自定义登录页

.passwordParameter("username") // 用户名属性名

.passwordParameter("password") // 密码属性名

.loginProcessingUrl("/sign_in") // 登录请求路径

.permitAll(); // 代表任意用户可访问

.cors().and()).csrf().disable()) // 禁用csrf(跨站请求伪造)

.authorizeRequests() // 过滤请求

.antMatchers(new String[]{"/upload/**", "/definition/**", "/activiti/**"}).permitAll() // 放行匹配请求

.anyRequest().authenticated().and() //除上面外的所有请求全部需要鉴权认证

.exceptionHandling().accessDeniedHandler((AccessDeniedHandler) this.accessDeniedHandler); // 异常解析器

http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); // 退出登录

}

}

2|3权限控制

匹配规则

anyRequest():表示匹配所有的请求,一般情况都会使用此方法,设置全部内容都需要进行认证,比如anyRequest().authenticated();。antMatcher(String... antPatterns): 表示匹配指定的请求,参数是不定向参数,每个参数是一个 ant 表达式,? 表示匹配一个字符,* 表示匹配 0~N 个字符,** 表示匹配 0~N 个目录。例如 antMatchers( "/**/*.js").permitAll()。regexMatchers(String... regexPatterns):使用正则表达式进行匹配,与 antMatchers() 主要的区别就是参数,antMatchers()参数是 ant 表达式,二 regexMatchers() 参数是正则表达式。例如 .regexMatchers( ".+[.]js").permitAll()。mvcMatchers():适用于配置了 servletPath 的情况。.servletPath() 是 mvcMatchers() 返回值特有的方法,例如 .mvcMatchers( "demo").servletPath( "/bjsxt").permitAll() 等价于 antMatchers( "/bjsxt/demo").permitAll()。访问控制

permitAll():表示所匹配的 URL 任何人都允许访问,也就是不需要认证,随意访问。anonymous():表示可以匿名访问匹配的 URL,只是设置为 anonymous() 的 URL 会执行 filter 链中,比如说浏览商城时。authenticated():表示所匹配的 URL 都需要被认证才能访问,也就是用户登录后可访问。denyAll():表示所匹配的 URL 都不允许被访问。rememberMe():只有被 remember me 的用户才能访问。fullyAuthenticated():如果用户不是被 remember me 的,才可以访问。角色控制

hasRole():用户具备某个角色即可访问资源,此方法会自动给传入的字符串加上 ROLE_ 前缀,例如 hasRole("list"),表示拥有 ROLE_list 权限即可访问。hasAnyRole():用户具备多个角色中的任意一个即可访问资源,例如 hasAnyRole("admin", "save"),只要具备其中一个角色,即可访问资源。hasAuthority():类似于 hasRole,但是不会添加 ROLE_ 前缀,也就是说,使用 hasAuthority 更具有一致性,你不用考虑要不要加 ROLE_ 前缀,数据库什么样这里就是什么样!hasAnyAuthority():类似于 hasAnyRole,只是没有前缀。注解控制

当我们使用注解之前,必须通过 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) 开启注解。

@PreAuthorize:方法执行前进行权限检查,允许使用 SpEL(pring表达式语言)。@PostAuthorize:方法执行后进行权限检查,基本不用。@Secured:类似于 @PreAuthorize,但是不允许使用 SpEL(pring表达式语言)。例如:

@Controller

public class HelloController {

// 只有当前登录用户名为 javaboy 的用户才可以访问该方法。

@PreAuthorize("principal.username.equals('javaboy')")

public String hello() {

return "hello";

}

// 表示访问该方法的用户必须具备 admin 角色

@PreAuthorize("hasRole('admin')")

public String admin() {

return "admin";

}

// 表示访问该方法的 age 参数必须大于 98,否则请求不予通过。

@PreAuthorize("#age>98")

public String getAge(Integer age) {

return String.valueOf(age);

}

// 表示该方法的用户必须具备 user 角色,但是注意 user 角色需要加上 ROLE_ 前缀。

@Secured({"ROLE_user"})

public String user() {

return "user";

}

}

2|4请求认证

让我们仔细分析认证过程:

当用户发送登录请求的时候,首先进入到 UsernamePasswordAuthenticationFilter 中进行校验。UsernamePasswordAuthenticationFilter 通过 attemptAuthentication 方法会获取用户的username以及password参数的信息,封装为 UsernamePasswordAuthenticationToken 对象,最后会进入 AuthenticationManager 接口的实现类 ProviderManager 中。AuthenticationManager(认证管理器) 本身不包含验证的逻辑,它的作用是用来管理 AuthenticationProvider。进入 ProviderManager 类调用 authenticate() 方法,通过循环遍历判断它是否支持这种登录方式,具体的登录方式有表单登录,qq登录,微信登录等。如果支持则会进入A uthenticationProvider 接口的抽象实现类 AbstractUserDetailsAuthenticationProvider 中调用 authenticate() 方法对用户的身份进入校验。进入 AbstractUserDetailsAuthenticationProvider 的 authenticate方法之后,UserDetail 的 user 对象是否为空,如果为空,表示还没有认证,就需要调用 DaoAuthenticationProvider 类的 retrieveUser 方法去获取用户的信息。该扩展类的 retrieveUser 方法中调用 UserDetailsService 这个接口的实现类的 loadUserByUsername 方法去获取用户信息。如果需要自定义实现,则可以实现 UserDetails 接口,编写自己的逻辑,从数据库中获取用户密码等权限信息返回。获取到用户信息之后,返回到 AbstractUserDetailsAuthenticationProvider 类中调用 createSuccessAuthentication() 方法,通过 PasswordEncoder 对比用户信息是否与 AuthenticationManager 一直,如果一直,则认证通过(setAuthenticated(true))。认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息, 身份信息,细节信息,但密码通常会被移除) Authentication 实例。2|5请求授权

让我们仔细分析授权过程:

已认证用户访问受保护的 web 资源将被 SecurityFilterChain 中的 FilterSecurityInterceptor 的子类拦截。FilterSecurityInterceptor 会从 SecurityMetadataSource 的 getAttributes() 方法,获取要访问当前资源所需要的权限。其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则。FilterSecurityInterceptor 会调用 AccessDecisionManager(授权决策器)的 decide() 方法进行授权决策,若决策通过,则允许访问资源,否则将禁止访问

相关文章

手机录音剪辑技巧:轻松编辑你的录音文件
365体育投注网

手机录音剪辑技巧:轻松编辑你的录音文件

07-31 阅读: 9732
曾用中文感谢“冰丝带”工作人员的小平奈绪宣布10月退役
光盘修复问题大全
365体育投注网

光盘修复问题大全

07-14 阅读: 2754
IM即时通讯软件
365体育投注网

IM即时通讯软件

10-11 阅读: 6695
2025年[郑州]登封市人口第七次人口普查有多少和普查人口数据
全面解析电脑机器码:定义、作用及获取方法