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

目录
  • SecurityUtils.getSubject().getPrincipal()为null
    • 解决办法
  • shiro SecurityUtils.getSubject()深度分析

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

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

(0)

相关推荐

  • spring-shiro权限控制realm实战教程

    目录 spring-shiro权限控制realm 用户与角色实体 Realm类 Shiro 配置类 控制器 Service shiro权限不生效原因分析 shiro遇到的坑 问题原因:权限标签定义问题 spring-shiro权限控制realm 用户与角色实体 Role.java @Data @Entity public class Role { @Id @GeneratedValue private Integer id; private Long userId; private String

  • Spring MVC整合Shiro权限控制的方法

    Apache Shiro 是一个功能强大且灵活的开放源代码安全框架,可以细粒度地处理认证 (Authentication),授权 (Authorization),会话 (Session) 管理和加密 (cryptography) 等企业级应用中常见的安全控制流程. Apache Shiro 的首要目标是易于使用和理解. 有时候安全性的流程控制会非常复杂,对开发人员来说是件很头疼的事情,但并不一定如此. 框架就应该尽可能地掩盖复杂性,并公开一个简洁而直观的 API,从而简化开发人员的工作,确保其应

  • SpringBoot基于Shiro处理ajax请求代码实例

    写一个Shiro的过滤器 import cn.erika.demo.common.model.vo.Message; import com.alibaba.fastjson.JSON; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.servlet.AdviceFilter; import javax.servlet.Servle

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

    目录 SecurityUtils.getSubject().getPrincipal()为null 解决办法 shiro SecurityUtils.getSubject()深度分析 SecurityUtils.getSubject().getPrincipal()为null 我在项目中获取getUserId(),和getUserName()获取不到值. 他们都是通过SecurityUtils.getSubject().getPrincipal()去获取的. 反复测试发现原因是 :在shiroC

  • SpringBoot Mybatis Plus公共字段自动填充功能

    一.应用场景 平时在建对象表的时候都会有最后修改时间,最后修改人这两个字段,对于这些大部分表都有的字段,每次在新增和修改的时候都要考虑到这几个字段有没有传进去,很麻烦.mybatisPlus有一个很好的解决方案.也就是公共字段自动填充的功能.一般满足下面条件的字段就可以使用此功能: 这个字段是大部分表都会有的. 这个字段的值是固定的,或则字段值是可以在后台动态获取的. 常用的就是last_update_time,last_update_name这两个字段. 二.配置MybatisPlus 导包:

  • SpringBoot Shiro授权实现过程解析

    这篇文章主要介绍了SpringBoot Shiro授权实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 使用Shiro过滤器实现授权 设置好授权拦截跳转的请求地址 /** * 创建ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") Defaul

  • spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制

    1.添加maven依赖(先安装好cas-server-3.5.2,安装步骤请查看本文参考文章) <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>

  • SpringBoot整合Shiro实现登录认证的方法

    安全无处不在,趁着放假读了一下 Shiro 文档,并记录一下 Shiro 整合 Spring Boot 在数据库中根据角色控制访问权限 简介 Apache Shiro是一个功能强大.灵活的,开源的安全框架.它可以干净利落地处理身份验证.授权.企业会话管理和加密. 上图是 Shiro 的基本架构 Authentication(认证) 有时被称为"登录",用来证明用户是用户他们自己本人 Authorization(授权) 访问控制的过程,即确定"谁"访问"什么

  • spring boot 集成 shiro 自定义密码验证 自定义freemarker标签根据权限渲染不同页面(推荐

    项目里一直用的是 spring-security ,不得不说,spring-security 真是东西太多了,学习难度太大(可能我比较菜),这篇博客来总结一下折腾shiro的成果,分享给大家,强烈推荐shiro,真心简单 : ) 引入依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4

  • SpringBoot2.0整合Shiro框架实现用户权限管理的示例

    GitHub源码地址:知了一笑 https://github.com/cicadasmile/middle-ware-parent 一.Shiro简介 1.基础概念 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.作为一款安全框架Shiro的设计相当巧妙.Shiro的应用不依赖任何容器,它不仅可以在JavaEE下使用,还可以应用在JavaSE环境中. 2.核心角色 1)Subject:认证主体 代表当前系统的使用者,就是用户,在Shiro的认证中,

  • Spring Boot集成Shiro实现动态加载权限的完整步骤

    一.前言 本文小编将基于 SpringBoot 集成 Shiro 实现动态uri权限,由前端vue在页面配置uri,Java后端动态刷新权限,不用重启项目,以及在页面分配给用户 角色 . 按钮 .uri 权限后,后端动态分配权限,用户无需在页面重新登录才能获取最新权限,一切权限动态加载,灵活配置 基本环境 spring-boot 2.1.7 mybatis-plus 2.1.0 mysql 5.7.24 redis 5.0.5 温馨小提示:案例demo源码附文章末尾,有需要的小伙伴们可参考哦 ~

  • Springboot+Shiro记录用户登录信息并获取当前登录用户信息的实现代码

    由于最近做项目需要,在用户登陆后有一个功能是需要用户的信息,进行写入数据库的操作.但是目前还用不到Shiro的高级权限,只为了简单获取用户信息,自己整合了一个只记录用户,获取用户信息的功能. 导入Shiro依赖 <!-- Shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version

随机推荐