Cách nhận UserDetails của người dùng đang hoạt động


170

Trong bộ điều khiển của tôi, khi tôi cần người dùng đang hoạt động (đã đăng nhập), tôi đang làm như sau để UserDetailsthực hiện:

User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.debug(activeUser.getSomeCustomField());

Nó hoạt động tốt, nhưng tôi nghĩ rằng Spring có thể làm cho cuộc sống dễ dàng hơn trong trường hợp như thế này. Có cách nào để tự động UserDetailsvào bộ điều khiển hoặc phương thức không?

Ví dụ: một cái gì đó như:

public ModelAndView someRequestHandler(Principal principal) { ... }

Nhưng thay vì nhận được UsernamePasswordAuthenticationToken, tôi nhận được một UserDetailsthay thế?

Tôi đang tìm kiếm một giải pháp thanh lịch. Có ý kiến ​​gì không?

Câu trả lời:


226

Lời mở đầu: Kể từ Spring-Security 3.2, có một chú thích hay @AuthenticationPrincipalđược mô tả ở cuối câu trả lời này. Đây là cách tốt nhất để sử dụng khi bạn sử dụng Spring-Security> = 3.2.

Khi bạn:

  • sử dụng phiên bản cũ hơn của Spring-Security,
  • cần tải Đối tượng người dùng tùy chỉnh của bạn từ Cơ sở dữ liệu bằng một số thông tin (như thông tin đăng nhập hoặc id) được lưu trữ trong hiệu trưởng hoặc
  • muốn tìm hiểu làm thế nào một HandlerMethodArgumentResolverhoặc WebArgumentResolvercó thể giải quyết điều này một cách tao nhã, hoặc chỉ muốn tìm hiểu nền tảng phía sau @AuthenticationPrincipalAuthenticationPrincipalArgumentResolver(vì nó dựa trên một HandlerMethodArgumentResolver)

sau đó tiếp tục đọc - khác chỉ cần sử dụng @AuthenticationPrincipalvà cảm ơn Rob Winch (Tác giả @AuthenticationPrincipal) và Lukas Schmelzeisen (cho câu trả lời của mình).

(BTW: Câu trả lời của tôi đã cũ hơn một chút (tháng 1 năm 2012), do đó, Lukas Schmelzeisen đã trở thành người đầu tiên có @AuthenticationPrincipalcơ sở giải pháp chú thích trên Spring Security 3.2.)


Sau đó, bạn có thể sử dụng trong bộ điều khiển của bạn

public ModelAndView someRequestHandler(Principal principal) {
   User activeUser = (User) ((Authentication) principal).getPrincipal();
   ...
}

Đó là ok nếu bạn cần nó một lần. Nhưng nếu bạn cần nó nhiều lần vì nó xấu vì nó làm ô nhiễm bộ điều khiển của bạn với các chi tiết cơ sở hạ tầng, điều đó thường sẽ bị ẩn bởi khung.

Vì vậy, những gì bạn có thể thực sự muốn là có một bộ điều khiển như thế này:

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
   ...
}

Do đó bạn chỉ cần thực hiện a WebArgumentResolver. Nó có một phương pháp

Object resolveArgument(MethodParameter methodParameter,
                   NativeWebRequest webRequest)
                   throws Exception

Điều đó nhận được yêu cầu web (tham số thứ hai) và phải trả về Usernếu nó cảm thấy có trách nhiệm với đối số phương thức (tham số đầu tiên).

Kể từ mùa xuân 3.1, có một khái niệm mới gọi là HandlerMethodArgumentResolver. Nếu bạn sử dụng Spring 3.1+ thì bạn nên sử dụng nó. (Nó được mô tả trong phần tiếp theo của câu trả lời này))

public class CurrentUserWebArgumentResolver implements WebArgumentResolver{

   Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
        if(methodParameter is for type User && methodParameter is annotated with @ActiveUser) {
           Principal principal = webRequest.getUserPrincipal();
           return (User) ((Authentication) principal).getPrincipal();
        } else {
           return WebArgumentResolver.UNRESOLVED;
        }
   }
}

