欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Security 认证流程

程序员文章站 2022-05-05 15:16:26
...

Spring Security 认证流程

继承WebSecurityConfigurerAdapter ,在其中进行安全认证的配置

  1. CustomizeAuthenticationFilter extends UsernamePasswordAuthenticationFilter
    判断数据格式是否为json格式.如果为json格式,则执行自定义解析,否则执行父类默认的解析.
    过程:
    解析登录表单中的账户名密码,封装到Spring Security的UsernamePasswordAuthenticationToken(principal,credentials),并且调用父类AbstractAuthenticationToken的构造器,设置权限为null,setAuthenticated(false)(即未认证).然后调用AuthenticationManager的authenticate()进行验证.
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    } else {
        String username = this.obtainUsername(request);
        String password = this.obtainPassword(request);
        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

2.ProviderManager
进入AuthenticationManager的实现类ProviderManager中.首先遍历自身支持的登录方式(AuthenticationProvider),确认是否支持UsernamePasswordAuthenticationToken的登录方式.
只有登录方式被支持,才会进行验证.进入
AuthenticationProvider.authenticate()(子类AbstractUserDetailsAuthenticationProvider).


public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    Authentication result = null;
    boolean debug = logger.isDebugEnabled();
    Iterator var6 = this.getProviders().iterator();

    while(var6.hasNext()) {
        AuthenticationProvider provider = (AuthenticationProvider)var6.next();
        if (provider.supports(toTest)) {  //验证是否支持登录方式
            if (debug) {
                logger.debug("Authentication attempt using " + provider.getClass().getName());
            }

            try {
                result = provider.authenticate(authentication);  //进入身份验证
                if (result != null) {
                    this.copyDetails(authentication, result);
                    break;
                }
            } catch (AccountStatusException var11) {
                this.prepareException(var11, authentication);
                throw var11;
            } catch (InternalAuthenticationServiceException var12) {
                this.prepareException(var12, authentication);
                throw var12;
            } catch (AuthenticationException var13) {
                lastException = var13;
            }
        }
    }

3.AbstractUserDetailsAuthenticationProvider

(1)在AbstractUserDetailsAuthenticationProvider的authenticate()中,首先获取UserCache中的UserDetails,如果为空说明未验证.

(2)利用retrieveUser()方法获取用户信息,由DaoAuthenticationProvider获得UserDetailsService,调用loadUserByUsername()获取数据库中的用户信息(该部分非常关键,自定义实现类,注册到验证体系中).

(3)而后对UserDetail进行三步验证.this.preAuthenticationChecks.check(user);验证用户是否锁定,是否过期,是否冻结.

(4)DaoAuthenticationProvider实现additionalAuthenticationChecks(),通过默认的编码格式验证密码.(可以实现PasswordEncoder,自定义密码的编码格式,注册到体系中)

(5)然后this.postAuthenticationChecks.check(user);验证用户密码是否过期.并将其存入缓存中.

(6)createSuccessAuthentication将验证用户信息封装成UsernamePasswordAuthenticationToken.

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
    String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
    boolean cacheWasUsed = true;
    UserDetails user = this.userCache.getUserFromCache(username);
    if (user == null) {
        cacheWasUsed = false;

        try {
//1.获取用户信息
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
        } catch (UsernameNotFoundException var6) {
            this.logger.debug("User '" + username + "' not found");
            if (this.hideUserNotFoundExceptions) {
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }

            throw var6;
        }

        Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
    }

    try {
//2.检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,可用)
        this.preAuthenticationChecks.check(user);
//3.验证密码
        this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
    } catch (AuthenticationException var7) {
        if (!cacheWasUsed) {
            throw var7;
        }

        cacheWasUsed = false;
        user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
        this.preAuthenticationChecks.check(user);
        this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
    }
//4.验证用户密码是否过期
    this.postAuthenticationChecks.check(user);
    if (!cacheWasUsed) {
        this.userCache.putUserInCache(user);
    }

    Object principalToReturn = user;
    if (this.forcePrincipalAsString) {
        principalToReturn = user.getUsername();
    }

    return this.createSuccessAuthentication(principalToReturn, authentication, user);
}

4.AbstractAuthenticationProcessingFilter

验证成功后,进入该类的successfulAuthentication,将认证结果保存起来,然后返回实现AuthenticationSuccessHandler的自定义类AuthenticationSuccessHandlerImpl.
项目中,AuthenticationSuccessHandlerImpl主要移除现有的token,并创建新的token返回给客户端.


protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
    }

    SecurityContextHolder.getContext().setAuthentication(authResult);
    this.rememberMeServices.loginSuccess(request, response, authResult);
    if (this.eventPublisher != null) {
        this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
    }

    this.successHandler.onAuthenticationSuccess(request, response, authResult);
}

总结

UserDetailsService接口作为桥梁,是DaoAuthenticationProvier与特定用户信息来源进行解耦的地方,UserDetailsService由UserDetails和UserDetailsManager所构成;UserDetails和UserDetailsManager各司其责,一个是对基本用户信息进行封装,一个是对基本用户信息进行管理;

特别注意,UserDetailsService、UserDetails以及UserDetailsManager都是可被用户自定义的扩展点,我们可以继承这些接口提供自己的读取用户来源和管理用户的方法

另外:
项目中主要注册了;
拒绝访问处理器:AccessDeniedHandler
认证失败处理器:SimpleUrlAuthenticationFailureHandler
认证成功处理器:AuthenticationSuccessHandler
用户密码编码格式:PasswordEncoder
登出成功处理器:LogoutSuccessHandler
认证失败终点(非登录过程):AuthenticationEntryPoint
请求拦截认证:OncePerRequestFilter
客户登录认证:UsernamePasswordAuthenticationFilter

加载方法:
AuthenticationManagerBuilder --userDetailsService()注册获取用户信息的UserDetailsService


@Override
    protected void configure(HttpSecurity http) throws Exception {
        //@formatter:off
        http
                .csrf().disable().cors()
                .and()
                    .httpBasic().authenticationEntryPoint(unauthorisedEntryPoint)    //注册认证失败终点处理器
                .and()
                    .exceptionHandling()
                    .accessDeniedHandler(authenticationAccessDeniedHandler()) //注册认证失败处理器

                .and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                    .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessHandler(logoutSuccessHandler())//登出
                .and()
                    .authorizeRequests()//配置认证请求
                    //注意顺序!按顺序依次认证
                    .antMatchers("/address").authenticated()

                    .antMatchers("/datastation/**").permitAll()
                .and()
                    .addFilterBefore(tokenAuthenticationFilter, BasicAuthenticationFilter.class)//注册请求拦截认证的策略
                    .addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) //注册登录拦截认证
        ;
        //@formatter:on
    }

@Bean
CustomizeAuthenticationFilter customAuthenticationFilter() throws Exception {
    CustomizeAuthenticationFilter filter = new CustomizeAuthenticationFilter();
//注册认证成功和失败的处理器
    filter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
    filter.setAuthenticationFailureHandler(authenticationFailureHandler());
    filter.setFilterProcessesUrl("/login");//配置登录认证路径
    filter.setAuthenticationManager(authenticationManagerBean());
    return filter;
}

相关标签: spring java