Spring Security认证之登录用户数据获取

本文内容来自王松老师的《深入浅出Spring Security》,自己在学习的时候为了加深理解顺手抄录的,有时候还会写一些自己的想法。

登录用户数据获取

        登录成功之后,在后续的业务逻辑中开发者可能还需要获取到登录成功的用户对象,如果不是用任何安全管理框架那么可以将用户信息保存在HttpSession中,如果以后需要的话可以从HttpSession中获取数据。在Spring Security汇总,用户登录信息本质上还是保存在HttpSession中的,但是为了方便使用Spring Security对HttpSession中的用户信息进行了封装。

        获取登录用户的信息离不开一个重要的对象:Authentication。在Spring Security中,Authentication对象主要由两方面功能:

  • 作为AuthenticationManager的输入参数,提供用户身份认证的凭证。当它作为一个入参是,它的isAuthenticated方法返回false,表示用户还未认证。
  • 代表已经认证过的用户,此时的Authentication可以从SecurityContext中获取。

        一个Authentication对象主要包含三个方面的信息:

  • principal:定义认证的用户。如果用户使用用户名、密码的方式登录,principal通常就是一个UserDetails对象。
  • credentials:登录凭证,一般是指密码。当用户登录成功之后,登录凭证就会自动擦数,以防泄漏。
  • authorities:用户被授予的权限信息

        Java中本身提供了一个Principal接口来描述认证主体,Principal可以代表一个公司、个人或者登陆者ID。Spring Security中定义了Authentication接口用来规范登录用户信息,Authentication继承自Principal:


public interface Authentication extends Principal, Serializable {
    Collection getAuthorities();

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

        这里接口中定义的方法都很好理解:

  • getAuthorities方法:用来获取用户权限
  • getCredentials方法:用来获取用户凭证,一般来说是密码
  • getDetails方法:用来获取用户详细信息,可能是当前请求之类
  • getPrincipal方法:用来获取当前用户信息,可能是一个用户名,也可能是一个用户对象
  • isAuthenticated方法:当前用户是否认证成功

        可以看到在Spring Security中,只要获得Authentication对象就能获取到登录用户的详细信息。

        不同的认证方式对应不同的Authentication实例。在Spring Security中的Authentication实例类如下图:

Spring Security认证之登录用户数据获取_第1张图片

        这些实现类看起来可能比较陌生,在后续的学习中这些实现类基本上都会涉及。在这些Authentication的实例中,最常用的的两个类:UsernamePasswordAuthenticationToken和RememberMeAuthentication。这里我们先混个眼熟,后面的学习中我们会学习到。大致了解了Authentication对象之后,接下来我们看下如何在登录成功后获取用户登录信息,即Authentication对象。

从SecurityContextHolder中获取

        在前面的学习项目中增加一个UserController,内容如下:

/**
 * @author tlh
 * @date 2022/11/17 21:11
 */
@RestController
public class UserController {

    @GetMapping("/user")
    public Object userInfo() throws JsonProcessingException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String name = authentication.getName();
        Collection authorities = authentication.getAuthorities();
        ObjectMapper mapper = new ObjectMapper();
        Map userMap = new HashMap(2);
        userMap.put("userName", name);
        userMap.put("authorities", authorities);
        return mapper.writeValueAsString(userMap);
    }
}

        配置成功后启动项目,登录成功后访问/user接口,会返回用户名字和用户所拥有的的权限信息:

Spring Security认证之登录用户数据获取_第2张图片

        这里看到SecurityContextHolder.getContext( )是一个静态方法,也就意味着我们随时可以获取到登录的用户信息,在service层中也可以轻松获取到。获取用户登录信息的代码很简单,那么SecurityContextHolder到底是什么?它里面的数据又是从何而来的?

 

SecurityContextHolder

        SecurityContextHolder中存储的是SecurityContext,SecurityContext中存储的是Authentication。三者的关系如下图:

Spring Security认证之登录用户数据获取_第3张图片

 

        在SecurityContextHolder中存储SecurityContext定义了三种存储模式。使用的典型的策略模式。

  • MODE_THREADLOCAL:该种模式存放策略是将SecurityContext存放在ThreadLocal中。大家都知道ThreadLocal的特点就是在哪个线程中存储就只能在对应的线程中读取到,这个模式非常合适Web应用。应为一个请求无论经过多少个Filter到达Servlet都是有一个线程来处理。该模式也是Spring Security默认的存储SecurityContext的模式。
  • MODE_INHERITABLETHREADLOCAL:该种存储模式适用于多线程环境,如果希望子线程也能获取到登录用户的数据,就可以使用该模式。
  • MODE_GLOBAL:该模式实际使用了一个静态变量来存储SecurityContext。 

        我们看到SecurityContextHolder的源码大致上就看明白:

public class SecurityContextHolder {
    public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
    public static final String MODE_GLOBAL = "MODE_GLOBAL";
    private static final String MODE_PRE_INITIALIZED = "MODE_PRE_INITIALIZED";
    public static final String SYSTEM_PROPERTY = "spring.security.strategy";
    private static String strategyName = System.getProperty("spring.security.strategy");
    private static SecurityContextHolderStrategy strategy;
    private static int initializeCount = 0;

    public SecurityContextHolder() {
    }

    private static void initialize() {
        initializeStrategy();
        ++initializeCount;
    }

    //根据 strategyName 来加载对应的SecurityContextHolderStrategy(定义了各种保存SecurityContext的策略接口)
    private static void initializeStrategy() {
        if ("MODE_PRE_INITIALIZED".equals(strategyName)) {
            Assert.state(strategy != null, "When using MODE_PRE_INITIALIZED, setContextHolderStrategy must be called with the fully constructed strategy");
        } else {
              //默认采用ThreadLocal保存SecurityContext
            if (!StringUtils.hasText(strategyName)) {
                strategyName = "MODE_THREADLOCAL";
            }
            if (strategyName.equals("MODE_THREADLOCAL")) {
                strategy = new ThreadLocalSecurityContextHolderStrategy();
            } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
                //InheritableThreadLocal不同于ThreadLoca在于在创建子线程时会将父线程中的数据复制到子线程中
                strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
            } else if (strategyName.equals("MODE_GLOBAL")) {
                //采用一个静态变量保存SecurityContext
                strategy = new GlobalSecurityContextHolderStrategy();
            } else {
                try {
                    Class clazz = Class.forName(strategyName);
                    Constructor customStrategy = clazz.getConstructor();
                    strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
                } catch (Exception var2) {
                    ReflectionUtils.handleReflectionException(var2);
                }
            }
        }
    }
    //省略。。。
}

        

你可能感兴趣的:(Spring,Security,SecurityContext,用户信息获取)