SecurityUtils.getSubject().getPrincipal()为null的问题

 更新时间:2022年07月01日 09:56:17   作者:神雕大侠mu  
这篇文章主要介绍了SecurityUtils.getSubject().getPrincipal()为null的问题及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

SecurityUtils.getSubject().getPrincipal()为null

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

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

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

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

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

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

解决办法

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

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

修改为:

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。 

相关文章

  • java实现二分法的完整代码

    java实现二分法的完整代码

    这篇文章主要为大家详细介绍了java实现二分法的完整代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-11-11
  • idea2020.1设置多个spring boot的service启动的实现

    idea2020.1设置多个spring boot的service启动的实现

    这篇文章主要介绍了idea2020.1设置多个spring boot的service启动,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • SpringBoot接口限流的实现方法小结

    SpringBoot接口限流的实现方法小结

    在一个高并发系统中对流量的把控是非常重要的,当巨大的流量直接请求到我们的服务器上没多久就可能造成接口不可用,不处理的话甚至会造成整个应用不可用,所以我们需要接口限流,本文给大家介绍了SpringBoot接口限流的实现方法,需要的朋友可以参考下
    2024-10-10
  • servlet之session工作原理简介_动力节点Java学院整理

    servlet之session工作原理简介_动力节点Java学院整理

    这篇文章主要介绍了servlet之session工作原理简介,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • mybatis-plus自动装配时间失效的解决

    mybatis-plus自动装配时间失效的解决

    本文主要介绍了mybatis-plus自动装配时间失效,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • Java日常练习题,每天进步一点点(58)

    Java日常练习题,每天进步一点点(58)

    下面小编就为大家带来一篇Java基础的几道练习题(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望可以帮到你
    2021-08-08
  • java 桥模式(Bridge Pattern)详解

    java 桥模式(Bridge Pattern)详解

    这篇文章主要介绍了java 桥模式(Bridge Pattern)详解的相关资料,需要的朋友可以参考下
    2016-10-10
  • mybatis-plus @DS实现动态切换数据源原理

    mybatis-plus @DS实现动态切换数据源原理

    本文主要介绍了mybatis-plus @DS实现动态切换数据源原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Mybatis自定义TypeHandler解决特殊类型转换问题详解

    Mybatis自定义TypeHandler解决特殊类型转换问题详解

    这篇文章主要介绍了Mybatis自定义TypeHandler解决特殊类型转换问题详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • 解析Java程序中对象内存的分配和控制的基本方法

    解析Java程序中对象内存的分配和控制的基本方法

    这篇文章主要介绍了解析Java程序中对象内存的分配和控制的基本方法,包括计算对象的内存占用的方法,要的朋友可以参考下
    2016-04-04

最新评论