【Spring】使用ThreadLocal获取当前用户的信息

传统通过session获取用户信息会增加代码耦合性,可使用ThreadLocal将当前用户信息存入session,需要时从全局ThreadLocal中取出。实践操作包括创建RequestHolder类定义相关方法、定义拦截器防止内存泄漏、利用过滤器将请求和用户信息存入ThreadLocal并在web.xml中配置,最后可直接通过RequestHolder方法获取用户信息。

引入:

一提到获取用户信息,大部分人想到的都是通过session来获取用户信息,当要想获取到session,就需要获取对应的request,这样,就会增加代码的耦合性,基于以上缺陷,我们可以使用ThreadLocal来将当前的用户信息存入一个session,需要的时候,直接从全局的ThreadLocal中取出来即可

接下来我们实践操作一波:

创建一个RequestHolder类,定义相关的set和get用户信息的方法

/**
 * @author evan_qb
 * @date 2018/8/29 15:09
 */
public class RequestHolder {
    private static final ThreadLocal<SysUser> userHolder = new ThreadLocal<SysUser>();

    private static final ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>();

    public static void add(SysUser sysUser){
        userHolder.set(sysUser);
    }

    public static void add(HttpServletRequest request){
        requestHolder.set(request);
    }

    public static SysUser getCurrentUser(){
        return userHolder.get();
    }

    public static HttpServletRequest getCurrentRequest(){
        return requestHolder.get();
    }

    public static void remove(){
        userHolder.remove();
        requestHolder.remove();
    }

}

然后定义一个拦截器用于移除对应的ThreadLocal对象,防止内存泄漏

public class HttpInterceptor extends HandlerInterceptorAdapter {

    private static final String START_TIME = "requestStartTime";

    /**
     * 处理前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI().toString();
        Map parameterMap = request.getParameterMap();
        log.info("request start url:{},params:{}",url,JsonMapper.object2String(parameterMap));
        long startTime = System.currentTimeMillis();
        request.setAttribute(START_TIME,startTime);
        return true;
    }

    /**
     * 处理后调用(正常)
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        /*String url = request.getRequestURI().toString();
        Map parameterMap = request.getParameterMap();
        log.info("request finish url:{},params:{}",url,JsonMapper.object2String(parameterMap));*/

    }

    /**
     * 处理后调用(任何情况)
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String url = request.getRequestURI().toString();
        long start = (long) request.getAttribute(START_TIME);
        long end = System.currentTimeMillis();
        log.info("request exception url:{},cost:{}ms",url,end - start);
        removeThreadLocalInfo();
    }

    /**
     * 移除信息
     */
    public void removeThreadLocalInfo(){
        RequestHolder.remove();
    }
}

这时,我们需要利用一个过滤器LoginFilter,将每次的请求,和每次的用户信息存入ThreadLocal中

/**
 * @author evan_qb
 * @date 2018/8/29 15:29
 */
public class LoginFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        String path = req.getServletPath();

        SysUser sysUser = (SysUser) WebUtils.getSessionAttribute(req,"user");
        if (sysUser == null){
            resp.sendRedirect("/signin.jsp");
            return;
        }
        RequestHolder.add(sysUser);
        RequestHolder.add(req);
        chain.doFilter(request,response);
        return;
    }

    @Override
    public void destroy() {

    }
}

同时在web.xml中配置好LoginFilter

<filter>
  <filter-name>loginFilter</filter-name>
  <filter-class>cn.qblank.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>loginFilter</filter-name>
  <url-pattern>/sys/*</url-pattern>
  <url-pattern>/admin/*</url-pattern>
</filter-mapping>

当我们需要用户的信息时,我们可以直接通过RequestHolder的方法直接获取

 

 

### Spring Security 使用 ThreadLocal 获取用户信息并实现鉴权 在 Spring Security 中,`ThreadLocal` 是用于保存当前线程中用户信息的核心机制。以下是实现从 `ThreadLocal` 获取用户信息并进行鉴权的方法和步骤。 #### 1. 获取用户信息 Spring Security 提供了 `SecurityContextHolder` 类来存储当前线程的用户信息。默认情况下,`SecurityContextHolder` 使用 `ThreadLocal` 来存储这些信息[^2]。可以通过以下方式获取当前登录用户的详细信息: ```java import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; public class SecurityUtil { public static String getCurrentUsername() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || authentication.getPrincipal() == "anonymousUser") { return null; // 用户未登录 } return authentication.getName(); // 返回用户名 } public static Object getPrincipal() { return SecurityContextHolder.getContext().getAuthentication().getPrincipal(); // 返回用户对象 } } ``` #### 2. 鉴权实现 Spring Security 的鉴权流程通过一系列过滤器完成,其中 `FilterSecurityInterceptor` 是核心组件之一。它负责根据配置的规则对请求进行权限校验[^1]。自定义鉴权逻辑可以通过实现 `AccessDecisionManager` 接口来完成。 ##### 自定义 AccessDecisionManager 示例 以下是一个简单的 `AccessDecisionManager` 实现,用于判断用户是否具有特定角色: ```java import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; import java.util.Iterator; public class CustomAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (configAttributes == null) { return; } Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while (iterator.hasNext()) { ConfigAttribute attribute = iterator.next(); String role = attribute.getAttribute(); for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) { if (role.equals(grantedAuthority.getAuthority())) { return; // 用户具有所需的角色 } } } throw new AccessDeniedException("Access Denied"); // 用户没有所需的角色 } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } } ``` #### 3. 配置自定义 AccessDecisionManager 将自定义的 `AccessDecisionManager` 注册到 Spring Security 配置中: ```java import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { private final CustomAccessDecisionManager customAccessDecisionManager; public SecurityConfig(CustomAccessDecisionManager customAccessDecisionManager) { this.customAccessDecisionManager = customAccessDecisionManager; } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .accessDecisionManager(customAccessDecisionManager) .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasRole("USER") .and() .formLogin(); } } ``` #### 4. 角色投票器(RoleVoter) Spring Security 内置了 `RoleVoter`,它会根据用户的角色与请求所需的权限进行匹配。如果用户的角色满足请求所需的权限,则返回 `ACCESS_GRANTED`;否则返回 `ACCESS_DENIED`[^5]。 ##### 自定义 RoleVoter 示例 可以通过扩展 `RoleVoter` 来实现更复杂的逻辑: ```java import org.springframework.security.access.vote.RoleVoter; public class CustomRoleVoter extends RoleVoter { @Override public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { // 自定义逻辑 return super.vote(authentication, object, attributes); } } ``` #### 5. 数据持久化与 Session 管理 当用户登录成功后,Spring Security 会将用户信息存储在 `Session` 中,并通过 `ThreadLocal` 在当前线程中临时保存。在请求结束时,`SecurityContextHolder` 中的信息会被清除,下一次请求时重新从 `Session` 加载[^3]。 ##### Session 存储示例 Spring Security 默认使用 `SPRING_SECURITY_CONTEXT_KEY` 作为 `Session` 中的键值存储用户信息: ```java private String springSecurityContextKey = "SPRING_SECURITY_CONTEXT_KEY"; private SecurityContext readSecurityContextFromSession(HttpSession httpSession) { Object contextFromSession = httpSession.getAttribute(this.springSecurityContextKey); return (SecurityContext) contextFromSession; } ``` #### 总结 通过 `ThreadLocal` 和 `SecurityContextHolder`,Spring Security 能够方便地获取当前线程中的用户信息,并通过自定义 `AccessDecisionManager` 或 `RoleVoter` 实现灵活的鉴权逻辑[^4]。 --- ###
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值