9.Subject(用户安全操作门面)

Subject几乎代理了所有有关用户的Security的操作。如:验证、授权、session管理..,它也可以说是用户安全相关安全操作的门面。通常我们可以通过SecurityUtils.getSubject()来获取一个Subject。
我们知道在基于WEB的应用中,shiro在生成Session后,然后把SessionID保存在客户端浏览器的cookie value中。当请求再次过来时,根据cookie的名字就能找到cookie然后获取shiro所设置的sessionID来找到对应的session了。
在基于WEB的应用中,那么Subject呢?为什么Subject可以代表一个特定用户的安全操作?shiro是如何确定哪个Subject对应哪个用户的?看下面..
1.每次请求过来时,AbstractShiroFilter的doFilterInternal中,会调用createSubject方法根据参数sessionManager、request、response获取一个Subject
2.然后调用DefaultSecurityManager的createSubject方法构造一个Subject
1)准备WebSubjectContext所需的一些数据。如:session(根据上面的方法找到对应的session)、securityManager、principle..
2)调用DefaultWebSubjectFactory根据WebSubjectContext来创建一个Subject。
3)保存当前subject的最新状态。如:是否验证,和principle

1.Subject

public interface Subject {
    //获取principle
    Object getPrincipal();

    //获取PrincipalCollection (多个realm的principle集合)
    PrincipalCollection getPrincipals();


    boolean isPermitted(String permission);


    boolean isPermitted(Permission permission);


    boolean[] isPermitted(String... permissions);


    boolean[] isPermitted(List<Permission> permissions);


    boolean isPermittedAll(String... permissions);


    boolean isPermittedAll(Collection<Permission> permissions);


    void checkPermission(String permission) throws AuthorizationException;


    void checkPermission(Permission permission) throws AuthorizationException;


    void checkPermissions(String... permissions) throws AuthorizationException;


    void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;


    boolean hasRole(String roleIdentifier);


    boolean[] hasRoles(List<String> roleIdentifiers);


    boolean hasAllRoles(Collection<String> roleIdentifiers);


    void checkRole(String roleIdentifier) throws AuthorizationException;


    void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;


    void checkRoles(String... roleIdentifiers) throws AuthorizationException;

    //执行登录操作,如果登录失败,则抛出异常,如果无任何返回则认为登录成功
    void login(AuthenticationToken token) throws AuthenticationException;

    //判断是否已登录成功,如果当前session中该用户已登录则认为已登录,记住密码不代表登录成功
    boolean isAuthenticated();


    /**
    如果当前用户的principle不为null且isAuthenticated()为false,则返回true,否则返回false。
    即isRemembered()为true,并不代表该用户的真实身份,只是它曾经登录过,选择了记住我功能把身份保存在了cookie中。
    但是他并不存活在当前的session中。如果有攻击者伪造了用户的cookie身份。那么shiro会认为他伪造的用户的isRemembered为true。
    isRemembered VS isAuthenticated
    如果只是访问一些非敏感类的信息,那么我们可以设置isRemembered 就可以了。如果涉及敏感信息则设置权限要求为isAuthenticated
    **/
    boolean isRemembered();

    //获取一个Session,如果不存在则创建
    Session getSession();

    //如果session不存在是否创建session,true创建,false不创建
    Session getSession(boolean create);

    //用户执行退出,且删除和该用户绑定的相关的信息
    void logout();

    //绑定Runnable到当前的Subject中,在当前执行
    void execute(Runnable runnable);

    //绑定Callable到当前的Subject中,在当前执行
    <V> V execute(Callable<V> callable) throws ExecutionException;

    //返回一个封装过的Callable,然后用户可以选择开启一个新线程执行
    <V> Callable<V> associateWith(Callable<V> callable);

    //返回一个封装过的Runnable,然后用户可以选择开启一个新线程执行
    Runnable associateWith(Runnable runnable);

    //如果该用户的principle存在,允许该用户假定为其他用户的身份进行操作。
    void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException;

   //判断用户是否是假定身份
    boolean isRunAs();

    //获取假定身先前的真实身份
    PrincipalCollection getPreviousPrincipals();

    //释放假定身份改为真实身份
    PrincipalCollection releaseRunAs();

    /**
    该Builder使用了建造者模式(Builder design pattern),很容易的构造了Subject。
    如果要构造一个匿名的Subject(即不指向特定的用户),可通过new Subject.Builder().buildSubject().该方法默认绑定当前的SecurityManager。
    如果要构造一个根据SessionID来指向用户的Subject,则通过new Subject.Builder().sessionId(sessionId).buildSubject()
    如果要构造一个根据principle来指向用户的Subject,则通过new Subject.Builder().principals(principals).buildSubject()
    通过上面几个构造Subject的方法,大家也可以知道建造者模式的好处了
    1.当构造复杂的对象的算法独立于对象的组成部分以及他们的装配时(SecurityManager.createSubject(this.subjectContext)是真正构造Subject的算法,
    SecurityManager的实现类DefaultWebSecurityManager可根据SubjectContext的子类WebSubjectContext来根据特定需要的数据构造Subject)

    2.当构造过程必须允许构造的对象有不同的表现时(上面几个构造方法的体现)
    关于建造者模式,这里不多说,有需要的自己去百度
    **/
    public static class Builder {

        //构造Subject需要的数据 -》SubjectContext
        private final SubjectContext subjectContext;

        //SecurityManager.createSubject(SubjectContext)真正创建Subject的方法
        private final SecurityManager securityManager;


        public Builder() {
            this(SecurityUtils.getSecurityManager());
        }


        public Builder(SecurityManager securityManager) {
            if (securityManager == null) {
                throw new NullPointerException("SecurityManager method argument cannot be null.");
            }
            this.securityManager = securityManager;
        //获取SubjectContext
            this.subjectContext = newSubjectContextInstance();
            if (this.subjectContext == null) {
                throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                        "cannot be null.");
            }
            this.subjectContext.setSecurityManager(securityManager);
        }

        //获取一个新创建的DefaultSubjectContext,该方法的提供有利于子类可以重写,提供不同SubjectContext实现类
        protected SubjectContext newSubjectContextInstance() {
            return new DefaultSubjectContext();
        }


        protected SubjectContext getSubjectContext() {
            return this.subjectContext;
        }

        //设置subjectContext的属性SessionId,该方法有利于shiro根据SessionID查找该用户对应的session
        public Builder sessionId(Serializable sessionId) {
            if (sessionId != null) {
                this.subjectContext.setSessionId(sessionId);
            }
            return this;
        }


        public Builder host(String host) {
            if (StringUtils.hasText(host)) {
                this.subjectContext.setHost(host);
            }
            return this;
        }

        //通常使用sessionId()比较多
        public Builder session(Session session) {
            if (session != null) {
                this.subjectContext.setSession(session);
            }
            return this;
        }


        public Builder principals(PrincipalCollection principals) {
            if (!CollectionUtils.isEmpty(principals)) {
                this.subjectContext.setPrincipals(principals);
            }
            return this;
        }

        //如果获取不到Session,该参数决定是否要创建个新的Session
        public Builder sessionCreationEnabled(boolean enabled) {
            this.subjectContext.setSessionCreationEnabled(enabled);
            return this;
        }

        //该参数决定Subject是否已验证登陆
        public Builder authenticated(boolean authenticated) {
            this.subjectContext.setAuthenticated(authenticated);
            return this;
        }


        public Builder contextAttribute(String attributeKey, Object attributeValue) {
            if (attributeKey == null) {
                String msg = "Subject context map key cannot be null.";
                throw new IllegalArgumentException(msg);
            }
            if (attributeValue == null) {
                this.subjectContext.remove(attributeKey);
            } else {
                this.subjectContext.put(attributeKey, attributeValue);
            }
            return this;
        }

        //securityManager根据subjectContext来构造Subject返回
        public Subject buildSubject() {
            return this.securityManager.createSubject(this.subjectContext);
        }
    }

}

2.DelegatingSubject

/**
DelegatingSubject实际上相当于SecurityManager的代理,因为它的权限相关的方法都是调用SecurityManager的相关方法的。
且该实现类除了并没有真正存储任何关于特定用户的状态,这有利于application实现无状态架构。
/**
public class DelegatingSubject implements Subject {

    private static final Logger log = LoggerFactory.getLogger(DelegatingSubject.class);

    private static final String RUN_AS_PRINCIPALS_SESSION_KEY =
            DelegatingSubject.class.getName() + ".RUN_AS_PRINCIPALS_SESSION_KEY";

    protected PrincipalCollection principals;
    //是否已验证登陆
    protected boolean authenticated;
    protected String host;

    protected Session session;
    /**
     * @since 1.2
     */
    protected boolean sessionCreationEnabled;

    protected transient SecurityManager securityManager;

    public DelegatingSubject(SecurityManager securityManager) {
        this(null, false, null, null, securityManager);
    }

    public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
                             Session session, SecurityManager securityManager) {
        this(principals, authenticated, host, session, true, securityManager);
    }

    //since 1.2
    public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
                             Session session, boolean sessionCreationEnabled, SecurityManager securityManager) {
        if (securityManager == null) {
            throw new IllegalArgumentException("SecurityManager argument cannot be null.");
        }
        this.securityManager = securityManager;
        this.principals = principals;
        this.authenticated = authenticated;
        this.host = host;
        if (session != null) {
            this.session = decorate(session);
        }
        this.sessionCreationEnabled = sessionCreationEnabled;
    }
    //封装Subject内的Session
    protected Session decorate(Session session) {
        if (session == null) {
            throw new IllegalArgumentException("session cannot be null");
        }
        return new StoppingAwareProxiedSession(session, this);
    }

    public SecurityManager getSecurityManager() {
        return securityManager;
    }

    protected boolean hasPrincipals() {
        return !CollectionUtils.isEmpty(getPrincipals());
    }


    public String getHost() {
        return this.host;
    }

    private Object getPrimaryPrincipal(PrincipalCollection principals) {
        if (!CollectionUtils.isEmpty(principals)) {
            return principals.getPrimaryPrincipal();
        }
        return null;
    }


    public Object getPrincipal() {
        return getPrimaryPrincipal(getPrincipals());
    }
    //如果假定身份不为空,则获取假定身份的principle,否则的话RunAs还有什么意义?
    public PrincipalCollection getPrincipals() {
        List<PrincipalCollection> runAsPrincipals = getRunAsPrincipalsStack();
        return CollectionUtils.isEmpty(runAsPrincipals) ? this.principals : runAsPrincipals.get(0);
    }

    public boolean isPermitted(String permission) {
        return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
    }

    public boolean isPermitted(Permission permission) {
        return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
    }

    public boolean[] isPermitted(String... permissions) {
        if (hasPrincipals()) {
            return securityManager.isPermitted(getPrincipals(), permissions);
        } else {
            return new boolean[permissions.length];
        }
    }

    public boolean[] isPermitted(List<Permission> permissions) {
        if (hasPrincipals()) {
            return securityManager.isPermitted(getPrincipals(), permissions);
        } else {
            return new boolean[permissions.size()];
        }
    }

    public boolean isPermittedAll(String... permissions) {
        return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);
    }

    public boolean isPermittedAll(Collection<Permission> permissions) {
        return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);
    }
    //判断principle是否为空,为空则抛出异常
    protected void assertAuthzCheckPossible() throws AuthorizationException {
        if (!hasPrincipals()) {
            String msg = "This subject is anonymous - it does not have any identifying principals and " +
                    "authorization operations require an identity to check against.  A Subject instance will " +
                    "acquire these identifying principals automatically after a successful login is performed " +
                    "be executing " + Subject.class.getName() + ".login(AuthenticationToken) or when 'Remember Me' " +
                    "functionality is enabled by the SecurityManager.  This exception can also occur when a " +
                    "previously logged-in Subject has logged out which " +
                    "makes it anonymous again.  Because an identity is currently not known due to any of these " +
                    "conditions, authorization is denied.";
            throw new UnauthenticatedException(msg);
        }
    }

    public void checkPermission(String permission) throws AuthorizationException {
        assertAuthzCheckPossible();
        securityManager.checkPermission(getPrincipals(), permission);
    }

    public void checkPermission(Permission permission) throws AuthorizationException {
        assertAuthzCheckPossible();
        securityManager.checkPermission(getPrincipals(), permission);
    }

    public void checkPermissions(String... permissions) throws AuthorizationException {
        assertAuthzCheckPossible();
        securityManager.checkPermissions(getPrincipals(), permissions);
    }

    public void checkPermissions(Collection<Permission> permissions) throws AuthorizationException {
        assertAuthzCheckPossible();
        securityManager.checkPermissions(getPrincipals(), permissions);
    }

    public boolean hasRole(String roleIdentifier) {
        return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
    }

    public boolean[] hasRoles(List<String> roleIdentifiers) {
        if (hasPrincipals()) {
            return securityManager.hasRoles(getPrincipals(), roleIdentifiers);
        } else {
            return new boolean[roleIdentifiers.size()];
        }
    }

    public boolean hasAllRoles(Collection<String> roleIdentifiers) {
        return hasPrincipals() && securityManager.hasAllRoles(getPrincipals(), roleIdentifiers);
    }

    public void checkRole(String role) throws AuthorizationException {
        assertAuthzCheckPossible();
        securityManager.checkRole(getPrincipals(), role);
    }

    public void checkRoles(String... roleIdentifiers) throws AuthorizationException {
        assertAuthzCheckPossible();
        securityManager.checkRoles(getPrincipals(), roleIdentifiers);
    }

    public void checkRoles(Collection<String> roles) throws AuthorizationException {
        assertAuthzCheckPossible();
        securityManager.checkRoles(getPrincipals(), roles);
    }

    //用户登陆验证方法
    public void login(AuthenticationToken token) throws AuthenticationException {
        //先删除先前的假定身份,否则的话,当用户获取当前身份时,获取到的还是假定身份
        clearRunAsIdentitiesInternal();
    //调用securityManager.login执行真正的登陆验证方法,且返回个subject
        Subject subject = securityManager.login(this, token);

        PrincipalCollection principals;

        String host = null;
        //如果subject是DelegatingSubject的子类,则设置host和principals
        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;

            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
            String msg = "Principals returned from securityManager.login( token ) returned a null or " +
                    "empty value.  This value must be non null and populated with one or more elements.";
            throw new IllegalStateException(msg);
        }
        this.principals = principals;
    //设置身份验证为已验证成功
        this.authenticated = true;
        if (token instanceof HostAuthenticationToken) {
            host = ((HostAuthenticationToken) token).getHost();
        }
        if (host != null) {
            this.host = host;
        }
        Session session = subject.getSession(false);
        if (session != null) {
        //装饰session
            this.session = decorate(session);
        } else {
            this.session = null;
        }
    }

    public boolean isAuthenticated() {
        return authenticated;
    }

    public boolean isRemembered() {
        PrincipalCollection principals = getPrincipals();
        return principals != null && !principals.isEmpty() && !isAuthenticated();
    }


    protected boolean isSessionCreationEnabled() {
        return this.sessionCreationEnabled;
    }

    public Session getSession() {
        return getSession(true);
    }

    public Session getSession(boolean create) {
        if (log.isTraceEnabled()) {
            log.trace("attempting to get session; create = " + create +
                    "; session is null = " + (this.session == null) +
                    "; session has id = " + (this.session != null && session.getId() != null));
        }

        if (this.session == null && create) {

            //added in 1.2:
            if (!isSessionCreationEnabled()) {
                String msg = "Session creation has been disabled for the current subject.  This exception indicates " +
                        "that there is either a programming error (using a session when it should never be " +
                        "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
                        "for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +
                        "for more.";
                throw new DisabledSessionException(msg);
            }

            log.trace("Starting session for host {}", getHost());
            SessionContext sessionContext = createSessionContext();
            Session session = this.securityManager.start(sessionContext);
            this.session = decorate(session);
        }
        return this.session;
    }

    protected SessionContext createSessionContext() {
        SessionContext sessionContext = new DefaultSessionContext();
        if (StringUtils.hasText(host)) {
            sessionContext.setHost(host);
        }
        return sessionContext;
    }

    private void clearRunAsIdentitiesInternal() {
        //try/catch added for SHIRO-298
        try {
            clearRunAsIdentities();
        } catch (SessionException se) {
            log.debug("Encountered session exception trying to clear 'runAs' identities during logout.  This " +
                    "can generally safely be ignored.", se);
        }
    }
    //用户退出
    public void logout() {
        try {
            clearRunAsIdentitiesInternal();
            this.securityManager.logout(this);
        } finally {
            this.session = null;
            this.principals = null;
            this.authenticated = false;

        }
    }

    private void sessionStopped() {
        this.session = null;
    }

    public <V> V execute(Callable<V> callable) throws ExecutionException {
        Callable<V> associated = associateWith(callable);
        try {
            return associated.call();
        } catch (Throwable t) {
            throw new ExecutionException(t);
        }
    }

    public void execute(Runnable runnable) {
        Runnable associated = associateWith(runnable);
        associated.run();
    }

    public <V> Callable<V> associateWith(Callable<V> callable) {
        return new SubjectCallable<V>(this, callable);
    }

    public Runnable associateWith(Runnable runnable) {
        if (runnable instanceof Thread) {
            String msg = "This implementation does not support Thread arguments because of JDK ThreadLocal " +
                    "inheritance mechanisms required by Shiro.  Instead, the method argument should be a non-Thread " +
                    "Runnable and the return value from this method can then be given to an ExecutorService or " +
                    "another Thread.";
            throw new UnsupportedOperationException(msg);
        }
        return new SubjectRunnable(this, runnable);
    }
    //装饰Session的stop方法
    private class StoppingAwareProxiedSession extends ProxiedSession {

        private final DelegatingSubject owner;

        private StoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) {
            super(target);
            owner = owningSubject;
        }

        public void stop() throws InvalidSessionException {
            super.stop();
            owner.sessionStopped();
        }
    }


    //让该用户假设principals的身份
    public void runAs(PrincipalCollection principals) {
        if (!hasPrincipals()) {
            String msg = "This subject does not yet have an identity.  Assuming the identity of another " +
                    "Subject is only allowed for Subjects with an existing identity.  Try logging this subject in " +
                    "first, or using the " + Subject.Builder.class.getName() + " to build ad hoc Subject instances " +
                    "with identities as necessary.";
            throw new IllegalStateException(msg);
        }
        pushIdentity(principals);
    }
    //判断当前subject是否使用的是假设身份
    public boolean isRunAs() {
        List<PrincipalCollection> stack = getRunAsPrincipalsStack();
        return !CollectionUtils.isEmpty(stack);
    }
    //获取假设身份前一个的身份
    public PrincipalCollection getPreviousPrincipals() {
        PrincipalCollection previousPrincipals = null;
        List<PrincipalCollection> stack = getRunAsPrincipalsStack();
        int stackSize = stack != null ? stack.size() : 0;
        if (stackSize > 0) {
            if (stackSize == 1) {
                previousPrincipals = this.principals;
            } else {
                //always get the one behind the current:
                assert stack != null;
                previousPrincipals = stack.get(1);
            }
        }
        return previousPrincipals;
    }
    //释放当前的假设身份
    public PrincipalCollection releaseRunAs() {
        return popIdentity();
    }

    @SuppressWarnings("unchecked")
    private List<PrincipalCollection> getRunAsPrincipalsStack() {
        Session session = getSession(false);
        if (session != null) {
            return (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
        }
        return null;
    }

    private void clearRunAsIdentities() {
        Session session = getSession(false);
        if (session != null) {
            session.removeAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
        }
    }
    //压入假设的身份principals到session的RUN_AS_PRINCIPALS_SESSION_KEY里
    private void pushIdentity(PrincipalCollection principals) throws NullPointerException {
        if (CollectionUtils.isEmpty(principals)) {
            String msg = "Specified Subject principals cannot be null or empty for 'run as' functionality.";
            throw new NullPointerException(msg);
        }
        List<PrincipalCollection> stack = getRunAsPrincipalsStack();
        if (stack == null) {
            stack = new CopyOnWriteArrayList<PrincipalCollection>();
        }
        stack.add(0, principals);
        Session session = getSession();
        session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack);
    }
    //session的RUN_AS_PRINCIPALS_SESSION_KEY弹栈假设身份
    private PrincipalCollection popIdentity() {
        PrincipalCollection popped = null;

        List<PrincipalCollection> stack = getRunAsPrincipalsStack();
        if (!CollectionUtils.isEmpty(stack)) {
            popped = stack.remove(0);
            Session session;
            if (!CollectionUtils.isEmpty(stack)) {
                //persist the changed stack to the session
                session = getSession();
                session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack);
            } else {
                //stack is empty, remove it from the session:
                clearRunAsIdentities();
            }
        }

        return popped;
    }
}

