Shiro 소스 코드 분석 --- 인증 절차

18948 단어 Shiro
본 고 는 소스 코드 를 바탕 으로 Shiro 인증 절 차 를 분석 하기 때문에 읽 는 사람 이 Shiro 에 대해 어느 정도 알 고 있다 고 가정 합 니 다. 만약 에 Shiro 에 대해 잘 모 르 면 박문 을 추천 합 니 다. 저 에 게 Shiro 디 렉 터 리 에 Apache Shiro 를 붙 이 는 것 을 배우 면 가장 중요 한 두 가지 업무 입 니 다. 하 나 는 인증 입 니 다. 즉, 로그 인 한 사용자 의 신분 이 합 법 적 인지 해결 하 는 것 입 니 다.둘 째 는 사용자 가 로그 인 한 후에 어떤 권한 이 있 는 지 하 는 것 이다.본 고 는 Shiro 소스 코드 를 바탕 으로 Shiro 의 인증 절 차 를 분석 하고 Shiro 인증 절 차 를 깊이 이해 해 야 인증 과정 에서 각 구성 요소 의 역할 을 실제 응용 에서 유연 하 게 사용 할 수 있다.Shiro 는 일반적으로 웹 환경 에 사용 되 고 Spring 과 통합 적 으로 사용 되 기 때문에 이번 인증 절차 의 분석 전제 도 웹 환경 이 고 Shiro 는 Spring 과 통합 되 었 다.특별 설명: 본 고 에서 사용 한 Shiro 버 전: 1.2.2.
Shiro 와 Spring 이 통합 되 었 을 때 웹. xml 에 Shiro 입구 필 터 를 설정 해 야 합 니 다.

    shiroFilter
    org.springframework.web.filter.DelegatingFilterProxy
    true
    
        targetFilterLifecycle
        true
    


    shiroFilter
    /*

Spring 에 익숙 한 사람들 은 Delegating Filter Proxy 의 역할 을 알 고 있 을 것 이다. 이 Spring 이 제공 하 는 필 터 는 의뢰 역할 만 하고 실행 절 차 는 Spring 용기 에 shiro Filter 라 는 필터 에 위탁 된다.그래서 Spring 프로필 에 shiroFilter 를 설정 해 야 합 니 다. 다음 과 같 습 니 다.


    
    
    
    
        
            
        
    
    
        
            /index.jsp = anon
            /unauthorized.jsp = anon
            /login.jsp = authc
            /logout = logout
            /authenticated.jsp = authc 
            /** = user
        
    

ShiroFilterFactory Bean 은 org. springframework. beans. factory. Factory Bean 인 터 페 이 스 를 실 현 했 기 때문에 shiroFilter 대상 은 ShiroFilterFactory Bean 의 getObject () 방법 으로 되 돌 아 왔 습 니 다.
public Object getObject() throws Exception {
    if (instance == null) {
        instance = createInstance();
    }
    return instance;
}

protected AbstractShiroFilter createInstance() throws Exception {

    log.debug("Creating Shiro Filter instance.");
	//                
    SecurityManager securityManager = getSecurityManager();
    if (securityManager == null) {
        String msg = "SecurityManager property must be set.";
        throw new BeanInitializationException(msg);
    }
	//    Web        
    if (!(securityManager instanceof WebSecurityManager)) {
        String msg = "The security manager does not implement the WebSecurityManager interface.";
        throw new BeanInitializationException(msg);
    }

	//          
    FilterChainManager manager = createFilterChainManager();

    //                 
    PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
    chainResolver.setFilterChainManager(manager);

    //   SpringShiroFilter  
    return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}

상기 소스 코드 에서 볼 수 있 듯 이 최종 적 으로 SpringShiroFilter 대상, 즉 Spring 설정 파일 의 shiroFilter 대상 을 되 돌려 주 었 습 니 다. 이 필 터 는 세 가지 중요 한 대상 을 가지 고 있 습 니 다: Security Manager, PathMatchingFilterChain Resolver, FilterChain Manager.Spring 설정 에 filter Chain Definitions 속성 이 설정 되 어 있 기 때문에 setFilterChain Definitions 방법 을 호출 합 니 다.
public void setFilterChainDefinitions(String definitions) {
    Ini ini = new Ini();
    ini.load(definitions);
    //did they explicitly state a 'urls' section?  Not necessary, but just in case:
    Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
    if (CollectionUtils.isEmpty(section)) {
        //no urls section.  Since this _is_ a urls chain definition property, just assume the
        //default section contains only the definitions:
        section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
    }
    /**     section,     
    		/index.jsp = anon
            /unauthorized.jsp = anon
            /login.jsp = authc
            /logout = logout
            /authenticated.jsp = authc 
            /** = user
                ,            URL       Filter, anon、authc、logout  Filter   ,
            Ini.Section   Map  , key URL   ,value Filter  
    **/
    //   filterChainDefinitionMap
    setFilterChainDefinitionMap(section);
}

