In-depth analysis of shiro SecurityUtils.getSubject()

1. In general, SecurityUtils.getSubject() is to create a Subject for each request and save it to the resources (ThreadLocal<Map<Object, Object>>) variable of ThreadContext, that is, a HTTP request to a subject, and bind it to the current thread.

Here comes the question: after the .subject.login() login authentication is successful, how does the next request know which user is the request?

Friendly reminder: The only thing you can read in this article is to analyze this question. If you already understand it, you don’t need to read further.

Conjecture: Since the subject is bound to the current thread, this must require an intermediary to store state

    public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Builder()).buildSubject();
            ThreadContext.bind(subject);
        }

        return subject;
    }




public abstract class ThreadContext {
    private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
    public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
    public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
    private static final ThreadLocal<Map<Object, Object>> resources = new ThreadContext.InheritableThreadLocalMap();

    protected ThreadContext() {
    }

    public static Map<Object, Object> getResources() {
        return (Map)(resources.get() == null ? Collections.emptyMap() : new HashMap((Map)resources.get()));
    }

    public static void setResources(Map<Object, Object> newResources) {
        if (!CollectionUtils.isEmpty(newResources)) {
            ensureResourcesInitialized();
            ((Map)resources.get()).clear();
            ((Map)resources.get()).putAll(newResources);
        }
    }

    private static Object getValue(Object key) {
        Map<Object, Object> perThreadResources = (Map)resources.get();
        return perThreadResources != null ? perThreadResources.get(key) : null;
    }

    private static void ensureResourcesInitialized() {
        if (resources.get() == null) {
            resources.set(new HashMap());
        }

    }

    public static Object get(Object key) {
        if (log.isTraceEnabled()) {
            String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }

        Object value = getValue(key);
        if (value != null && log.isTraceEnabled()) {
            String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" + key + "] bound to thread [" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }

        return value;
    }

    public static void put(Object key, Object value) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        } else if (value == null) {
            remove(key);
        } else {
            ensureResourcesInitialized();
            ((Map)resources.get()).put(key, value);
            if (log.isTraceEnabled()) {
                String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" + key + "] to thread [" + Thread.currentThread().getName() + "]";
                log.trace(msg);
            }

        }
    }

    public static Object remove(Object key) {
        Map<Object, Object> perThreadResources = (Map)resources.get();
        Object value = perThreadResources != null ? perThreadResources.remove(key) : null;
        if (value != null && log.isTraceEnabled()) {
            String msg = "Removed value of type [" + value.getClass().getName() + "] for key [" + key + "]from thread [" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }

        return value;
    }

    public static void remove() {
        resources.remove();
    }

    public static SecurityManager getSecurityManager() {
        return (SecurityManager)get(SECURITY_MANAGER_KEY);
    }

    public static void bind(SecurityManager securityManager) {
        if (securityManager != null) {
            put(SECURITY_MANAGER_KEY, securityManager);
        }

    }

    public static SecurityManager unbindSecurityManager() {
        return (SecurityManager)remove(SECURITY_MANAGER_KEY);
    }

    public static Subject getSubject() {
        return (Subject)get(SUBJECT_KEY);
    }

    public static void bind(Subject subject) {
        if (subject != null) {
            put(SUBJECT_KEY, subject);
        }

    }

    public static Subject unbindSubject() {
        return (Subject)remove(SUBJECT_KEY);
    }

    private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> {
        private InheritableThreadLocalMap() {
        }

        protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {
            return parentValue != null ? (Map)((HashMap)parentValue).clone() : null;
        }
    }
}


After the subject is successfully logged in, how does the next request know which user is the request?

After source code analysis, the core implementation is as follows in the DefaultSecurityManager class:
  public Subject createSubject(SubjectContext subjectContext) {
        SubjectContext context = this.copy(subjectContext);
        context = this.ensureSecurityManager(context);
        context = this.resolveSession(context);
        context = this.resolvePrincipals(context);
        Subject subject = this.doCreateSubject(context);
        this.save(subject);
        return subject;
    }



Each request will reset the Session and Principals. You can probably guess from here: if it is a web project, get httpSession directly from the web container, and then get Principals from httpSession. The essence is to get user information from cookies, and then every time Set the Principal, so that you know which user's request is, and only get whether the user has successfully authenticated, -- essence: relying on the browser's cookie to maintain the

