有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

我想在每个请求中获得用户的用户名,以便将其添加到日志文件中

这是我的解决方案:

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

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

问题是我不希望在每个@Controller中都有AuthenticationFacade,例如,如果我有10000个控制器,那将需要大量的工作

你有更好的解决办法吗


共 (5) 个答案

  1. # 1 楼答案

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

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

    希望这有帮助

  2. # 2 楼答案

    好的,您已经直接从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来记录用户名

  3. # 3 楼答案

    该解决方案称为鱼标签。每个好的日志框架都有这个功能。一些框架称之为MDC(映射的诊断上下文)。你可以在herehere读到它

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

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

  4. # 4 楼答案

    根据Jhovanni的建议,我创建了一个AOP注释,如下所示:

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

    在同一个包中,我使用AuthenticationFacade注入添加了新的@Aop{}类:

    @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
    
  5. # 5 楼答案

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

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