[英]Spring Security @AuthenticationPrincipal
我一直在嘗試讓@AuthenticationPrincipal 與自定義用戶 class 一起正常工作。不幸的是,用戶始終是 null。這是代碼:
Controller
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index(@AuthenticationPrincipal User user) {
ModelAndView mav= new ModelAndView("/web/index");
mav.addObject("user", user);
return mav;
}
安全配置
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CustomUserDetailsService customUserDetailsService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
}
自定義用戶詳細信息服務
@Component
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Spring Data findByXY function
return userRepository.findByUsername(username);
}
用戶實體
public class User implements UserDetails{
private String username;
private String password;
private Collection<Authority> authorities;
// Getters and Setters
}
權威實體
public class Authority implements GrantedAuthority{
private User user;
private String role;
// Getters and Setters
@Override
public String getAuthority() {
return this.getRole();
}
}
我已經嘗試過在網上找到的各種解決方案,例如像這樣轉換我的自定義用戶 object:
return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), true, true, true, true, authorities);
獲得活躍用戶的其他方法沒有問題,但我發現 @AuthenticationProvider CustomUserObject 是最干凈的方法,這就是為什么我想讓它起作用的原因。 任何幫助是極大的贊賞。
您可以直接在方法參數中為經過身份驗證的用戶指定依賴項,而不是使用 @AuthenticationPrincipal。 下面給出的東西
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index(Principal user) {
ModelAndView mav= new ModelAndView("/web/index");
mav.addObject("user", user);
return mav;
}
此 Principal 對象將是通過 Spring Security 進行身份驗證的實際對象。 當方法被調用時,Spring 會為你注入它。
就我而言,我得到一個String
(用戶名)而不是UserDetails
對象,即您應該將方法簽名定義為
public ModelAndView index(@AuthenticationPrincipal String username)
這並不奇怪,因為@AuthenticationPrincipal
實際上返回Authentication.getPrincipal()
並且根據文檔:
在帶有用戶名和密碼的身份驗證請求的情況下,這將是用戶名。 調用者應為身份驗證請求填充主體。
AuthenticationManager 實現通常會返回一個包含更豐富信息的身份驗證作為應用程序使用的主體。 許多身份驗證提供程序將創建一個 UserDetails 對象作為主體。 請參閱: https : //docs.spring.io/spring-security/site/docs/5.0.0.RELEASE/api/
所以,我假設你的AuthenticationManager
實現只返回一個用戶名
從文檔:
AuthenticationPrincipalArgumentResolver 類將使用 SecurityContextHolder 中的 Authentication.getPrincipal() 解析 CustomUser 參數。 如果 Authentication 或 Authentication.getPrincipal() 為空,它將返回空。 如果類型不匹配,除非 AuthenticationPrincipal.errorOnInvalidType() 為 true,否則將返回 null,在這種情況下將拋出 ClassCastException。
這個簡單的代碼對我有用:
@RestController
public class ApiController {
@RequestMapping(method = RequestMethod.GET, path = "/api/public/{id}")
public ResponseEntity<ApiResponse> getPublicResource(
@PathVariable Integer id,
@AuthenticationPrincipal String principal
) {
if (id % 2 == 0) {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
return ResponseEntity.ok(new ApiResponse(
authentication.getPrincipal().toString(),
"this method scrapes principal directly from SpringSecurityContext"));
}
return ResponseEntity.ok(new ApiResponse(
principal.toString(),
"this method retrieves principal from method argument"));
}
}
只需檢查身份驗證對象中主體的類型。
您需要檢查是否一起使用了正確版本的@AuthenticationPrincipal
注釋和正確版本的AuthenticationPrincipalArgumentResolver
類。
在 Spring Security 4.0 版本之前,您必須使用類:
org.springframework.security.web.bind.annotation.AuthenticationPrincipal
org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver
從 4.0 版開始,您必須使用:
org.springframework.security.core.annotation.AuthenticationPrincipal
org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver
查看@AuthenticationPrincipal官方文檔以獲取配置示例。
我找到了另一種解決方案,即使這不是規范的解決方案。
在您的控制器中
@RequestMapping(value = "/login", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<UserDTO> login(@RequestBody UserDTO user){
try {
usersService.authenticate(user.getUsername(), user.getPassword());
return new ResponseEntity<UserDTO>(user, HttpStatus.OK);
}
catch (BadCredentialsException e){
return new ResponseEntity<UserDTO>(HttpStatus.UNAUTHORIZED);
}
}
UserDTO os 只是一個包含用戶名和密碼的表單
在您的 CustomUserDetailsService 中
public void authenticate(String username, String password){
try{
User user = new User(username, password);
Authentication request = new UsernamePasswordAuthenticationToken(user, password, Arrays.asList(WebSecurityConfiguration.USER));
Authentication result = authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
} catch (InternalAuthenticationServiceException e){
// treat as a bad credential
}
}
實現你自己的 AuthenticationManager
@Component
class DefaultAuthenticationManager implements AuthenticationManager {
@Autowired
private CustomUserDetailsService usersService;
public Authentication authenticate(Authentication auth) throws AuthenticationException {
UserDetails user = usersService.loadUserByUsername(((User)auth.getPrincipal()).getUsername());
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
}
// Handle bad credentials here
}
}
根本的是CustomUserDetailsService#authenticate
中的Principal
是一個對象,而不是您的已驗證用戶的名稱,以便框架可以處理它,然后通過@AuthenticationPrincipal
機制注入。 這對我有用。
@RequestMapping(method= RequestMethod.GET,value="/authenticate", produces = MediaType.APPLICATION_JSON_VALUE)
public Object authenticate(@AuthenticationPrincipal Object obj) {
return obj;
}
我有同樣的問題,我能夠完成這項工作。 您可以將它與映射器一起使用
@RequestMapping(method = RequestMethod.GET, value = "/authenticate2", produces = MediaType.APPLICATION_JSON_VALUE)
public User authenticate2(@AuthenticationPrincipal Object obj) throws IOException {
return mapper.readValue(mapper.writeValueAsString(obj), User.class);
}
這些對我有用,我希望它將來對任何人有用
為 @AuthenticationPrincipal 定義 (expression = "userDetails") 對我有用
public ResponseEntity<UserDetails> index(@AuthenticationPrincipal(expression = "userDetails") UserDetails userDetails) {
return ResponseEntity.ok(userDetails);
}
我和你面臨着完全相同的問題。 出於某種原因,spring security 的@EnableWebSecurity 不會自動添加argumentResolver,您需要手動添加:
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
我正在使用擴展 AbstractUserDetailsAuthenticationProvider 的身份驗證提供程序來實現這一點。
在我的提供者中,我覆蓋了以下方法:
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) 拋出 AuthenticationException {}
在這個方法中,我正在創建我的 UserDetails 實現並返回它。 然后我可以使用 AuthenticationPrincipal 注釋在控制器中獲取它。
盡管文檔提到了 customUser,但我認為這是不可能的。 使用@AuthenticationPrincipal
和java.lang.Object
或org.springframework.security.core.userdetails.User
作為 spring 控制器方法參數類型。
如果類型不匹配,除非 AuthenticationPrincipal.errorOnInvalidType() 為 true,否則將返回 null,在這種情況下將拋出 ClassCastException。 Spring API 文檔
您必須確保您想要獲得的 class 是實現“UserDetails”,因為注釋會返回此內容。
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index(@AuthenticationPrincipal(expression = "user") User user) {
ModelAndView mav= new ModelAndView("/web/index");
mav.addObject("user", user);
return mav;
}
只需輸入expression = "user"
即可使用 go。(如果您的用戶實體 class 名稱不同,請相應更新。)
對於不同的身份驗證方法(例如,用戶名/密碼與 Oauth2), @AuthenticationPrincipal
的返回類型可能不同。
找出@AuthenticationPrincipal
的返回類型。 請參閱https://stackoverflow.com/a/76435763/5303092
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.