spring集成shiro登陆流程(上)

上一篇已经分析了shiro的入口filter是SpringShiroFilter, 那么它的doFilter在哪儿呢?

我们看到它的直接父类AbstractShrioFilter继承了OncePerRequestFilter类,该类是shiro内置的大部分filter的父类(抽像公共部分),在该类中定义了doFilter方法

OncePerRequestFilte

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {    //当前过滤器的名字
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();     //如果该过滤器执行过,那么将不执行同一个名字的过滤器 直接执行过滤链中的下一个过滤器
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            filterChain.doFilter(request, response);
        } else if (!isEnabled(request, response) || shouldNotFilter(request) ) {        //如果当前过滤器设置了enabled属性为false,则不执行,直接执行过滤链中的下一个过滤器
            filterChain.doFilter(request, response);
        } else {
            //标志当前过滤器已经执行过
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
            try {          //1、 核心方法
                doFilterInternal(request, response, filterChain);
            } finally {
                //过滤链执行完毕后,清空request中的过滤链执行记录
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

由于我们是通过SpringShiroFilter拦截进来的那么会调用AbstractShrioFilter中的doFilterInternal

AbstractShrioFilter

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
        throws ServletException, IOException {

     //封装容器的request和response为shiro自己的 其中在request中标识了当前不为servlet容器的session (在创建session时会用到servlet容器调用getSession()时 )
    final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
    final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
    //2、 创建subject(可以看出每次请求都会创建一个Subject对象)
    final Subject subject = createSubject(request, response);

    //执行过滤链
    subject.execute(new Callable() {
        public Object call() throws Exception {
            updateSessionLastAccessTime(request, response);  //修改session的最后活动时间
            executeChain(request, response, chain);  //执行过滤链
            return null;
        }
    });
}
//创建subject对象
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
    return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}
//7、执行过滤链
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
        throws IOException, ServletException {
    //获取当前请求对应的过滤链
    FilterChain chain = getExecutionChain(request, response, origChain);
    chain.doFilter(request, response);
}

WebSubject

//3、public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
    //每次都创建subject上下文(subjectContext),  并设置securityManager对象
    super(securityManager);
    //将request和response添加到subject上下文(subjectContext), 即上面创建的对象
    setRequest(request);
    setResponse(response);
}
//4、创建
public WebSubject buildWebSubject() {
    Subject subject = super.buildSubject();
    return (WebSubject) subject;
}

Subject


//5、调用DefaultSecurityManager的createSubject方法
public Subject buildSubject() {  return this.securityManager.createSubject(this.subjectContext);
}

DefaultSecurityManager

// 6public Subject createSubject(SubjectContext subjectContext) {
    //web的subjectContext时,会重新创建一个新的,其他的(ini等),只是copy
    SubjectContext context = copy(subjectContext);

    //验证是否subject上下文中有securityMangary对象,如果没有创建一个
    context = ensureSecurityManager(context);

    //将session放入subjectContext 该session会从cookie或者rul上带的JSESSIONID(默认) 注意:第一次访问项目来到这儿没有session
    context = resolveSession(context);

    //校验用户登陆信息, 并放入context, 如果subject,session,和授权认证AuthenticationInfo中都没有,将会从rememberMeManager(cookie)中获取   //注意:第一次访问项目来到这儿没有这些信息
    context = resolvePrincipals(context);

    //创建一个WebDelegatingSubject对象
    Subject subject = doCreateSubject(context);

    //保存当前认证的用户信息(一般是用户名)在session里,并标记当前用户已经被认证过  为了下次remember使用
    save(subject);

    return subject;
}
// 从context中获取session
protected SubjectContext resolveSession(SubjectContext context) {
    Session session = resolveContextSession(context);
    if (session != null) {
        context.setSession(session);
    }
    return context;
}
protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
    //调用的下面子类DefaultWebSecurityManager的方法
    SessionKey key = getSessionKey(context);
    if (key != null) {
        //调用 SessionsSecurityManager#getSession
        return getSession(key);
    }
    return null;
}
//登陆
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
    AuthenticationInfo info;
    try {
        info = authenticate(token);
    } catch (AuthenticationException ae) {
        onFailedLogin(token, ae, subject);
    }
    Subject loggedIn = createSubject(token, info, subject);
    //登陆成功后 根据配置的"记住我" 保存认证信息
    onSuccessfulLogin(token, info, loggedIn);
    return loggedIn;
}

protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
    rememberMeSuccessfulLogin(token, info, subject);
}
protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
    //获取 rememberMeManager管理器
    RememberMeManager rmm = getRememberMeManager();
    rmm.onSuccessfulLogin(subject, token, info);
}

 DefaultWebSecurityManager

//创建sessionKey@Override
protected SessionKey getSessionKey(SubjectContext context) {
    //从context中获取sessonId和request,response
    if (WebUtils.isWeb(context)) {
        Serializable sessionId = context.getSessionId();
        ServletRequest request = WebUtils.getRequest(context);
        ServletResponse response = WebUtils.getResponse(context);
        return new WebSessionKey(sessionId, request, response);
    } else {
        ...
    }
}
// 第一次调用时 getSession方法最后会调用到这儿来 返回null
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
    Serializable sessionId = getSessionId(sessionKey);
    if (sessionId == null) {
        return null;
    }
    Session s = retrieveSessionFromDataSource(sessionId);
    if (s == null) {
        //session ID was provided, meaning one is expected to be found, but we couldn‘t find one:
        String msg = "Could not find session with ID [" + sessionId + "]";
        throw new UnknownSessionException(msg);
    }
    return s;
}

现在开始执行请求路径对应的过滤器

由于过滤链中的过滤器也是OncePerRequestFilte的子类,继续走OncePerRequestFilte#doFilter方法 然后会调用第一步doFilterInternal方法

  我们自定义的方法一般也是继承了AdviceFilter过滤器

AdviceFilter

public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        Exception exception = null;
        try {       //8、执行前置方法
            boolean continueChain = preHandle(request, response);
       if (continueChain) {
                executeChain(request, response, chain);
            }

            postHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }

        } catch (Exception e) {
            exception = e;
        } finally {
            cleanup(request, response, exception);
        }
    }

AccessControlFilter

public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    //9、是登陆的rul或者已经认证过 否则重定向到登陆页面
    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

UserFilter

(这里以user过滤器为例,如果没有认证过,直接重定向到登陆url)

该过滤器重写了这两个方法

//10、判断是否认证过protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {  //判断当前请求的路径是否为当前过滤器配置的登陆路径(登陆不需要任何权限,返回true)
    if (isLoginRequest(request, response)) {
        return true;
    } else {    //判断是否已经认证过
        Subject subject = getSubject(request, response);
        return subject.getPrincipal() != null;
    }
}
//11、返回false
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  //重定向到登陆rul
    saveRequestAndRedirectToLogin(request, response);
    return false;
}//12、
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
    //调用webUtils的方法,将当前请求失败的的信息保存起来(便于下次认证成功后直接重定向到该路径)
    saveRequest(request);
    //重定向的时候会生成session(shrio的)
    redirectToLogin(request, response);
}

WebUtils

//保存public static void saveRequest(ServletRequest request) {
    Subject subject = SecurityUtils.getSubject();
    //13、这里会创建一个 StoppingAwareProxiedSession   AbstractNativeSessionManager#start是创建simpleSession并调用session监听器  //会将sessinID存在cookie中和sessionDao中(默认时ehcache缓存,可以自己实现redis等)(在DefaultSessionManager#create(Session session)方法中)
    Session session = subject.getSession();
    HttpServletRequest httpRequest = toHttp(request);
    //将当前目标路径的请求信息保存起来
    SavedRequest savedRequest = new SavedRequest(httpRequest);
    //存到session中,跳到登陆页面后登陆成功后会重定向到此次失败的路径
    session.setAttribute(SAVED_REQUEST_KEY, savedRequest);
}
//重定向到savedRequet保存的路径 如果是直接访问的登陆url,则直接重定向到当前过滤器配置的登陆成功url
//成功后的重定向可是不生成session的
public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl)
        throws IOException {
    String successUrl = null;
    boolean contextRelative = true;
    //从session中获取,并清空上一次失败保存的信息
    SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request);
    //上一次请求失败的保存的对象  而且是get请求(这里一般是直接浏览器输入的url) 如果是post请求过来的(一般是表单),直接返回目标路径
    if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
        successUrl = savedRequest.getRequestUrl();
        contextRelative = false;
    }  //第一次请求时,successUrl为null, 登陆成功后,有值(上一次失败的url)
    if (successUrl == null) {
        successUrl = fallbackUrl;
    }  //15、发出重定向
    WebUtils.issueRedirect(request, response, successUrl, null, contextRelative);
}