3.WebDelegatingSubject

//WebDelegatingSubject实现了servlet request\response的支持。
public class WebDelegatingSubject extends DelegatingSubject implements WebSubject {

    private static final long serialVersionUID = -1655724323350159250L;

    private final ServletRequest servletRequest;
    private final ServletResponse servletResponse;

    public WebDelegatingSubject(PrincipalCollection principals, boolean authenticated,
                                String host, Session session,
                                ServletRequest request, ServletResponse response,
                                SecurityManager securityManager) {
        this(principals, authenticated, host, session, true, request, response, securityManager);
    }

    //since 1.2
    public WebDelegatingSubject(PrincipalCollection principals, boolean authenticated,
                                String host, Session session, boolean sessionEnabled,
                                ServletRequest request, ServletResponse response,
                                SecurityManager securityManager) {
        super(principals, authenticated, host, session, sessionEnabled, securityManager);
        this.servletRequest = request;
        this.servletResponse = response;
    }

    public ServletRequest getServletRequest() {
        return servletRequest;
    }

    public ServletResponse getServletResponse() {
        return servletResponse;
    }

    //重写父类的是否支持创建新session,如果当前session不存在
    @Override
    protected boolean isSessionCreationEnabled() {
        boolean enabled = super.isSessionCreationEnabled();

        return enabled && WebUtils._isSessionCreationEnabled(this);
    }
    //重写父类的createSessionContext,返回一个DefaultWebSessionContext
    @Override
    protected SessionContext createSessionContext() {
        WebSessionContext wsc = new DefaultWebSessionContext();
        String host = getHost();
        if (StringUtils.hasText(host)) {
            wsc.setHost(host);
        }
        wsc.setServletRequest(this.servletRequest);
        wsc.setServletResponse(this.servletResponse);
        return wsc;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值