FilterChainManager 는 현재 Shiro 응용 프로그램의 모든 Filter 를 관리 하 는 데 사 용 됩 니 다. Shiro 가 기본적으로 사용 하 는 Filter 가 있 고 사용자 정의 Filter 일 수도 있 습 니 다.FilterChain Manager 가 어떻게 만 들 어 졌 는 지 살 펴 보 겠 습 니 다.
protected FilterChainManager createFilterChainManager() {
	//   DefaultFilterChainManager
    DefaultFilterChainManager manager = new DefaultFilterChainManager();
    //   Shiro  Filter,  org.apache.shiro.web.filter.mgt.DefaultFilter  
    Map defaultFilters = manager.getFilters();
    //apply global settings if necessary:
    for (Filter filter : defaultFilters.values()) {
    	//     Filter loginUrl、successUrl、unauthorizedUrl  
        applyGlobalPropertiesIfNecessary(filter);
    }

    //    Spring        Filter
    Map filters = getFilters();
    if (!CollectionUtils.isEmpty(filters)) {
        for (Map.Entry entry : filters.entrySet()) {
            String name = entry.getKey();
            Filter filter = entry.getValue();
            applyGlobalPropertiesIfNecessary(filter);
            if (filter instanceof Nameable) {
                ((Nameable) filter).setName(name);
            }
            //     Filter     ,    Filter        Filter
            manager.addFilter(name, filter, false);
        }
    }

    //build up the chains:
    Map chains = getFilterChainDefinitionMap();
    if (!CollectionUtils.isEmpty(chains)) {
        for (Map.Entry entry : chains.entrySet()) {
            String url = entry.getKey();
            String chainDefinition = entry.getValue();
            //        URL    FilterChain  ,
            //        URL   ,   URL        URL       Filter
            //   URL        ,            ,                ,            
            manager.createChain(url, chainDefinition);
        }
    }

    return manager;
}

PathMatchingFilterChain Resolver 대상 의 직책 은 간단 합 니 다. ant 경로 매 칭 방법 으로 방문 한 URL 과 일치 하 는 것 입 니 다. pathMatchingFilterChain Resolver 는 FilterChain Manager 대상 을 가지 고 있 기 때문에 URL 이 일치 하면 이 URL 에 적용 해 야 할 FilterChain 을 얻 을 수 있 습 니 다.상기 분석 을 통 해 알 수 있 듯 이 Shiro 는 일련의 URL 일치 자 를 통 해 URL 에 적용 해 야 할 Filter 를 설정 한 다음 Filter 에서 해당 하 는 작업 을 수행 하기 때문에 Shiro 의 모든 기능 은 Filter 를 통 해 이 루어 집 니 다.물론 인증 기능 도 예외 가 아니다. 상기 설정 에서 인증 기능 은 org. apache. shiro. web. filter. authc. FormAuthenticationFilter 에 의 해 이 루어 졌 다.입구 필터 SpringShiroFilter 의 실행 절 차 를 살 펴 보 겠 습 니 다. FormAuthenticationFilter 까지 어떻게 실행 되 는 지 살 펴 보 겠 습 니 다.Filter 라면 가장 중요 한 것 은 doFilter 방법 입 니 다. SpringShiroFilter 는 Once PerRequestFilter 에서 계승 되 기 때문에 doFilter 방법 도 Once PerRequestFilter 에서 정 의 됩 니 다.
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    //            Filter       
    String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
        log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
        filterChain.doFilter(request, response);
    } else //noinspection deprecation
        if (/* added in 1.2: */ !isEnabled(request, response) ||
            /* retain backwards compatibility: */ shouldNotFilter(request) ) {
        log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                getName());
        filterChain.doFilter(request, response);
    } else {
        // Do invoke this filter...
        log.trace("Filter '{}' not yet executed.  Executing now.", getName());
        request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

        try {
        	//          
            doFilterInternal(request, response, filterChain);
        } finally {
            // Once the request has finished, we're done and we don't
            // need to mark as 'already filtered' any more.
            request.removeAttribute(alreadyFilteredAttributeName);
        }
    }
}

