`
ahua186186
  • 浏览: 554163 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

shiro SecurityUtils.getSubject()深度分析

 
阅读更多
1.总的来说,SecurityUtils.getSubject()是每个请求创建一个Subject, 并保存到ThreadContext的resources(ThreadLocal<Map<Object, Object>>)变量中,也就是一个http请求一个subject,并绑定到当前线程。

问题来了:.subject.login()登陆认证成功后,下一次请求如何知道是那个用户的请求呢?

友情提示:本文唯一可以读一下的就是分析这个疑问,如果你已经明白就不用往下看了。

首先给出内部原理:1个请求1个Subject原理:由于ShiroFilterFactoryBean本质是个AbstractShiroFilter过滤器,所以每次请求都会执行doFilterInternal里面的createSubject方法。

猜想:因为subject是绑定到当前线程,这肯定需要一个中介存储状态

    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;
        }
    }
}


subject登陆成功后,下一次请求如何知道是那个用户的请求呢?

经过源码分析,核心实现如下DefaultSecurityManager类中:
  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;
    }



每次请求都会重新设置Session和Principals,看到这里大概就能猜到:如果是web工程,直接从web容器获取httpSession,然后再从httpSession获取Principals,本质就是从cookie获取用户信息,然后每次都设置Principal,这样就知道是哪个用户的请求,并只得到这个用户有没有人认证成功,--本质:依赖于浏览器的cookie来维护session的

扩展,如果不是web容器的app,如何实现实现无状态的会话

1.一般的作法会在header中带有一个token,或者是在参数中,后台根据这个token来进行校验这个用户的身份,但是这个时候,servlet中的session就无法保存,我们在这个时候,就要实现自己的会话创建,普通的作法就是重写session与request的接口,然后在过滤器在把它替换成自己的request,所以得到的session也是自己的session,然后根据token来创建和维护会话

2.shiro实现:

重写shiro的sessionManage

代码实现转自:http://www.cnblogs.com/zhuxiaojie/p/7809767.html

实现代码:

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>
 * 时间 2017/11/8 15:55
 * 说明 ...
 */
public class StatelessSessionManager extends DefaultWebSessionManager {
    /**
     * 这个是服务端要返回给客户端,
     */
    public final static String TOKEN_NAME = "TOKEN";
    /**
     * 这个是客户端请求给服务端带的header
     */
    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();
        }

        //这段代码还没有去查看其作用,但是这是其父类中所拥有的代码,重写完后我复制了过来...开始
        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());
        //这段代码还没有去查看其作用,但是这是其父类中所拥有的代码,重写完后我复制了过来...结束
        return token;
    }

}

@RequestMapping("/")
    public void login(@RequestParam("code")String code, HttpServletRequest request){
        Map<String,Object> data = new HashMap<>();
        if(SecurityUtils.getSubject().isAuthenticated()){
        //这里代码着已经登陆成功,所以自然不用再次认证,直接从rquest中取出就行了,
            data.put(StatelessSessionManager.HEADER_TOKEN_NAME,getServerToken());
            data.put(BIND,ShiroKit.getUser().getTel() != null);
            response(data);
        }
        LOG.info("授权码为:" + 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);
      //这里的代码,必须放到login之执行,因为login后,才会创建session,才会得到最新的token咯
        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>
 * 时间 2017/11/8 15:40
 * 说明 ...
 */
@Configuration
public class ShiroConfiguration {

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

    /**
     * 此处注入一个realm
     * @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;
    }
}
分享到:
评论

相关推荐

    shiro1.7.1.zip

    最新版shiro1.7.1.jar包,安全升级专用

    ShiroConfig.java

    ShiroConfig.java 看博客吧

    shiro.freemarker.ShiroTags已打包

    com.jagregory.shiro.freemarker.ShiroTags 已经打包过的权限标签,可直接使用

    Shiro教程.pdf

    java的一款安全,权限的校验框架.

    shiro_tool.zip

    一款好用的shiro检测利用工具,使用方式java -jar shiro_tool.jar https://xx.xx.xx.xx,Github有开源下载链接,在这里上传是为了赚积分下载别的工具,欢迎使用

    shiro认证.docx

    shiro的认证部分,从导入jar包引入依赖开始,到最后测试使用。 。。

    gradle+springboot+mybaits+shiro+......

    身份认证,整合Apache Shiro和自定义拦截器两种方式13.发送邮件14.消息队列,集成RabbitMQ15.生成token 集成jwt1后台gradle构建,2springboot,3,系统架构,4.模板引擎---thymeleaf或者json返回,集成fastjson,5....

    spring boot框架继承代码生成以及mybatis以及shiro登录.rar

    spring boot框架继承代码生成以及mybatis以及shiro登录.rar

    sabar:go语言权限框架,打算试试模拟shiro写一个相似的功能

    Subject currentUser = SecurityUtils.getSubject();currentUser.login(token);import("github.com/itwarcraft/sabar")currentUser := sabar.GetSubject()currentUser.Login(sabar.UsernamePasswordToken{Name:...

    shiro 1.4.1.rar

    Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大. Shiro 可以非常容易的开发出足够好的应用,其不仅...

    计算机后端-Java-java高手加薪课视频教程14 shiro资.mp4

    计算机后端-Java-java高手加薪课视频教程14 shiro资.mp4

    shiro1.6.0.zip

    最新shiro1.6.0最全的jar包,包含shiro-features-1.6.0-features.xml的配置以及shiro-core shiro-spring shiro-web 等等一系列jar

    开涛Shiro教程.

    Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能...本教程只介绍基本的Shiro使用,不会过多分析源码等,重在使用。

    shiro-core-1.4.0.jar

    项目整合shiro时使用的jar包..

    shiro认证.pdf

    shiro 认证 #资源达人分享计划 # 技术文档

    shiro介绍.pdf

    shiro 初步介绍认识 #资源达人分享计划 # 技术文档

    shiroDemo.rar

    springboot+shiro+jwt+redis集成 ,springboot集成shiro控制权限,前后端分离,返回json jwt做验证。

    SpringBoot与Shiro整合.docx

    SpringBoot与Shiro整合-权限管理实战-课堂笔记.docx

    ShiroDemo.zip

    SpringBoot整合Shiro案例,实现用户登录认证、权限认证功能。代码中包含详细注释。适合初学Shiro者。

Global site tag (gtag.js) - Google Analytics