基本原理

本质:一个过滤器链

1、底层核心过滤器

(1)FilterSecurityInterceptor:一个方法级的权限过滤器,基本位于过滤链的最底部

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    invoke(new FilterInvocation(request, response, chain));
}

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
    if (isApplied(filterInvocation) && this.observeOncePerRequest) {
        //过滤器已经应用于这个请求,而用户希望我们观察到的是已经处理一次请求,所以不需要重新进行安全检查
        filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        return;
    }
    //第一次调用这个请求,所以要进行安全检查
    if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
        filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
    }
    //查看之前的过滤器是否放行
    InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
    try {
        //执行本过滤器的doFilter,表示真正的调用后台的服务
        filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
    }
    finally {
        super.finallyInvocation(token);
    }
    super.afterInvocation(token, null);
}

(2)ExceptionTranslationFilter:一个异常过滤器,处理在认证授权过程中抛出的异常

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    try {
        chain.doFilter(request, response);
    }
    catch (IOException ex) {
        throw ex;
    }
    catch (Exception ex) {
        // Try to extract a SpringSecurityException from the stacktrace
        Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
        RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
            .getFirstThrowableOfType(AuthenticationException.class, causeChain);
        if (securityException == null) {
            securityException = (AccessDeniedException) this.throwableAnalyzer
                .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        }
        if (securityException == null) {
            rethrow(ex);
        }
        if (response.isCommitted()) {
            throw new ServletException("Unable to handle the Spring Security Exception "
                                       + "because the response is already committed.", ex);
        }
        handleSpringSecurityException(request, response, chain, securityException);
    }
}

(3)UsernamePasswordAuthenticationFilter:拦截 /login 的 POST 请求,校验表单中用户名和密码

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    throws AuthenticationException {
    //判断是否为POST请求
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    //获取用户名
    String username = obtainUsername(request);
    username = (username != null) ? username.trim() : "";
    //获取密码
    String password = obtainPassword(request);
    password = (password != null) ? password : "";
    //校验
    UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
                                                                                                          password);
    //允许子类设置details属性
    setDetails(request, authRequest);
    return this.getAuthenticationManager().authenticate(authRequest);
}

2、加载过滤器流程

(1)DelegatingFilterProxy 类

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    //如果有必要,可以懒加载Filter代理
    Filter delegateToUse = this.delegate;
    if (delegateToUse == null) {
        synchronized (this.delegateMonitor) {
            delegateToUse = this.delegate;
            if (delegateToUse == null) {
                WebApplicationContext wac = findWebApplicationContext();
                if (wac == null) {
                    throw new IllegalStateException("No WebApplicationContext found: " +
                                                    "no ContextLoaderListener or DispatcherServlet registered?");
                }
                //获取将要使用的代理
                delegateToUse = initDelegate(wac);
            }
            this.delegate = delegateToUse;
        }
    }

    //让delegateToUse执行实际doFilter操作
    invokeDelegate(delegateToUse, request, response, filterChain);
}

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    //内置过滤器:FilterChainProxy
    String targetBeanName = getTargetBeanName();
    Assert.state(targetBeanName != null, "No target bean name set");
    //从容器中获取代理
    Filter delegate = wac.getBean(targetBeanName, Filter.class);
    if (isTargetFilterLifecycle()) {
        delegate.init(getFilterConfig());
    }
    return delegate;
}

(2)FilterChinaProxy 类

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
    if (!clearContext) {
        doFilterInternal(request, response, chain);
        return;
    }
    try {
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        //内部过滤
        doFilterInternal(request, response, chain);
    }
    catch (RequestRejectedException ex) {
        this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
    }
    finally {
        SecurityContextHolder.clearContext();
        request.removeAttribute(FILTER_APPLIED);
    }
}

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
    HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
    //收集Spring Security所有Filter到过滤链中
    List<Filter> filters = getFilters(firewallRequest);
    if (filters == null || filters.size() == 0) {
        if (logger.isTraceEnabled()) {
            logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
        }
        firewallRequest.reset();
        chain.doFilter(firewallRequest, firewallResponse);
        return;
    }
    if (logger.isDebugEnabled()) {
        logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
    }
    VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
    virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}

