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是绑定到当前线程,这肯定需要一个中介存储状态 
 

Java代码 

 收藏代码

  1. public static Subject getSubject() {  
  2.     Subject subject = ThreadContext.getSubject();  
  3.     if (subject == null) {  
  4.         subject = (new Builder()).buildSubject();  
  5.         ThreadContext.bind(subject);  
  6.     }  
  7.   
  8.     return subject;  
  9. }  


 

Java代码 

 收藏代码

  1. public abstract class ThreadContext {  
  2.     private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);  
  3.     public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";  
  4.     public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";  
  5.     private static final ThreadLocal<Map<Object, Object>> resources = new ThreadContext.InheritableThreadLocalMap();  
  6.   
  7.     protected ThreadContext() {  
  8.     }  
  9.   
  10.     public static Map<Object, Object> getResources() {  
  11.         return (Map)(resources.get() == null ? Collections.emptyMap() : new HashMap((Map)resources.get()));  
  12.     }  
  13.   
  14.     public static void setResources(Map<Object, Object> newResources) {  
  15.         if (!CollectionUtils.isEmpty(newResources)) {  
  16.             ensureResourcesInitialized();  
  17.             ((Map)resources.get()).clear();  
  18.             ((Map)resources.get()).putAll(newResources);  
  19.         }  
  20.     }  
  21.   
  22.     private static Object getValue(Object key) {  
  23.         Map<Object, Object> perThreadResources = (Map)resources.get();  
  24.         return perThreadResources != null ? perThreadResources.get(key) : null;  
  25.     }  
  26.   
  27.     private static void ensureResourcesInitialized() {  
  28.         if (resources.get() == null) {  
  29.             resources.set(new HashMap());  
  30.         }  
  31.   
  32.     }  
  33.   
  34.     public static Object get(Object key) {  
  35.         if (log.isTraceEnabled()) {  
  36.             String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";  
  37.             log.trace(msg);  
  38.         }  
  39.   
  40.         Object value = getValue(key);  
  41.         if (value != null && log.isTraceEnabled()) {  
  42.             String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" + key + "] bound to thread [" + Thread.currentThread().getName() + "]";  
  43.             log.trace(msg);  
  44.         }  
  45.   
  46.         return value;  
  47.     }  
  48.   
  49.     public static void put(Object key, Object value) {  
  50.         if (key == null) {  
  51.             throw new IllegalArgumentException("key cannot be null");  
  52.         } else if (value == null) {  
  53.             remove(key);  
  54.         } else {  
  55.             ensureResourcesInitialized();  
  56.             ((Map)resources.get()).put(key, value);  
  57.             if (log.isTraceEnabled()) {  
  58.                 String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" + key + "] to thread [" + Thread.currentThread().getName() + "]";  
  59.                 log.trace(msg);  
  60.             }  
  61.   
  62.         }  
  63.     }  
  64.   
  65.     public static Object remove(Object key) {  
  66.         Map<Object, Object> perThreadResources = (Map)resources.get();  
  67.         Object value = perThreadResources != null ? perThreadResources.remove(key) : null;  
  68.         if (value != null && log.isTraceEnabled()) {  
  69.             String msg = "Removed value of type [" + value.getClass().getName() + "] for key [" + key + "]from thread [" + Thread.currentThread().getName() + "]";  
  70.             log.trace(msg);  
  71.         }  
  72.   
  73.         return value;  
  74.     }  
  75.   
  76.     public static void remove() {  
  77.         resources.remove();  
  78.     }  
  79.   
  80.     public static SecurityManager getSecurityManager() {  
  81.         return (SecurityManager)get(SECURITY_MANAGER_KEY);  
  82.     }  
  83.   
  84.     public static void bind(SecurityManager securityManager) {  
  85.         if (securityManager != null) {  
  86.             put(SECURITY_MANAGER_KEY, securityManager);  
  87.         }  
  88.   
  89.     }  
  90.   
  91.     public static SecurityManager unbindSecurityManager() {  
  92.         return (SecurityManager)remove(SECURITY_MANAGER_KEY);  
  93.     }  
  94.   
  95.     public static Subject getSubject() {  
  96.         return (Subject)get(SUBJECT_KEY);  
  97.     }  
  98.   
  99.     public static void bind(Subject subject) {  
  100.         if (subject != null) {  
  101.             put(SUBJECT_KEY, subject);  
  102.         }  
  103.   
  104.     }  
  105.   
  106.     public static Subject unbindSubject() {  
  107.         return (Subject)remove(SUBJECT_KEY);  
  108.     }  
  109.   
  110.     private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> {  
  111.         private InheritableThreadLocalMap() {  
  112.         }  
  113.   
  114.         protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {  
  115.             return parentValue != null ? (Map)((HashMap)parentValue).clone() : null;  
  116.         }  
  117.     }  
  118. }  



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