Bạn cần xác định Chú thích tùy chỉnh - Bạn có thể bỏ qua nếu mọi trường hợp của Người dùng phải luôn được lấy từ ngữ cảnh bảo mật, nhưng không bao giờ là đối tượng lệnh.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActiveUser {}

Trong cấu hình, bạn chỉ cần thêm phần này:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
    id="applicationConversionService">
    <property name="customArgumentResolver">
        <bean class="CurrentUserWebArgumentResolver"/>
    </property>
</bean>

@See: Tìm hiểu để tùy chỉnh các đối số phương thức Spring MVC @Controll

Cần lưu ý rằng nếu bạn đang sử dụng Spring 3.1, họ khuyên dùng HandlerMethodArgumentResolver trên WebArgumentResolver. - xem bình luận của Jay


Tương tự với HandlerMethodArgumentResolverSpring 3.1+

public class CurrentUserHandlerMethodArgumentResolver
                               implements HandlerMethodArgumentResolver {

     @Override
     public boolean supportsParameter(MethodParameter methodParameter) {
          return
              methodParameter.getParameterAnnotation(ActiveUser.class) != null
              && methodParameter.getParameterType().equals(User.class);
     }

     @Override
     public Object resolveArgument(MethodParameter methodParameter,
                         ModelAndViewContainer mavContainer,
                         NativeWebRequest webRequest,
                         WebDataBinderFactory binderFactory) throws Exception {

          if (this.supportsParameter(methodParameter)) {
              Principal principal = webRequest.getUserPrincipal();
              return (User) ((Authentication) principal).getPrincipal();
          } else {
              return WebArgumentResolver.UNRESOLVED;
          }
     }
}

Trong cấu hình, bạn cần thêm cái này

<mvc:annotation-driven>
      <mvc:argument-resolvers>
           <bean class="CurrentUserHandlerMethodArgumentResolver"/>         
      </mvc:argument-resolvers>
 </mvc:annotation-driven>

@See Tận dụng giao diện Spring MVC 3.1 HandlerMethodArgumentResolver


Giải pháp bảo mật mùa xuân 3.2

Spring Security 3.2 (không nhầm lẫn với Spring 3.2) có giải pháp xây dựng riêng: @AuthenticationPrincipal( org.springframework.security.web.bind.annotation.AuthenticationPrincipal). Điều này được mô tả độc đáo trong câu trả lời của Lukas Schmelzeisen

Nó chỉ là viết

ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
 }

Để làm việc này, bạn cần đăng ký AuthenticationPrincipalArgumentResolver( org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver): bằng cách "kích hoạt" @EnableWebMvcSecurityhoặc bằng cách đăng ký hạt này trong mvc:argument-resolvers- giống như cách tôi đã mô tả với giải pháp Spring 3.1 ở trên.

@See Bảo mật mùa xuân 3.2 Tham khảo, Chương 11.2. @AuthenticationPrincipal


Giải pháp bảo mật mùa xuân 4.0

Nó hoạt động giống như giải pháp Spring 3.2, nhưng trong Spring 4.0 @AuthenticationPrincipal, nó AuthenticationPrincipalArgumentResolverđã được "chuyển" sang một gói khác:

(Nhưng các lớp cũ trong các gói cũ vẫn tồn tại, vì vậy đừng trộn chúng!)

Nó chỉ là viết

import org.springframework.security.core.annotation.AuthenticationPrincipal;
ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
}

Để làm việc này, bạn cần đăng ký ( org.springframework.security.web.method.annotation.) AuthenticationPrincipalArgumentResolver: bằng cách "kích hoạt" @EnableWebMvcSecurityhoặc bằng cách đăng ký hạt này trong mvc:argument-resolvers- giống như cách tôi đã mô tả với giải pháp Spring 3.1 ở trên.

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

Tài liệu tham khảo @See Spring Security 5.0, Chương 39.3 @AuthenticationPrincipal


Hoặc chỉ cần tạo một bean có phương thức getUserDetails () và @Autowire vào bộ điều khiển của bạn.
sourcedelica