doFilter 내부 방법 정의 AbstractShiroFilter 중:
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

    Throwable t = null;

    try {
        final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
        final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
		
		//   Subject  ,    ,       ,    createSubject  
        final Subject subject = createSubject(request, response);

        //   Subject        ,
        subject.execute(new Callable() {
            public Object call() throws Exception {
            	//           ,        
                updateSessionLastAccessTime(request, response);
                //       
                executeChain(request, response, chain);
                return null;
            }
        });
    } catch (ExecutionException ex) {
        t = ex.getCause();
    } catch (Throwable throwable) {
        t = throwable;
    }

    //       ...
}

Subject 가 어떻게 만 들 어 졌 는 지 살 펴 보 겠 습 니 다.
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
    return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}

추적 코드 최종 호출 DefaultWebSubjectFactory. createSubject 방법:
public Subject createSubject(SubjectContext context) {
    if (!(context instanceof WebSubjectContext)) {
        return super.createSubject(context);
    }
    WebSubjectContext wsc = (WebSubjectContext) context;
    SecurityManager securityManager = wsc.resolveSecurityManager();
    Session session = wsc.resolveSession();
    boolean sessionEnabled = wsc.isSessionCreationEnabled();
    PrincipalCollection principals = wsc.resolvePrincipals();
    //        ,          ,     false
    boolean authenticated = wsc.resolveAuthenticated();
    String host = wsc.resolveHost();
    ServletRequest request = wsc.resolveServletRequest();
    ServletResponse response = wsc.resolveServletResponse();

    return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
            request, response, securityManager);
}

다음은 필터 체인 이 어떻게 만 들 고 실행 되 는 지 보 겠 습 니 다.
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
        throws IOException, ServletException {
    //     URL       
    FilterChain chain = getExecutionChain(request, response, origChain);
    //            
    chain.doFilter(request, response);
}

protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
    FilterChain chain = origChain;
	//          ,      PathMatchingFilterChainResolver  
    FilterChainResolver resolver = getFilterChainResolver();
    if (resolver == null) {
        log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
        return origChain;
    }
	
	//    getChain  ,  URL         
    FilterChain resolved = resolver.getChain(request, response, origChain);
    if (resolved != null) {
        log.trace("Resolved a configured FilterChain for the current request.");
        chain = resolved;
    } else {
        log.trace("No FilterChain configured for the current request.  Using the default.");
    }

    return chain;
}

상기 Spring 설정 에 따 르 면 현재 URL: "/ authenticated. jsp" 를 처음 방문 했다 고 가정 하면 authc 라 는 Filter, 즉 FormAuthenticationFilter 를 사용 합 니 다. FormAuthenticationFilter 의 계승 체계 에 따라 dviceFilter. do Filter Internal 방법 을 먼저 실행 합 니 다.
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {
    Exception exception = null;

    try {
		//   preHandle
        boolean continueChain = preHandle(request, response);
        if (log.isTraceEnabled()) {
            log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
        }
		//   preHandle  false         
        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);
    }
}