经过源码分析,核心实现如下DefaultSecurityManager类中: 

Java代码 

 收藏代码

  1. public Subject createSubject(SubjectContext subjectContext) {  
  2.       SubjectContext context = this.copy(subjectContext);  
  3.       context = this.ensureSecurityManager(context);  
  4.       context = this.resolveSession(context);  
  5.       context = this.resolvePrincipals(context);  
  6.       Subject subject = this.doCreateSubject(context);  
  7.       this.save(subject);  
  8.       return subject;  
  9.   }  



每次请求都会重新设置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 

实现代码: 
 

Java代码 

 收藏代码

  1. import org.apache.shiro.session.mgt.SessionKey;  
  2. import org.apache.shiro.web.servlet.ShiroHttpServletRequest;  
  3. import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;  
  4. import org.apache.shiro.web.util.WebUtils;  
  5. import org.slf4j.Logger;  
  6. import org.slf4j.LoggerFactory;  
  7.   
  8. import javax.servlet.ServletRequest;  
  9. import javax.servlet.ServletResponse;  
  10. import javax.servlet.http.HttpServletRequest;  
  11. import javax.servlet.http.HttpServletResponse;  
  12. import java.io.Serializable;  
  13. import java.util.UUID;  
  14.   
  15. /** 
  16.  * @author zxj<br> 
  17.  * 时间 2017/11/8 15:55 
  18.  * 说明 ... 
  19.  */  
  20. public class StatelessSessionManager extends DefaultWebSessionManager {  
  21.     /** 
  22.      * 这个是服务端要返回给客户端, 
  23.      */  
  24.     public final static String TOKEN_NAME = "TOKEN";  
  25.     /** 
  26.      * 这个是客户端请求给服务端带的header 
  27.      */  
  28.     public final static String HEADER_TOKEN_NAME = "token";  
  29.     public final static Logger LOG = LoggerFactory.getLogger(StatelessSessionManager.class);  
  30.   
  31.   
  32.     @Override  
  33.     public Serializable getSessionId(SessionKey key) {  
  34.         Serializable sessionId = key.getSessionId();  
  35.         if(sessionId == null){  
  36.             HttpServletRequest request = WebUtils.getHttpRequest(key);  
  37.             HttpServletResponse response = WebUtils.getHttpResponse(key);  
  38.             sessionId = this.getSessionId(request,response);  
  39.         }  
  40.         HttpServletRequest request = WebUtils.getHttpRequest(key);  
  41.         request.setAttribute(TOKEN_NAME,sessionId.toString());  
  42.         return sessionId;  
  43.     }  
  44.   
  45.     @Override  
  46.     protected Serializable getSessionId(ServletRequest servletRequest, ServletResponse servletResponse) {  
  47.         HttpServletRequest request = (HttpServletRequest) servletRequest;  
  48.         String token = request.getHeader(HEADER_TOKEN_NAME);  
  49.         if(token == null){  
  50.             token = UUID.randomUUID().toString();  
  51.         }  
  52.   
  53.         //这段代码还没有去查看其作用,但是这是其父类中所拥有的代码,重写完后我复制了过来...开始  
  54.         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,  
  55.                 ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);  
  56.         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);  
  57.         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);  
  58.         request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());  
  59.         //这段代码还没有去查看其作用,但是这是其父类中所拥有的代码,重写完后我复制了过来...结束  
  60.         return token;  
  61.     }  
  62.   
  63. }  

 

