spring security 源码分析(1)

前言:本文会结合相应的代码示例及框架源码。

Spring Security是一个安全框架,侧重于为Java应用程序提供身份验证和受权。 Pivotal 团队的背书,再加上如今主流web开发是围绕着Spring框架的其优点相较于其余安全框架优点不言而喻🙃。废话就很少说了,然咱们开始吧。web

首先导入相关依赖(tip:做者开发使用的框架是SpringBoot)spring

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

接下来是代码api

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 当前版本是须要配置编码器的
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //开启身份认证任何请求,任何请求都须要有基本ROLE_USER身份
        //用户还能够进行更细粒度的控制。
        http.authorizeRequests()
                .anyRequest()
                .authenticated();
        //开启表单登陆
        http.formLogin();

    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //使用内存用户信息认证
        auth.inMemoryAuthentication().withUser("baozi")
                //对咱们的明文密码进行加密
                .password(passwordEncoder.encode("888"))
                .roles("USER")
                .and()
                .passwordEncoder(passwordEncoder);
    }
}

而后启动咱们的springboot应用,因为我配置的是anyrequest(),第一次请求没有进行过身边验证因此它会跳转到springboot默认生成的登陆页安全

clipboard.png

而后输入刚才咱们的用户名("baozi")与密码("888");springboot

clipboard.png
简单几步咱们就实现了登陆功能,那其中发生了什么呢?让咱们先看一张流程图(精简版);
clipboard.png
其实springsecurity的核心就是一组过滤器。下面列出的三个过滤器是此次demo涉及到的主要过滤器。我将结合部分源码解读。(tip:方法参数及一些不那么重要的代码我省略了)mvc

首先用户请求咱们的受保护资源,第一次用户请求时没有带任何信息的,因此用户的请求会直接到最后一个拦截器FilterSecurityInterceptor进行访问权限处理。框架

// FilterSecurityInterceptor dofilter()中执行invoke()
public void invoke(){
    ......
    // 这里调用父类的方法进行鉴权
    InterceptorStatusToken token = super.beforeInvocation(fi);
    
    // 这里实际已经调用了咱们的受保护资源
    fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    ......
}

//让咱们展开父类AbstractSecurityInterceptor的beforeInvocation()方法
//tip:该方法内会根据处理结果发布不一样的事件,若是你想作日志的话,能够以此为拓展。
protected InterceptorStatusToken beforeInvocation(){
    ......
    try{
        //决策是否有权限访问咱们的资源,具体在上面SecurityConfig.configure(HttpSecurity http)
        //中配置决策管理器决策失败抛出异常
        this.accessDecisionManager.decide(authenticated, object, attributes);
    }
    catch (AccessDeniedException accessDeniedException) {
        publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                accessDeniedException));
       
        throw accessDeniedException;
    }
    ......
}

异常逐级抛出,最后由ExceptionTranslationFilter捕获。此过滤器将尝试解析异常SpringSecurityException
异常,解析失败直接抛出,最终由springmvc的异常拦截器捕获处理。解析成功将根据不一样的访问失败缘由返回。ide

public void doFilter(){
        
    try{
        chain.doFilter(request, response);
    }catch(){
     //io异常继续抛出
    }
    catch(){
        ......
        //上面省略代码是尝试将异常转换为SpringSecurityException 成功后将进行。
        if(ase != null){
              ......
              handleSpringSecurityException(request, response, chain, ase);
        }
    }
    //非SpringSecurityException继续抛出
    ......

 }
 //此处将根据异常类型或异常信息返回不一样的页面 sendStartAuthentication()负责;
private void handleSpringSecurityException(){
    //跳转到登陆页。
    ex instance AuthenticationException
        sendStartAuthentication(......)
    // 分为匿名用户 跳转到登陆页
    // 与 非匿名用户(被服务端主动拒绝的用户) accessDeniedHandler执行handle();
    //默认的accessDeniedHandler是返回403页面,咱们也能够本身实现accessDeniedHandle接口
    //自定义返回
    ex instance AuthenticationException
        sendStartAuthentication(......) or accessDeniedHandle.handle()
}

而后咱们被引导到了登陆页(本次不考虑非匿名用户被拒绝的状况),此次咱们登陆将带上username password;
还有请求的是/login post方法(tip:可自定义),这个请求将被AbstractAuthenticationProcessingFilter::UsernamePasswordAuthenticationFilter过滤器进行处理。spring-boot

public void doFilter(){
    ......
    //抽象方法交由子类实现,本例为UsernamePasswordAuthenticationFilter
    //内部进行相应的用户验证,成功则将Authentication加入安全上下文。
    authResult = attemptAuthentication(request, response);
    
    ......
    //处理失败跳转到失败的页面,可自定义
    unsuccessfulAuthentication(request, response, failed);

    ......
    
    //处理成功转发到原请求handler,可自定义
    successfulAuthentication(request, response, chain, authResult);
}
}

到此登陆处理流程就完成了。post

总结一下

springsecurity将简单易用的api暴露给咱们,但的"复杂"的流程封装在其中.可能第一次接触的开发者有点懵,可是归根结底只是一组过滤器,弄明白顺序,这也是往后咱们"把玩"这个框架的最大前提。下面我列出了主要的过滤器及顺序,从上到下依次执行。

ChannelProcessingFilter
ConcurrentSessionFilter
SecurityContextPersistenceFilter
LogoutFilter
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
UsernamePasswordAuthenticationFilter
ConcurrentSessionFilter
OpenIDAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
ConcurrentSessionFilter
DigestAuthenticationFilter
org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
SwitchUserFilter

根据不一样的状况加载的过滤器也有所不一样,具体可观察控制台,会有相应的日志报告。

下节我将主要讲解与分析被我一笔带过的AbstractAuthenticationProcessingFilter,好了今天的分享就到这里:-)

相关文章
相关标签/搜索