Thực hiện giải pháp này, tôi đặt một điểm dừng ở đầu giải quyếtArArument () nhưng ứng dụng của tôi không bao giờ bước vào trình giải quyết đối số web. Cấu hình mùa xuân của bạn trong bối cảnh servlet không phải là bối cảnh gốc, phải không?
Jay

@Jay: Cấu hình này là một phần của bối cảnh gốc, không phải bối cảnh servlet. - đó là đường nối mà tôi đã quên chỉ định id (id="applicationConversionService")trong ví dụ
Ralph

11
Cần lưu ý rằng nếu bạn đang sử dụng Spring 3.1, họ khuyên dùng HandlerMethodArgumentResolver trên WebArgumentResolver. Tôi đã làm cho HandlerMethodArgumentResolver hoạt động bằng cách cấu hình với <annotation-direction> trong ngữ cảnh servlet. Ngoài ra, tôi đã thực hiện câu trả lời như được đăng ở đây và mọi thứ hoạt động rất tốt
Jay

@sourcedelica Tại sao không chỉ tạo phương thức tĩnh?
Alex78191

66

Trong khi Ralphs Trả lời cung cấp một giải pháp tao nhã, với Spring Security 3.2, bạn không còn cần phải thực hiện theo cách riêng của mình ArgumentResolver.

Nếu bạn có một UserDetailstriển khai CustomUser, bạn có thể làm điều này:

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {

    // .. find messages for this User and return them...
}

Xem Tài liệu bảo mật mùa xuân: @AuthenticationPrincipal


2
đối với những người không thích đọc các liên kết được cung cấp, điều này phải được kích hoạt bằng @EnableWebMvcSecurityhoặc bằng XML:<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
sodik

Làm thế nào để kiểm tra nếu customUserlà null hay không?
Sajad

if (customUser != null) { ... }
T3rm1

27

Spring Security dự định hoạt động với các khung công tác không phải Spring khác, do đó nó không được tích hợp chặt chẽ với Spring MVC. Spring Security trả về Authenticationđối tượng từ HttpServletRequest.getUserPrincipal()phương thức theo mặc định để đó là những gì bạn nhận được làm hiệu trưởng. Bạn có thể lấy UserDetailsđối tượng của mình trực tiếp từ điều này bằng cách sử dụng

UserDetails ud = ((Authentication)principal).getPrincipal()

Cũng lưu ý rằng các loại đối tượng có thể thay đổi tùy thuộc vào cơ chế xác thực được sử dụng ( UsernamePasswordAuthenticationTokenví dụ: bạn có thể không nhận được a ) và Authenticationkhông nhất thiết phải chứa a UserDetails. Nó có thể là một chuỗi hoặc bất kỳ loại nào khác.

Nếu bạn không muốn gọi SecurityContextHoldertrực tiếp, cách tiếp cận thanh lịch nhất (mà tôi sẽ làm theo) là tiêm giao diện truy cập bối cảnh bảo mật tùy chỉnh của riêng bạn, được tùy chỉnh để phù hợp với nhu cầu của bạn và các loại đối tượng người dùng. Tạo một giao diện, với các phương thức liên quan, ví dụ:

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

Sau đó, bạn có thể thực hiện điều này bằng cách truy cập vào phần SecurityContextHoldertriển khai tiêu chuẩn của mình, do đó tách hoàn toàn mã của bạn khỏi Spring Security. Sau đó đưa phần này vào bộ điều khiển cần truy cập thông tin bảo mật hoặc thông tin về người dùng hiện tại.

Lợi ích chính khác là dễ dàng thực hiện các triển khai đơn giản với dữ liệu cố định để thử nghiệm, mà không phải lo lắng về việc điền địa phương luồng, v.v.


Tôi đã xem xét phương pháp này, nhưng tôi không chắc chắn a) chính xác làm thế nào để thực hiện đúng cách (tm) và b) nếu có bất kỳ vấn đề phân luồng nào. Bạn có chắc chắn sẽ không có vấn đề ở đó? Tôi đang sử dụng phương pháp chú thích được đăng ở trên, nhưng tôi nghĩ rằng đây vẫn là một cách hay để thực hiện. Cảm ơn vì đăng. :)
Gấu Awnry