Java代码 

 收藏代码

  1. @RequestMapping("/")  
  2.     public void login(@RequestParam("code")String code, HttpServletRequest request){  
  3.         Map<String,Object> data = new HashMap<>();  
  4.         if(SecurityUtils.getSubject().isAuthenticated()){  
  5.         //这里代码着已经登陆成功,所以自然不用再次认证,直接从rquest中取出就行了,  
  6.             data.put(StatelessSessionManager.HEADER_TOKEN_NAME,getServerToken());  
  7.             data.put(BIND,ShiroKit.getUser().getTel() != null);  
  8.             response(data);  
  9.         }  
  10.         LOG.info("授权码为:" + code);  
  11.         AuthorizationService authorizationService = authorizationFactory.getAuthorizationService(Constant.clientType);  
  12.         UserDetail authorization = authorizationService.authorization(code);  
  13.   
  14.   
  15.   
  16.         Oauth2UserDetail userDetail = (Oauth2UserDetail) authorization;  
  17.   
  18.         loginService.login(userDetail);  
  19.         User user = userService.saveUser(userDetail,Constant.clientType.toString());  
  20.         ShiroKit.getSession().setAttribute(ShiroKit.USER_DETAIL_KEY,userDetail);  
  21.         ShiroKit.getSession().setAttribute(ShiroKit.USER_KEY,user);  
  22.         data.put(BIND,user.getTel() != null);  
  23.       //这里的代码,必须放到login之执行,因为login后,才会创建session,才会得到最新的token咯  
  24.         data.put(StatelessSessionManager.HEADER_TOKEN_NAME,getServerToken());  
  25.         response(data);  
  26.     }  


 

Java代码 

 收藏代码

  1. import org.apache.shiro.mgt.SecurityManager;  
  2. import org.apache.shiro.realm.Realm;  
  3. import org.apache.shiro.spring.LifecycleBeanPostProcessor;  
  4. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;  
  5. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;  
  6. import org.springframework.context.annotation.Bean;  
  7. import org.springframework.context.annotation.Configuration;  
  8.   
  9. import java.util.LinkedHashMap;  
  10. import java.util.Map;  
  11.   
  12. /** 
  13.  * @author zxj<br> 
  14.  * 时间 2017/11/8 15:40 
  15.  * 说明 ... 
  16.  */  
  17. @Configuration  
  18. public class ShiroConfiguration {  
  19.   
  20.     @Bean  
  21.     public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){  
  22.         return new LifecycleBeanPostProcessor();  
  23.     }  
  24.   
  25.     /** 
  26.      * 此处注入一个realm 
  27.      * @param realm 
  28.      * @return 
  29.      */  
  30.     @Bean  
  31.     public SecurityManager securityManager(Realm realm){  
  32.         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();  
  33.         securityManager.setSessionManager(new StatelessSessionManager());  
  34.         securityManager.setRealm(realm);  
  35.         return securityManager;  
  36.     }  
  37.   
  38.     @Bean  
  39.     public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){  
  40.         ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();  
  41.         bean.setSecurityManager(securityManager);  
  42.   
  43.         Map<String,String> map = new LinkedHashMap<>();  
  44.         map.put("/public/**","anon");  
  45.         map.put("/login/**","anon");  
  46.         map.put("/**","user");  
  47.         bean.setFilterChainDefinitionMap(map);  
  48.   
  49.         return bean;  
  50.     }  
  51. }  

转载出处:https://ahua186186.iteye.com/blog/2407608

  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值