public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative) throws IOException {
    issueRedirect(request, response, url, queryParams, contextRelative, true);
}
// 会把sessionID写在rul上, 下面的内容就不带大家看了
public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative, boolean http10Compatible) throws IOException {
    RedirectView view = new RedirectView(url, contextRelative, http10Compatible);
    view.renderMergedOutputModel(queryParams, toHttp(request), toHttp(response));
}

SavedRequest

// SavedRequest的构造方法
public SavedRequest(HttpServletRequest request) {  //当前请求的方式(get|post...)
    this.method = request.getMethod();  //当前请求的参数
    this.queryString = request.getQueryString();  //当前请求失败的路径
    this.requestURI = request.getRequestURI();
}

RedirectView (拼接重定向的参数请求头、url加sessionID,url编码等操作都由这儿进入)

public final void renderMergedOutputModel(
            Map model, HttpServletRequest request, HttpServletResponse response) throws IOException {

        // Prepare name URL.
        StringBuilder targetUrl = new StringBuilder();
        if (this.contextRelative && getUrl().startsWith("/")) {
            targetUrl.append(request.getContextPath());
        }
        targetUrl.append(getUrl());     //拼接请求参数
        appendQueryProperties(targetUrl, model, this.encodingScheme);
        sendRedirect(request, response, targetUrl.toString(), this.http10Compatible);
    }

AbstractNativeSessionManager

//14、创建session时会调用public Session start(SessionContext context) {
    //创建simpleSession
    Session session = createSession(context);
    //重置session时间
    applyGlobalSessionTimeout(session);  //会将sessionID存到cookie
    onStart(session, context);
    //调用session的Listner
    notifyStart(session);
    //Don‘t expose the EIS-tier Session object to the client-tier:
    return createExposedSession(session, context);
}

那么此时一个没有授权的请求就执行完毕,现在就来到了我们的登陆界面

  登陆使用authc过滤器

FormAuthenticationFilter

和上面的过程一样,会判断是否认证,如果没有会执行onAccessDenied方法

// 这里需要该过滤器的 登陆url 和登陆所在的界面的url一样
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    //条件: 配置的该过滤器的登陆路径和请求路径相同
    if (isLoginRequest(request, response)) {
        //1、HttpServletRequest  2、post请求
        if (isLoginSubmission(request, response)) {
            return executeLogin(request, response);
        } else {
            //登陆页面的url 请求方式为get
            return true;
        }
    } else {
        //如果一个请求路径配置的authc过滤器,然后没有登陆直接调用,会走到这里
        //重定向到登陆页面  会创建一个StoppingAwareProxiedSession类型的session 并把sessionId放在登陆页面的url上
        saveRequestAndRedirectToLogin(request, response);
        return false;
    }
}
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    //调用 new UsernamePasswordToken(username, password, rememberMe, host);
    AuthenticationToken token = createToken(request, response);
    try {
        Subject subject = getSubject(request, response);
        subject.login(token);
        return onLoginSuccess(token, subject, request, response);
    } catch (AuthenticationException e) {
        return onLoginFailure(token, e, request, response);
    }
}
//登陆成功后 重定向到上一次重定向过来的路径或者当前过滤器的登陆路径
//可以重写该方法登陆后直接跳到当前过滤器配置的url  而不是上一次失败的url
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                 ServletRequest request, ServletResponse response) throws Exception {
    //调用父类AuthenticationFilter的issueSuccessRedirect方法
    issueSuccessRedirect(request, response);
    //重定向后,阻止过滤连调用
    return false;
}