extension , if it is not the app of the web container, how to achieve no Stateful session

1. The general approach will include a token in the header, or in the parameters, and the background will verify the user's identity based on this token, but at this time, the session in the servlet cannot be saved, we are in At this time, you need to create your own session. The common method is to rewrite the interface between session and request, and then replace it with your own request in the filter, so the obtained session is also your own session, and then create it according to the token. And maintain session

2.shiro implementation:

rewrite shiro's sessionManage

code implementation from: http://www.cnblogs.com/zhuxiaojie/p/7809767.html

implementation code:

import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.UUID;

/**
 * @author zxj<br>
 * Time 2017/11/8 15:55
 * illustrate...
 */
public class StatelessSessionManager extends DefaultWebSessionManager {
    /**
     * This is the server to return to the client,
     */
    public final static String TOKEN_NAME = "TOKEN";
    /**
     * This is the header that the client requests to the server
     */
    public final static String HEADER_TOKEN_NAME = "token";
    public final static Logger LOG = LoggerFactory.getLogger(StatelessSessionManager.class);


    @Override
    public Serializable getSessionId(SessionKey key) {
        Serializable sessionId = key.getSessionId();
        if(sessionId == null){
            HttpServletRequest request = WebUtils.getHttpRequest(key);
            HttpServletResponse response = WebUtils.getHttpResponse(key);
            sessionId = this.getSessionId(request,response);
        }
        HttpServletRequest request = WebUtils.getHttpRequest(key);
        request.setAttribute(TOKEN_NAME,sessionId.toString());
        return sessionId;
    }

    @Override
    protected Serializable getSessionId(ServletRequest servletRequest, ServletResponse servletResponse) {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader(HEADER_TOKEN_NAME);
        if(token == null){
            token = UUID.randomUUID().toString();
        }

        //This code has not yet checked its function, but this is the code owned by its parent class. After rewriting it, I copied it...start
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
        request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
        //This code has not yet checked its function, but this is the code owned by its parent class. After rewriting it, I copied it... End
        return token;
    }

}

@RequestMapping("/")
    public void login(@RequestParam("code")String code, HttpServletRequest request){
        Map<String,Object> data = new HashMap<>();
        if(SecurityUtils.getSubject().isAuthenticated()){
        //The code here has been successfully logged in, so naturally you don't need to authenticate again, just take it out from the request,
            data.put(StatelessSessionManager.HEADER_TOKEN_NAME,getServerToken());
            data.put(BIND,ShiroKit.getUser().getTel() != null);
            response(data);
        }
        LOG.info("Authorization code: " + code);
        AuthorizationService authorizationService = authorizationFactory.getAuthorizationService(Constant.clientType);
        UserDetail authorization = authorizationService.authorization(code);



        Oauth2UserDetail userDetail = (Oauth2UserDetail) authorization;

        loginService.login(userDetail);
        User user = userService.saveUser(userDetail,Constant.clientType.toString());
        ShiroKit.getSession().setAttribute(ShiroKit.USER_DETAIL_KEY,userDetail);
        ShiroKit.getSession().setAttribute(ShiroKit.USER_KEY,user);
        data.put(BIND,user.getTel() != null);
      //The code here must be executed in the login, because after login, the session will be created and the latest token will be obtained.
        data.put(StatelessSessionManager.HEADER_TOKEN_NAME,getServerToken());
        response(data);
    }


import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author zxj<br>
 * Time 2017/11/8 15:40
 * illustrate...
 */
@Configuration
public class ShiroConfiguration {

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * Inject a realm here
     * @param realm
     * @return
     */
    @Bean
    public SecurityManager securityManager(Realm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setSessionManager(new StatelessSessionManager());
        securityManager.setRealm(realm);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean ();
        bean.setSecurityManager(securityManager);

        Map<String,String> map = new LinkedHashMap<>();
        map.put("/public/**","anon");
        map.put("/login/**","anon");
        map.put("/**","user");
        bean.setFilterChainDefinitionMap(map);

        return bean;
    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326353115&siteId=291194637