4
Về mặt kỹ thuật cũng giống như truy cập SecurityContextHolder trực tiếp từ bộ điều khiển của bạn, do đó không nên có bất kỳ vấn đề luồng nào. Nó chỉ giữ cuộc gọi đến một nơi duy nhất và cho phép bạn dễ dàng tiêm các lựa chọn thay thế để thử nghiệm. Bạn cũng có thể sử dụng lại cách tiếp cận tương tự trong các lớp không phải web khác cần truy cập vào thông tin bảo mật.
Shaun the Sheep

Gotcha ... đó là những gì tôi đã nghĩ. Đây sẽ là một điều tốt để quay trở lại nếu tôi gặp vấn đề kiểm tra đơn vị với phương pháp chú thích hoặc nếu tôi muốn giảm khớp nối với Spring.
Gấu Awnry

@LukeTaylor bạn có thể vui lòng giải thích thêm về phương pháp này không, tôi hơi mới đối với bảo mật mùa xuân và mùa xuân, vì vậy tôi không hiểu lắm về cách thực hiện điều này. Tôi phải thực hiện điều này ở đâu để nó được truy cập? đến UserServiceImpl của tôi?
Cu7l4ss

9

Triển khai HandlerInterceptorgiao diện và sau đó đưa UserDetailsvào từng yêu cầu có Mô hình, như sau:

@Component 
public class UserInterceptor implements HandlerInterceptor {
    ....other methods not shown....
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if(modelAndView != null){
            modelAndView.addObject("user", (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        }
}

1
Cảm ơn @atrain, điều đó hữu ích và thanh lịch. Ngoài ra, tôi đã phải thêm <mvc:interceptors>tập tin cấu hình ứng dụng của mình.
Eric

Tốt hơn là lấy người dùng được ủy quyền trong mẫu qua spring-security-taglibs: stackoverflow.com/a/44373331/548473
Grigory Kislin

9

Bắt đầu với Spring Security phiên bản 3.2, chức năng tùy chỉnh đã được triển khai bởi một số câu trả lời cũ hơn, tồn tại ngoài hộp dưới dạng @AuthenticationPrincipalchú thích được hỗ trợ AuthenticationPrincipalArgumentResolver.

Một ví dụ đơn giản về việc sử dụng nó là:

@Controller
public class MyController {
   @RequestMapping("/user/current/show")
   public String show(@AuthenticationPrincipal CustomUser customUser) {
        // do something with CustomUser
       return "view";
   }
}

CustomUser cần được gán từ authentication.getPrincipal()

Dưới đây là các Javadocs tương ứng của xác thựcPrincipalxác thựcPrincipalArgumentResolver


1
@nbro Khi tôi thêm giải pháp cụ thể cho phiên bản, không có giải pháp nào khác được cập nhật để tính đến giải pháp này
Geoand


5
@Controller
public abstract class AbstractController {
    @ModelAttribute("loggedUser")
    public User getLoggedUser() {
        return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

0

Và nếu bạn cần người dùng được ủy quyền trong các mẫu (ví dụ: JSP), hãy sử dụng

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authentication property="principal.yourCustomField"/>

cùng với

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>${spring-security.version}</version>
    </dependency>

0

Bạn có thể thử điều này: Bằng cách sử dụng Đối tượng xác thực từ Spring, chúng ta có thể lấy chi tiết người dùng từ nó trong phương thức điều khiển. Dưới đây là ví dụ, bằng cách chuyển đối tượng Xác thực trong phương thức điều khiển cùng với đối số. Một khi người dùng được xác thực, các chi tiết được điền trong Đối tượng xác thực.

@GetMapping(value = "/mappingEndPoint") <ReturnType> methodName(Authentication auth) {
   String userName = auth.getName(); 
   return <ReturnType>;
}

Hãy giải thích câu trả lời của bạn.
Nikolai Shevchenko

Tôi đã chỉnh sửa câu trả lời của mình, chúng tôi có thể sử dụng Đối tượng xác thực có chi tiết người dùng của người dùng đã xác thực.
Mirza Shujathullah
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.