private List<Filter> getFilters(HttpServletRequest request) {
    int count = 0;
    for (SecurityFilterChain chain : this.filterChains) {
        if (logger.isTraceEnabled()) {
            logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
                                           this.filterChains.size()));
        }
        if (chain.matches(request)) {
            return chain.getFilters();
        }
    }
    return null;
}

 

UserDetailsService 接口

1、没有配置时,Spring Security 定义生成账号和密码;实际项目中,由数据库查询账号和密码,需要自定义逻辑控制、逻辑认证

2、用户认证时,继承 UsernamePasswordAuthenticationFilter 类,重写 attemptAuthentication(验证)、successfulAuthentication(验证成功)、unsuccessfulAuthentication(验证失败)

3、查询数据库时,实现 UserDetailsService 接口

public interface UserDetailsService {

    /*
    根据用户名找到用户。在实际执行中,搜索可能是区分大小写的,也可能是不区分大小写的,这取决于执行实例是如何配置的。在这种情况下,返回的UserDetails对象可能有一个与实际请求的不同大小写的用户名
    形参:
    username - 识别需要数据的用户的用户名
    返回值:
    一个完整的用户记录(不能为空)
    抛出:
    UsernameNotFoundException - 如果找不到用户或者用户没有GrantedAuthority的话
    */
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

}

(1)返回 User 对象,实现 UserDetails 接口,该 User 对象由 Spring Security 提供

public class User implements UserDetails, CredentialsContainer

(2)UserDetails 接口

public interface UserDetails extends Serializable {

    /*
    返回授予该用户的权限,不能返回空值
    返回值:
    按自然键排序的权限(不能为空)
    */
    Collection<? extends GrantedAuthority> getAuthorities();

    /*
    返回用于验证用户身份的密码
    返回值:
    密码
    */
    String getPassword();

    /*
    返回用于验证用户的用户名,不能返回空值
    返回值:
    用户名(不能为空)
    */
    String getUsername();

    /*
    表示用户的账户是否已经过期,一个过期的账户不能被认证
    返回值:
    如果用户的账户是有效的(即未过期),则为true;如果不再有效(即过期),则为false
    */
    boolean isAccountNonExpired();

    /*
    表示用户是被锁定还是未被锁定,一个被锁定的用户不能被认证
    返回值:
    如果用户没有被锁定,则为true,否则为false
    */
    boolean isAccountNonLocked();

    /*
    表示用户的凭证(密码)是否过期,过期的凭证会阻止认证
    返回值:
    如果用户的凭证有效(即未过期),则为true;如果不再有效(即过期),则为false
    */
    boolean isCredentialsNonExpired();

    /*
    表示该用户是启用还是禁用,一个禁用的用户不能被认证
    返回值:
    如果用户被激活,则为true,否则为false
    */
    boolean isEnabled();

}

 

PasswordEncoder 接口

1、密码加密接口,加密返回 User 对象的密码

public interface PasswordEncoder {

    /*
    对原始密码进行编码。一般来说,一个好的编码算法适用于SHA-1或更大的哈希值,并结合一个8字节或更大的随机生成的盐值
    */
    String encode(CharSequence rawPassword);

    /*
    验证从存储区获得的编码密码,与提交的原始密码在编码后是否一致。如果密码匹配则返回true,如果不匹配则返回false。存储的密码本身不会被解码
    形参:
    rawPassword - 要编码和匹配的原始密码
    encodedPassword - 与存储的编码密码进行比较
    返回值:
    如果编码后的原始密码与存储的编码密码匹配,则为true
    */
    boolean matches(CharSequence rawPassword, String encodedPassword);

    /*
    如果编码后的密码应该再次编码以提高安全性,则返回true,否则返回false。默认实现总是返回false。
    形参;
    encodedPassword - 要检查的编码密码
    返回值:
    如果为了更好的安全,应该重新编码密码,则返回true,否则返回false
    */
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }

}
posted @   半条咸鱼  阅读(107)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示