源码分析之Spring Security 和 Shiro 请求处理流程

 

一、Spring Security登录执行流程

 

1、首先用ServletFilter拦截器(AbstractAuthenticationProcessingFilter)

对应UsernamePasswordAuthenticationFilter:

  •     拦截到登录的请求(通常是form Login,比如 /login + POST )

  •     解析出登录信息principal和credentials(对应username和password),封装成Authentication(对应UsernamePasswordAuthenticationToken)

 

2、获得AuthenticationProvider执行provider.authenticate(Authentication authentication)方法

对应DaoAuthenticationProvider:(注意,可以有多个AuthenticationProvider,包括同级的和父子级别,具体参见ProviderManager)

  •     判断是否已经认证过(username是否存在于userCache中);

  •     查询用户username是否存在,然后查询密码是否正确,如果OK,构造一个UserDetails user;

  •     重新构造一个登录成功的UsernamePasswordAuthenticationToken信息,包括调用authoritiesMapper转换user的Authorities信息。

 

如果不想用user+password模式,可以替换上面的UsernamePasswordAuthenticationFilter类。

替换方法为 (addFilterBefore):

httpSecurity

    .authorizeRequests()

    .antMatchers("/""/*.html""/**/*.css""/**/*.js")

    .and()

    .addFilterBefore(myFilter(), BasicAuthenticationFilter.class);

 

二、Shiro安全控制及登录执行流程

首先,它会注册一个主 servlet filter,名字叫 ShiroFilterFactoryBean

PS:下面是我查看一次请求的所有tomcat filter的结果:

[name=corsFilter, filterClass=springweb.filter.CorsFilter], 

[name=characterEncodingFilter, filterClass=springboot.web.servlet.filter.OrderedCharacterEncodingFilter], 

[name=hiddenHttpMethodFilter, filterClass=springboot.web.servlet.filter.OrderedHiddenHttpMethodFilter], 

[name=formContentFilter, filterClass=springboot.web.servlet.filter.OrderedFormContentFilter], 

[name=requestContextFilter, filterClass=springboot.web.servlet.filter.OrderedRequestContextFilter], 

[name=delegatingFilterProxy, filterClass=springweb.filter.DelegatingFilterProxy], 

[name=xssFilter, filterClass=org.jretty.fast.core.xss.XssFilter], 

[name=webStatFilter, filterClass=com.alibaba.druid.support.http.WebStatFilter], 

[name=shiroFilter, filterClass=org.apache.shiro.spring.web.ShiroFilterFactoryBean$SpringShiroFilter]

null

可见,shiroFilter位于所有filter的末尾。

    这个shiroFilter是里面还会注册多个内部的filter(存放在list中),这些filter都会继承抽象类 PathMatchingFilter(继承自 OncePerRequestFilter )。Shiro默认注册的filter如下:

public enum DefaultFilter {

 

    anon(AnonymousFilter.class),

    authc(FormAuthenticationFilter.class),

    authcBasic(BasicHttpAuthenticationFilter.class),

    logout(LogoutFilter.class),

    noSessionCreation(NoSessionCreationFilter.class),

    perms(PermissionsAuthorizationFilter.class),

    port(PortFilter.class),

    rest(HttpMethodPermissionFilter.class),

    roles(RolesAuthorizationFilter.class),

    ssl(SslFilter.class),

    user(UserFilter.class);

}

    在shiro配置时,需要配置拦截的uri,以及对应的filter,例如下面的配置:

filterMap.put("/public/**""anon");

filterMap.put("/*.html""anon");

filterMap.put("/sys/login""anon");

filterMap.put("/favicon.ico""anon");

 

filterMap.put("/sys/logout/**""logout");

