- 前提
使用spring-boot(1.5.10.RELEASE) 和spring-security(4.2.4.RELEASE)作为依赖环境
通过maven构建项目 ,idea开发环境
- 构建项目
通过spring initializr向导,选择需要的模块后新建项目,但是要注意,这种方式构建的项目使用的都是最新的jar,项目新建完成后将pom进行适当的修改,引入 以下两个依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.10.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>4.2.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
不通过parent来处理依赖库的版本问题 ,这样我觉得更灵活,可以解决多模块的parent问题。
- 启动项目
现在可以启动项目了,但是由于没有服务,没有界面,所以可能效果表现上不是很好,建一个controller
@RestController
public class SecController {
@GetMapping("/hello")
public String hello(){
return "hello security!";
}
}
启动项目后访问 http://localhost:8080/hello
进入这样一个界面,同时看到请求变成了login,为什么这样,之后解释,
输入用户名:user,密码:项目 启动是控制台可见
登录后则进入了预想效果:
- 分析
首先我们要知道spring-security实现网络资源的权限是通过Filter实现,而对接口的权限的访问控制则是通过AOP。在我们发生请求/hello时,由于没有认证,被过滤器拦截。在启动日志中可以看到这样一句话:
Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@5073b38b, org.springframework.security.web.context.SecurityContextPersistenceFilter@dbd5b3d, org.springframework.security.web.header.HeaderWriterFilter@4f8256c6, org.springframework.security.web.csrf.CsrfFilter@3006ebb, org.springframework.security.web.authentication.logout.LogoutFilter@7a1f1a0, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@21db4147, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@268f3fa9, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@4efca98c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2c67ca1d, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@7d92a672, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@12eb1f47, org.springframework.security.web.session.SessionManagementFilter@7107591f, org.springframework.security.web.access.ExceptionTranslationFilter@5ff8d4d0, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@d3c5876]
主要说明了系统启动时,默认注册了哪些过滤器,那么在我们执行请求的时候,则会按照这个先后顺序执行,为什么最终会定位到/login这样一个请求并返回那样的登录界面呢?
通过断点发现,在没有认证时,认证过程中也就是经过上面的过滤器时出现异常,进而被ExceptionTranslationFilter拦截并处理处理,而实际的吹过程其实是由AuthenticationEntryPoint完成,默认情况配置了LoginUrlAuthenticationEntryPoint和BasicAuthenticationEntryPoint,可以看到相关的处理逻辑,最终其实是在LoginUrlAuthenticationEntryPoint处理,完成跳转到登录页面的处理。
那么具体的处理流程是怎样的呢?
1、用户通过浏览器发送请求,如果没有认证则交由AuthenticationEntryPoint处理;如果认证但无权限交由AccessDeniedHandler处理
2、经过上一步处理后,一般都会重定向到登录界面,需要注意的是,默认情况会下,/login对应的登录界面是由系统生成,见DefaultLoginPageConfigurer
3、此时用户输入用户名密码(或者认证id)
4、经由AbstractAuthenticationProcessingFilter,具体由哪一个类来处理,取决于我们在表单提交时的请求路径,比如默认情况我们提交【/logtin POST】,那么则会交由UsernamePasswordAuthenticationFilter,件构造器new AntPathRequestMatcher("/login", "POST"))
5、调用attemptAuthentication方法,构建Authentication,同样默认实现JaasAuthenticationToken,其实也就是用户令牌,主要包含了用户身份、证明、权限等信息,当然还有一个关键点则是UserDetails。
6、之后则是通过AuthenticationManager找到对应实现ProviderManager(默认 ),进而通过AuthenticationProvider找到适配对应Authentication类型的处理实现,最终通过UserDetailsService通过用户名构建UserDetails并设置到Authentication中。
7、最后则是在AuthenticationManager中完成用户的身份认证。
8、而权限验证则是通过AccessDecisionManager来完成,可以看到其实现类,提供了三种验证策略。