AuthenticationFilter

protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
    //当前过滤器配置的登陆url
    WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
}

DelegatingSubject

public void login(AuthenticationToken token) throws AuthenticationException {
    clearRunAsIdentitiesInternal();
    //委托给securiManager登陆
    Subject subject = securityManager.login(this, token);
    PrincipalCollection principals;
    String host = null;
    if (subject instanceof DelegatingSubject) {
        DelegatingSubject delegating = (DelegatingSubject) subject;
        //认证信息
        principals = delegating.principals;
        host = delegating.host;
    } else {
        principals = subject.getPrincipals();
    }
    this.principals = principals;
    //标记已经登陆过
    this.authenticated = true;
    if (token instanceof HostAuthenticationToken) {
        host = ((HostAuthenticationToken) token).getHost();
    }
    if (host != null) {
        this.host = host;
    }
    //获取登陆时的session
    Session session = subject.getSession(false);
    if (session != null) {
        //执行new StoppingAwareProxiedSession(session, this);  登陆后的session封装成StoppingAwareProxiedSession代理对象
        this.session = decorate(session);
    } else {
        this.session = null;
    }
}

AbstractRememberMeManager  处理 remeberme

public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
    //清空之前的认证信息
    forgetIdentity(subject);

    //如果是rememberMe类型的token
    if (isRememberMe(token)) {
        //记录
        rememberIdentity(subject, token, info);
    }
}
public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
    //从认证后的信息中获取
    PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
    rememberIdentity(subject, principals);
}
// 加密处理
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
    byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
    rememberSerializedIdentity(subject, bytes);
}
//使用CipherService类进行处理
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
    byte[] bytes = serialize(principals);
    if (getCipherService() != null) {
        bytes = encrypt(bytes);
    }
    return bytes;
}
// 返回加密后的认证信息
protected byte[] encrypt(byte[] serialized) {
    byte[] value = serialized;
    CipherService cipherService = getCipherService();
    if (cipherService != null) {
        // getEncryptionCipherKey()  获取的是rememberMe cookie加密和解密的密钥
        ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
        value = byteSource.getBytes();
    }
    return value;
}
//加密
protected byte[] encrypt(byte[] serialized) {
    byte[] value = serialized;
    CipherService cipherService = getCipherService();
    if (cipherService != null) {
        ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
        value = byteSource.getBytes();
    }
    return value;
}

CookieRememberMeManager

protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
    HttpServletRequest request = WebUtils.getHttpRequest(subject);
    HttpServletResponse response = WebUtils.getHttpResponse(subject);

    //base 64 encode it and store as a cookie:
    String base64 = Base64.encodeToString(serialized);
    //rememberMe的cookie模板  key为自定义的名字  我这儿是rememberMe
    Cookie template = getCookie();
    Cookie cookie = new SimpleCookie(template);
    cookie.setValue(base64);
    cookie.saveTo(request, response);
}

下面是remember的配置

<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <constructor-arg value="rememberMe"/>
    <property name="httpOnly" value="true"/>
    <property name="maxAge" value="2592000"/><!-- 30天 -->
</bean>

<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
    <!-- rememberMe cookie加密和解密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->
    <property name="cipherKey"
              value="#{T(org.apache.shiro.codec.Base64).decode(‘4AvVhmFLUs0KTA3Kprsdag==‘)}"/>
    <property name="cookie" ref="rememberMeCookie"/>
</bean>

由于篇幅原因,本节详细介绍的是登陆需要验证的请求跳转到登陆界面的源码解析

小结:

  1、当第一次请求失败后,会重定向到当前过滤器的登陆界面,并创建一个session,将sessinID存在cookie,重定向的url,还会存放在sessionDao中(默认是ehcache, 可自定义)

  2、当请求的路径为不用认证(anon等自定义preHandle返回true的路径),也会由servlet容器调用shiroRequest的getSession方法创建一个session,保存位置同1