filterMap.put("/**""authc");

    如无filterChain的特殊配置,每个filter都是一个独立的filterChain(被shiro封装成ProxiedFilterChain),每个请求,只会被一个filterChain处理,换句话说,在如无filterChain特殊配置,每个请求只会被第一个匹配到的filter处理,后面的filter不会执行。ProxiedFilterChain的处理逻辑如下:

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {

    if (this.filters == null || this.filters.size() == this.index) {

        //we've reached the end of the wrapped chain, so invoke the original one:

        log.trace("Invoking original filter chain.");

        this.orig.doFilter(request, response);

    else {

        log.trace("Invoking wrapped filter at index [" this.index + "]");

        this.filters.get(this.index++).doFilter(request, response, this);

    }

}

    每个请求进来,会尝试依次匹配这些path,如果匹配了,对应的filterChain就会执行。通常filterChain里面只有一个filter,比如这个名为 anno的filter,它执行完之后,就会调用 servlet的original filter,从而执行后面的其他servlet filter,根据我前面给出的servlet filter列表,shiroFilter已经是最后一个filter,所以最终这个url就会顺利地通过shiro,然后被执行。anno filter的代码如下:

public class AnonymousFilter extends PathMatchingFilter {

 

    @Override

    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {

        // Always return true since we allow access to anyone

        return true;

    }

 

}

    可见它是十分高效的,像这样的配置:filterMap.put("/public/**", "anon"),这个/public/下面的内容,会快速地通过shiro,shiro直接返回true,不会对它做任何处理。

 

    好了,明白上面的原理后,登录流程就容易说明了,shiro默认有一个filter,叫FormAuthenticationFilter,它会去匹配登录的uri,一旦拦截就会执行。这个FormAuthenticationFilter的流程,就不多说了,无非就是从Http Request中 提取出 username和password,然后执行login逻辑,如下所示:

protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {

    AuthenticationToken token = createToken(request, response);

    if (token == null) {

        String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +

                "must be created in order to execute a login attempt.";

        throw new IllegalStateException(msg);

    }

    try {

        Subject subject = getSubject(request, response);

        subject.login(token);

        return onLoginSuccess(token, subject, request, response);

    catch (AuthenticationException e) {

        return onLoginFailure(token, e, request, response);

    }

}

    首先,把username和password封装成 AuthenticationToken,然后提交给 Subject实现类(DelegatingSubject)去执行login,这个方法实际上是这个类的:

public class DefaultSecurityManager extends SessionsSecurityManager {...}

    这个类会调用 RealmSecurityManager 去验证账号密码,成功之后会返回一个携带成功信息(SubjectContext)的Subject类。

 

    需要注意的是,Subject恐怕是Shiro中最重要的设计,DelegatingSubject类有个非常重要的方法叫 getSession(),它会调用 SessionManager 去创建或者获取session,注意这个Session是 shiro Session(org.apache.shiro.session.Session)并不是 tomcat session(org.apache.catalina.Session),shiro默认的实现类是DefaultWebSessionManager,它是将session存于客户端的cookie中,因此它跟 tomcat的session,没有一点关系。

    PS:通常的session实现方式有三种:

  1. 使用web服务器自带的session存储(是在内存中的,可以选择持久化到本地文件中的)。

  2. 使用外挂的存储来保存session(比如redis、甚至数据库)

  3. 使用客户端的cookie来保存session

    因为这个Shiro Session的设计,我还踩过一个坑:我用的Keycloak单点登录平台,它是基于服务器Session来控制的,登录时,会在服务器Session中记录登录信息,单点退出时,会通知所有服务器清除Session,然而由于Shiro使用的是自己的独立的session,它根本感知不到服务器的session状态,所以它不能单点退出。

 

    题外话:查看tomcat的Request源码(org.apache.catalina.connector.Request),我发现 上面shiro的Subject,就相当于 tomcat的这个 Request,例如Request有个方法也是获取session的:

public class Request implements HttpServletRequest {

 

    protected Session doGetSession(boolean create) {

        ...

        session = manager.findSession(requestedSessionId);

        ...

    }

     

    /**

     * @return the principal that has been authenticated for this Request.

     */

    public Principal getUserPrincipal() {

        if (userPrincipal instanceof TomcatPrincipal) {

        ...

        return userPrincipal;

    }

     

    /**

     * @return <code>true</code> if the authenticated user principal

     * possesses the specified role name.

     *

     * @param role Role name to be validated

     */

    @Override

    public boolean isUserInRole(String role) {

        Realm realm = context.getRealm();

        if (realm == null) {

            return false;

        }

        // Check for a role defined directly as a <security-role>

        return realm.hasRole(getWrapper(), userPrincipal, role);

    }

     

    @Override

    public void login(String username, String password) throws ServletException {

        getContext().getAuthenticator().login(username, password, this);

    }

 

    @Override

    public void logout() throws ServletException {

        getContext().getAuthenticator().logout(this);

    }

}

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring SecurityShiro都是Java领域中常用的安全框架,用于应用程序的身份验证和授权。它们提供了一些功能来保护应用程序的安全性,但在实现和使用上有一些区别。 1. Spring Security是一个基于Spring框架的安全解决方案,它提供了一套完整的认证和授权机制。它可以与Spring框架无缝集成,支持灵活的配置和扩展。Spring Security提供了许多内置的认证和授权特性,例如基于角色的访问控制、记住我功能、并发会话管理等。 2. Shiro是一个独立的安全框架,它可以与任何Java应用程序集成,不依赖于任何特定的框架。Shiro提供了简单易用的API,可以处理身份验证、授权、会话管理和密码加密等。与Spring Security不同,Shiro更加灵活,可以根据需求进行自定义配置。 在比较Spring SecurityShiro时,有几个方面需要考虑: 1. 集成和依赖:Spring SecuritySpring框架的一部分,与其他Spring组件集成非常方便。而Shiro是一个独立的框架,可以与任何Java应用程序集成。 2. 功能和扩展性:Spring Security提供了许多内置的认证和授权特性,并且具有强大的扩展性,可以满足大多数应用程序的需求。Shiro相对而言更加灵活,可以根据具体需求进行自定义配置。 3. 学习曲线和复杂性:Spring Security相对复杂一些,需要熟悉Spring框架的相关概念和配置。Shiro则相对简单,学习曲线较为平缓。 4. 社区支持和文档资料:Spring Security是一个非常流行的框架,有着庞大的社区支持和丰富的文档资料。Shiro虽然没有Spring Security那么大的社区,但也有一定的支持和文档资可供参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值