如何在Spring Boot的每个请求中获取当前用户?

时间:2018-10-20 16:50:41

标签: java spring spring-mvc spring-boot

我想在每次将用户添加到日志文件的请求中获取用户名。

这是我的解决方案:

首先,我创建了一个具有LoggedUser属性的static

public class LoggedUser {

    private static final ThreadLocal<String> userHolder = 
        new ThreadLocal<>();

    public static void logIn(String user) {
        userHolder.set(user);
    }

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

    public static String get() {
        return userHolder.get();
    }
}

然后我创建了一个支持类来获取用户名:

public interface AuthenticationFacade {
    Authentication getAuthentication();
}
@Component
public class AuthenticationFacadeImpl implements AuthenticationFacade {
    @Override
    public Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

最后,我在控制器中使用了它们:

    @RestController
    public class ResourceController {

        Logger logger = LoggerFactory.getLogger(ResourceController.class);

        @Autowired
        private GenericService userService;
        @Autowired
        private AuthenticationFacade authenticationFacade;

        @RequestMapping(value ="/cities")
        public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
        loggedUser.logIn(authenticationFacade.getAuthentication().getName());
        logger.info(LoggedUser.get()); //Log username
        return userService.findAllRandomCities();
        }
    }

问题是我不想在每个AuthenticationFacade中都使用@Controller,例如,如果我有10000个控制器,那将是很多工作。

您有更好的解决方案吗?

5 个答案:

答案 0 :(得分:1)

Spring Security中有various ways,可以从安全上下文中获取用户详细信息。但是根据您的要求,您只对用户名感兴趣,因此您可以尝试以下操作:

@RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Authentication authentication){
    logger.info(authentication.getName()); //Log username
    return userService.findAllRandomCities();
}

希望这会有所帮助!

答案 1 :(得分:1)

好吧,您已经直接从SecurityContextHolder访问身份验证对象,您可以在控制器中完成它。

@RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
  Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  if(authentication != null){
    //log user name
    logger.info(authentication.get());
  }
  return userService.findAllRandomCities();
}

如果您不想将所有这些信息都放入每个端点,则可以创建一个实用程序方法来提取身份验证并返回其名称(如果找到)。

public class UserUtil {
  public static String userName(){
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    return authentication == null ? null : authentication.getName();
  }
}

并在端点中调用它,如

@RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
  //log user name
  logger.info(UserUtil.username());
  return userService.findAllRandomCities();
}

但是,您仍在每个端点中添加代码行,经过几行之后,被迫这样做会感到不对劲。我建议您要做的是尝试这种面向方面的编程。它将需要您花一些时间来学习它的工作原理,创建所需的注释或执行。但是您应该在一两天内吃完它。 面向方面,您的端点可能会这样结束

@RequestMapping(value ="/cities")
@LogUserName
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
  //LogUserName annotation will inform this request should log user name if found
  return userService.findAllRandomCities();
}

当然,您可以删除@LogUserName自定义批注并通过包内的方法或扩展@Controller的类等触发配置新方面。 绝对值得的,因为您不仅可以记录用户名,还可以使用aspect。

答案 2 :(得分:0)

您可以通过控制器方法中的请求或参数获取用户名。如果您添加Principal principal作为参数,Spring Ioc Container将注入有关该用户的信息,或者对于匿名用户为null。

@RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Principal principal){
    if(principal == null){
         // anonymous user
    }
}

答案 3 :(得分:0)

该解决方案称为鱼类标记。每个体面的日志记录框架都具有此功能。一些框架将其称为MDC(映射的诊断上下文)。您可以阅读herehere

基本思想是使用ThreadLocalInheritableThreadLocal在线程中保留一些键值对以跟踪请求。使用日志记录配置,您可以配置如何在日志条目中打印它。

基本上,您可以编写一个过滤器,从安全上下文中检索用户名并将其放入MDC中,而不必理会。在您的控制器中,您仅记录与业务逻辑相关的内容。用户名将与时间戳,日志级别等(根据您的日志配置)一起打印在日志条目中。

答案 4 :(得分:0)

在Jhovanni的建议下,我创建了一个像这样的AOP注释:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogUsername {
}

在同一包中,我添加了@Aop注入的新@Component AuthenticationFacade类:

@Aspect
@Component
public class LogUsernameAop {
    Logger logger = LoggerFactory.getLogger(LogUsernameAop.class);
    @Autowired
    private AuthenticationFacade authenticationFacade;

    @Before("@annotation(LogUsername)")
    public void logUsername() throws Throwable {
        logger.info(authenticationFacade.getAuthentication().getName());
        LoggedUser.logIn(authenticationFacade.getAuthentication().getName());
    }
}

然后,在每个@GetMapping方法中,如果需要登录用户名,则可以在该方法之前添加注释:

@PostMapping
@LogUsername
public Course createCourse(@RequestBody Course course){
    return courseService.saveCourse(course);
}

最后,这是结果:

2018-10-21 08:29:07.206  INFO 8708 --- [nio-8080-exec-2] com.khoa.aop.LogUsername                 : john.doe