다음 실행: PathMatchingFilter. preHandle 방법:
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

    if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
        if (log.isTraceEnabled()) {
            log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
        }
        return true;
    }

    for (String path : this.appliedPaths.keySet()) {
        //     ,  URL:"/authenticated.jsp" ,    FormAuthenticationFilter,
        //  FormAuthenticationFilter   PathMatchingFilter,    true
        if (pathsMatch(path, request)) {
            log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
            Object config = this.appliedPaths.get(path);
            //   isFilterChainContinued  ,     onPreHandle  
            return isFilterChainContinued(request, response, path, config);
        }
    }

    //no path matched, allow the request to go through:
    return true;
}

이어서 AccessControlFilter. onPreHandle 방법 을 실행 합 니 다.
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
	//   isAccessAllowed    false,    onAccessDenied  
    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

다음 에 Authenticating Filter. is Access Allowed 방법 을 실행 합 니 다.
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    return super.isAccessAllowed(request, response, mappedValue) ||
            (!isLoginRequest(request, response) && isPermissive(mappedValue));
}
super.isAccessAllowed  , AuthenticationFilter.isAccessAllowed  :
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    Subject subject = getSubject(request, response);
    return subject.isAuthenticated();
}

위 코드 를 통 해 알 수 있 듯 이 URL 을 처음 방문 한 URL 입 니 다. "/ authenticated. jsp" 이기 때문에 isAccessAllowed 방법 은 false 로 되 돌아 갑 니 다. 따라서 FormAuthenticationFilter. onAccessDenied 방법 을 실행 합 니 다.
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
	//              
    if (isLoginRequest(request, response)) {
    	//      POST  
        if (isLoginSubmission(request, response)) {
            if (log.isTraceEnabled()) {
                log.trace("Login submission detected.  Attempting to execute login.");
            }
            return executeLogin(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Login page view.");
            }
            //allow them to see the login page ;)
            return true;
        }
    } else {
        if (log.isTraceEnabled()) {
            log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                    "Authentication url [" + getLoginUrl() + "]");
        }
		//        
        saveRequestAndRedirectToLogin(request, response);
        return false;
    }
}

protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
	//  request     session ,                URL
    saveRequest(request);
    //         , :"/login.jsp"
    redirectToLogin(request, response);
}

설정 에 따라 URL: "/ login. jsp" 에 접근 할 때 도 FormAuthenticationFilter 를 적용 합 니 다. 방향 을 바 꾸 는 것 이기 때문에 GET 요청 을 했 기 때문에 isLogin Submission () 은 false 로 돌아 가기 때문에 executeLogin 방법 을 실행 하지 않 았 기 때문에 / login. jsp 페이지 에 접근 할 수 있 습 니 다.로그 인 폼 에 action = "을 설정 해 야 합 니 다. 로그 인 요청 은 / login. jsp 에 제출 됩 니 다. 이 때 는 POST 요청 이 므 로 executeLogin 방법 을 실행 합 니 다.
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
	//               AuthenticationToken
    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 subject = getSubject(request, response);
        //   Subject.login      
        subject.login(token);
        //       ,         URL
        return onLoginSuccess(token, subject, request, response);
    } catch (AuthenticationException e) {
    	//       ,        request,         
        return onLoginFailure(token, e, request, response);
    }
}

protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                     ServletRequest request, ServletResponse response) throws Exception {
    //          URL
    issueSuccessRedirect(request, response);
    //     false,          
    return false;
}

protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                 ServletRequest request, ServletResponse response) {
    //        request
    setFailureAttribute(request, e);
    //     true,          ,          
    return true;
}

이로써 인증 절 차 는 대체적으로 이 렇 습 니 다. 지면 에 국한 되 고 로그 인 절차 가 구체 적 이 니 다음 박문 을 기대 하 십시오.

좋은 웹페이지 즐겨찾기