原文地址:https://www.cnblogs.com/qiaozhuangshi/p/10777507.html

时间: 2024-08-28 09:42:26

spring集成shiro登陆流程(上)的相关文章

shiro实战系列(十五)之Spring集成Shiro

Shiro 的 JavaBean 兼容性使得它非常适合通过 Spring XML 或其他基于 Spring 的配置机制.Shiro 应用程序需要一个具 有单例 SecurityManager 实例的应用程序.请注意,这不会是一个静态的单例,但应该只有一个应用程序能够使用 的实例,无论它是否是静态单例的. Web Applications Shiro 拥有对 Spring Web 应用程序的一流支持.在 Web 应用程序中,所有 Shiro 可访问的万恶不请求必须通过一个 主要的 Shiro 过滤

Shiro(二):Spring-boot如何集成Shiro(上)

这篇文章主要介绍了spring-boot是如何集成shiro的authentication流程的. 从shiro-spring-boot-web-starter说起 shiro-spring-boot-web-starter是shiro在web环境下快速集成至spring-boot的配置包.其本身引入了shiro的必要模块.并在Configuration中以@Bean的形式声明了Shiro各组件,交由spring容器统一管理.先看META-INF定义了配置类: org.springframewo

spring 集成shiro 之 自定义过滤器

出自:http://blog.csdn.net/shuishouhcd/article/details/9077379 最近一段时间,我一直在将shiro集成到我的一个项目中,用作认证和授权处理. shiro对我来说是个新东西,以下是我学习过的内容: http://shiro.apache.org/authorization.html http://www.cnblogs.com/skyme/archive/2011/09/11/2173760.html  系列 http://www.infoq

Shiro学习总结(10)——Spring集成Shiro

1.引入Shiro的Maven依赖 [html] view plain copy <!-- Spring 整合Shiro需要的依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.1</version> </dependency> <dep

细说shiro之五:在spring框架中集成shiro

官网:https://shiro.apache.org/ 1. 下载在Maven项目中的依赖配置如下: <!-- shiro配置 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${version.shiro}</version> </dependency&g

Spring Boot 集成Shiro和CAS

请大家在看本文之前,先了解如下知识点: 1.Shiro 是什么?怎么用? 2.Cas 是什么?怎么用? 3.最好有Spring基础 可以先看看这两篇文章,按照这2篇文章的内容做一遍: Spring Boot Shiro 权限管理 CAS单点登录 首先看一下下面这张图: 第一个流程是单纯使用Shiro的流程. 第二个流程是单纯使用Cas的流程. 第三个图是Shiro集成Cas后的流程. [流程图高清图连接:http://img.blog.csdn.net/20160117224937078] PS

Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理

本文是接着上篇博客写的:Spring boot 入门(三):SpringBoot 集成结合 AdminLTE(Freemarker),利用 generate 自动生成代码,利用 DataTable 和 PageHelper 进行分页显示.按照前面的博客,已经可以搭建一个简单的 Spring Boot 系统,本篇博客继续对此系统进行改造,主要集成了 Shiro 权限认证框架,关于 Shiro 部分,在本人之前的博客(认证与Shiro安全框架)有介绍到,这里就不做累赘的介绍. 此系列的博客为实践部分

shiro与spring集成时报Versions of Ehcache before version 2.5 allowed any number of CacheManagers with the same name (same configuration resource) to exist in a JVM.

shiro与spring集成时,一直报Another unnamed CacheManager already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following:1. Use one of the CacheManager.create() static factory methods to reuse same CacheMa

重构Mybatis与Spring集成的SqlSessionFactoryBean(上)

一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改.但是今天却小心翼翼的重构了Mybatis官方提供的与Spring集成的SqlSessionFactoryBean类,一来是抱着试错的心态,二来也的确是有现实需要. 先说明两点: 通常来讲,重构是指不改变功能的情况下优化代码,但本文所说的重构也包括了添加功能 本文使用的主要jar包(版本):spring-*-4.3.3.RELEASE.jar.mybatis-3.4.1.jar.mybatis-spring-1.3.0.jar