spring-security使用-更友好的方式扩展登录AuthenticationProvider(三) 说明 接口定义 使用AuthenticationProvider自定义登录逻辑 原理 

在 我们使用的是重写了Spring-security的filter的方式来进行自定义,但是这样的弊端,就是侵入太大。直接把spring-security的filter给替换掉了,

通过AuthenticationProvider的方式是在spring-security的filter内部留的扩展点进行扩展自定义登录逻辑

接口定义

AuthenticationProvider

public interface AuthenticationProvider {
    /**
     * 验证用户身份
     * @param var1
     * @return
     * @throws AuthenticationException
     */
    Authentication authenticate(Authentication var1) throws AuthenticationException;

    /**
     * supports 则用来判断当前的 AuthenticationProvider 是否支持对应的 Authentication。
     * @param var1
     * @return
     */
    boolean supports(Class<?> var1);
}

Authentication

封装用户身份信息

public interface Authentication extends Principal, Serializable {
    //用来获取用户的权限。
    Collection<? extends GrantedAuthority> getAuthorities();
    //方法用来获取用户凭证,一般来说就是密码。
    Object getCredentials();
    //方法用来获取用户携带的详细信息,可能是当前请求之类的东西。
    Object getDetails();
    //方法用来获取当前用户,可能是一个用户名,也可能是一个用户对象。
    Object getPrincipal();
    //当前用户是否认证成功。
    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

类图

spring-security使用-更友好的方式扩展登录AuthenticationProvider(三)
说明
接口定义
使用AuthenticationProvider自定义登录逻辑
原理 

 

UsernamePasswordAuthenticationFilter 用的就是UserNamePasswordAuthenticationToken

使用AuthenticationProvider自定义登录逻辑

1.增加自定义provider继承DaoAuthenticationProvider 

public class CodeAuthenticationProvider extends DaoAuthenticationProvider {
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)   {
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String code = req.getParameter("code");
        String verify_code = (String) req.getSession().getAttribute("verify_code");
        if (code == null || verify_code == null || !code.equals(verify_code)) {
            throw new AuthenticationServiceException("验证码错误");
        }
        super.additionalAuthenticationChecks(userDetails, authentication);
    }
}

2.在public class SecurityConfig extends WebSecurityConfigurerAdapter 类增加以下

 /**
     * 对密码进行加密的实例
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        /**
         * 不加密所以使用NoOpPasswordEncoder
         * 更多可以参考PasswordEncoder 的默认实现官方推荐使用: BCryptPasswordEncoder,BCryptPasswordEncoder
         */
        return NoOpPasswordEncoder.getInstance();
    }
    /**
     * 自定义provider
     * @return
     */
   public CodeAuthenticationProvider codeAuthenticationProvider() {
        CodeAuthenticationProvider myAuthenticationProvider = new CodeAuthenticationProvider();
        //设置passorderEncoder
        myAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        //设置UserDetailsService 可以参考第一篇登录使用例子自定义userDetailServices
        myAuthenticationProvider.setUserDetailsService(userService);
        return myAuthenticationProvider;
    }

    /**
     * 重写父类自定义AuthenticationManager 将provider注入进去
     * 当然我们也可以考虑不重写 在父类的manager里面注入provider
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        ProviderManager manager = new ProviderManager(Arrays.asList(codeAuthenticationProvider()));
        return manager;
    }

原理 

spring-security使用-登录(一) 我们替换了默认的UsernamePasswordAuthenticationFilter

1.根据看这个类UsernamePasswordAuthenticationFilter我们可以看出他的父类实现了Servilet Fitler,所以spring-security是基于Filter的

org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter 父类实现

  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
                 //省略部分代码......
                //调用子类的实现 也就是 UsernamePasswordAuthenticationFilter
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }
        }
    }

2.看子类实现

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication

 public org.springframework.security.core.Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //注意这里不支持post请求,如果我们要让支持post请求,就要重写filter吧这里去掉
        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 封装
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            //委托给AuthenticationManager 执行 我们上面重写用的是ProviderManager
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

3.providerManger实现

org.springframework.security.authentication.ProviderManager#authenticate

    public org.springframework.security.core.Authentication authenticate(org.springframework.security.core.Authentication authentication) throws AuthenticationException {
        //获得Authentication的类型
        Class<? extends org.springframework.security.core.Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        org.springframework.security.core.Authentication result = null;
        org.springframework.security.core.Authentication parentResult = null;
        boolean debug = logger.isDebugEnabled();
        //获得所有的Providers 就是我们定义的CodeAuthenticationProvider
        Iterator var8 = this.getProviders().iterator();
        while(var8.hasNext()) {
            //迭代器迭代获取
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            //判断是否能处理
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                    //调用provider的authenticate 执行身份认证
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (AuthenticationException var14) {
                    lastException = var14;
                }
            }
        }
      //省略部分代码.......
    }