温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

SecurityUtils.getSubject().getPrincipal()为null的问题怎么解决

发布时间:2022-07-01 14:06:55 来源:亿速云 阅读:670 作者:iii 栏目:开发技术

本篇内容主要讲解“SecurityUtils.getSubject().getPrincipal()为null的问题怎么解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“SecurityUtils.getSubject().getPrincipal()为null的问题怎么解决”吧!

    SecurityUtils.getSubject().getPrincipal()为null

    我在项目中获取getUserId(),和getUserName()获取不到值。

    他们都是通过SecurityUtils.getSubject().getPrincipal()去获取的。

    反复测试发现原因是 :在shiroConfig里面:

    该方法,注意(是该接口名)被写为anon,不通过验证,即:

    filterMap.put("/druid/**", “anon”);

    这种写法是为了postman测试时不被拦截。

    解决办法

    从页面访问后端不需要anon了。

    • anon是不做拦截,authc是走自定义拦截

    • oauth3是自带拦截

    修改为:

    filterMap.put("/druid/**", “authc”);

    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 

    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);  
      
            Oauth3UserDetail userDetail = (Oauth3UserDetail) 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;  
        }  
    }

    到此,相信大家对“SecurityUtils.getSubject().getPrincipal()为null的问题怎么解决”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI