Spring Security AuthenticationManager authenticate() 方法如何检查发送的用户名和密码是否正确?
How the Spring Security AuthenticationManager authenticate() method is able to check if the username and password sent are correct?
我正在开发一个 Spring 引导应用程序,该应用程序采用系统上现有用户的用户名和密码,然后生成 JWT 令牌。我从教程中复制了它并对其进行了更改以处理我的特定用例。逻辑对我来说很清楚,但我对用户如何在系统上进行身份验证存有很大疑问。下面我将尝试向您解释,因为这是结构化的,我的疑问是什么。
JWT 生成令牌系统由两个不同的微服务组成,它们是:
GET-USER-WS:这个微服务简单地使用Hibernate\JPA来检索系统中特定用户的信息。基本上它包含一个控制器 class 调用服务 class 本身调用 JPA 存储库以检索特定用户信息:
@RestController
@RequestMapping("api/users")
@Log
public class UserController {
@Autowired
UserService userService;
@GetMapping(value = "/{email}", produces = "application/json")
public ResponseEntity<User> getUserByEmail(@PathVariable("email") String eMail) throws NotFoundException {
log.info(String.format("****** Get the user with eMail %s *******", eMail) );
User user = userService.getUserByEmail(eMail);
if (user == null)
{
String ErrMsg = String.format("The user with eMail %s was not found", eMail);
log.warning(ErrMsg);
throw new NotFoundException(ErrMsg);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
}
}
如您所见,此控制器包含一个使用电子邮件参数(即系统上的用户名)的 API 和包含以下信息的 return 一个 JSON这个用户。
然后我有第二个微服务(名为 AUTH-SERVER-JWT),它调用前一个 API 以获取用户信息将用于生成 JWT 令牌。为了使描述尽可能简单,它包含此控制器 class:
@RestController
//@CrossOrigin(origins = "http://localhost:4200")
public class JwtAuthenticationRestController {
@Value("${sicurezza.header}")
private String tokenHeader;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
@Qualifier("customUserDetailsService")
//private UserDetailsService userDetailsService;
private CustomUserDetailsService userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationRestController.class);
@PostMapping(value = "${sicurezza.uri}")
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtTokenRequest authenticationRequest)
throws AuthenticationException {
logger.info("Autenticazione e Generazione Token");
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
//final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final UserDetailsWrapper userDetailsWrapper = userDetailsService.loadCompleteUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetailsWrapper);
logger.warn(String.format("Token %s", token));
return ResponseEntity.ok(new JwtTokenResponse(token));
}
@RequestMapping(value = "${sicurezza.uri}", method = RequestMethod.GET)
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request)
throws Exception
{
String authToken = request.getHeader(tokenHeader);
if (authToken == null || authToken.length() < 7)
{
throw new Exception("Token assente o non valido!");
}
final String token = authToken.substring(7);
if (jwtTokenUtil.canTokenBeRefreshed(token))
{
String refreshedToken = jwtTokenUtil.refreshToken(token);
return ResponseEntity.ok(new JwtTokenResponse(refreshedToken));
}
else
{
return ResponseEntity.badRequest().body(null);
}
}
@ExceptionHandler({ AuthenticationException.class })
public ResponseEntity<String> handleAuthenticationException(AuthenticationException e)
{
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
private void authenticate(String username, String password)
{
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
/// ???
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (DisabledException e)
{
logger.warn("UTENTE DISABILITATO");
throw new AuthenticationException("UTENTE DISABILITATO", e);
}
catch (BadCredentialsException e)
{
logger.warn("CREDENZIALI NON VALIDE");
throw new AuthenticationException("CREDENZIALI NON VALIDE", e);
}
}
}
此class包含两个方法,第一个用于生成全新的 JWT 令牌,第二个用于刷新现有的 JWT 令牌。现在考虑与 createAuthenticationToken() 方法相关的第一个用例(生成全新的 JWT 令牌)。此方法将与 JWT 令牌请求相关的信息作为输入参数:@RequestBody JwtTokenRequest authenticationRequest。 Bascailly JwtTokenRequest 是一个简单的 DTO 对象,如下所示:
@Data
public class JwtTokenRequest implements Serializable
{
private static final long serialVersionUID = -3558537416135446309L;
private String username;
private String password;
}
所以正文请求中的负载将是这样的:
{
"username": "xxx@gmail.com",
"password": "password"
}
注意: 在我的数据库中,我有一个拥有此用户名和密码的用户,因此将在系统上检索并验证该用户。
如您所见,createAuthenticationToken() 方法执行的第一个有效操作是:
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
基本上它是在调用相同 class 中定义的 authenticate() 方法,向其传递先前的凭据("username": " xxx@gmail.com" and "password": "password").
如您所见,这是我的 authenticate() 方法
private void authenticate(String username, String password)
{
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
/// ???
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (DisabledException e)
{
logger.warn("UTENTE DISABILITATO");
throw new AuthenticationException("UTENTE DISABILITATO", e);
}
catch (BadCredentialsException e)
{
logger.warn("CREDENTIAL ERROR");
throw new AuthenticationException(""CREDENTIAL ERROR", e);
}
}
基本上它将这些凭据传递给定义到注入的 Spring Security AuthenticationManager 实例中的 authenticate() 方法,通过这条线:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
此方法似乎能够验证或不验证这些凭据。它似乎工作正常,因为如果我输入错误的用户名或密码,它会进入 CREDENTIAL ERROR 案例,并抛出 AuthenticationException 异常。
这里我有一个巨大的疑问:为什么它有效?!?!这怎么可能?如果您返回到 createAuthenticationToken() 控制器方法,您可以看到它按以下顺序执行这两个操作:
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetailsWrapper userDetailsWrapper = userDetailsService.loadCompleteUserByUsername(authenticationRequest.getUsername());
它首先执行authenticate()方法(应该检查发送的用户名和密码是否正确),然后调用检索用户信息的服务方法。
请问 authenticate() 方法如何检查原始负载中发送的凭据是否正确?
通常,AuthenticationManager
的实现是 ProviderManager
,它将遍历所有已配置的 AuthenticationProvider
,并尝试使用提供的凭据进行身份验证。
其中一个 AuthenticationProvider
是 DaoAuthenticationProvider
,它支持 UsernamePasswordAuthenticationToken
并使用 UserDetailsService
(您有一个 customUserDetailsService
)来检索用户并使用配置的 PasswordEncoder
.
比较 password
关于Authentication Architecture的参考文档中有更详细的解释。
我正在开发一个 Spring 引导应用程序,该应用程序采用系统上现有用户的用户名和密码,然后生成 JWT 令牌。我从教程中复制了它并对其进行了更改以处理我的特定用例。逻辑对我来说很清楚,但我对用户如何在系统上进行身份验证存有很大疑问。下面我将尝试向您解释,因为这是结构化的,我的疑问是什么。
JWT 生成令牌系统由两个不同的微服务组成,它们是:
GET-USER-WS:这个微服务简单地使用Hibernate\JPA来检索系统中特定用户的信息。基本上它包含一个控制器 class 调用服务 class 本身调用 JPA 存储库以检索特定用户信息:
@RestController
@RequestMapping("api/users")
@Log
public class UserController {
@Autowired
UserService userService;
@GetMapping(value = "/{email}", produces = "application/json")
public ResponseEntity<User> getUserByEmail(@PathVariable("email") String eMail) throws NotFoundException {
log.info(String.format("****** Get the user with eMail %s *******", eMail) );
User user = userService.getUserByEmail(eMail);
if (user == null)
{
String ErrMsg = String.format("The user with eMail %s was not found", eMail);
log.warning(ErrMsg);
throw new NotFoundException(ErrMsg);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
}
}
如您所见,此控制器包含一个使用电子邮件参数(即系统上的用户名)的 API 和包含以下信息的 return 一个 JSON这个用户。
然后我有第二个微服务(名为 AUTH-SERVER-JWT),它调用前一个 API 以获取用户信息将用于生成 JWT 令牌。为了使描述尽可能简单,它包含此控制器 class:
@RestController
//@CrossOrigin(origins = "http://localhost:4200")
public class JwtAuthenticationRestController {
@Value("${sicurezza.header}")
private String tokenHeader;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
@Qualifier("customUserDetailsService")
//private UserDetailsService userDetailsService;
private CustomUserDetailsService userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationRestController.class);
@PostMapping(value = "${sicurezza.uri}")
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtTokenRequest authenticationRequest)
throws AuthenticationException {
logger.info("Autenticazione e Generazione Token");
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
//final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final UserDetailsWrapper userDetailsWrapper = userDetailsService.loadCompleteUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetailsWrapper);
logger.warn(String.format("Token %s", token));
return ResponseEntity.ok(new JwtTokenResponse(token));
}
@RequestMapping(value = "${sicurezza.uri}", method = RequestMethod.GET)
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request)
throws Exception
{
String authToken = request.getHeader(tokenHeader);
if (authToken == null || authToken.length() < 7)
{
throw new Exception("Token assente o non valido!");
}
final String token = authToken.substring(7);
if (jwtTokenUtil.canTokenBeRefreshed(token))
{
String refreshedToken = jwtTokenUtil.refreshToken(token);
return ResponseEntity.ok(new JwtTokenResponse(refreshedToken));
}
else
{
return ResponseEntity.badRequest().body(null);
}
}
@ExceptionHandler({ AuthenticationException.class })
public ResponseEntity<String> handleAuthenticationException(AuthenticationException e)
{
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
private void authenticate(String username, String password)
{
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
/// ???
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (DisabledException e)
{
logger.warn("UTENTE DISABILITATO");
throw new AuthenticationException("UTENTE DISABILITATO", e);
}
catch (BadCredentialsException e)
{
logger.warn("CREDENZIALI NON VALIDE");
throw new AuthenticationException("CREDENZIALI NON VALIDE", e);
}
}
}
此class包含两个方法,第一个用于生成全新的 JWT 令牌,第二个用于刷新现有的 JWT 令牌。现在考虑与 createAuthenticationToken() 方法相关的第一个用例(生成全新的 JWT 令牌)。此方法将与 JWT 令牌请求相关的信息作为输入参数:@RequestBody JwtTokenRequest authenticationRequest。 Bascailly JwtTokenRequest 是一个简单的 DTO 对象,如下所示:
@Data
public class JwtTokenRequest implements Serializable
{
private static final long serialVersionUID = -3558537416135446309L;
private String username;
private String password;
}
所以正文请求中的负载将是这样的:
{
"username": "xxx@gmail.com",
"password": "password"
}
注意: 在我的数据库中,我有一个拥有此用户名和密码的用户,因此将在系统上检索并验证该用户。
如您所见,createAuthenticationToken() 方法执行的第一个有效操作是:
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
基本上它是在调用相同 class 中定义的 authenticate() 方法,向其传递先前的凭据("username": " xxx@gmail.com" and "password": "password").
如您所见,这是我的 authenticate() 方法
private void authenticate(String username, String password)
{
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
/// ???
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (DisabledException e)
{
logger.warn("UTENTE DISABILITATO");
throw new AuthenticationException("UTENTE DISABILITATO", e);
}
catch (BadCredentialsException e)
{
logger.warn("CREDENTIAL ERROR");
throw new AuthenticationException(""CREDENTIAL ERROR", e);
}
}
基本上它将这些凭据传递给定义到注入的 Spring Security AuthenticationManager 实例中的 authenticate() 方法,通过这条线:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
此方法似乎能够验证或不验证这些凭据。它似乎工作正常,因为如果我输入错误的用户名或密码,它会进入 CREDENTIAL ERROR 案例,并抛出 AuthenticationException 异常。
这里我有一个巨大的疑问:为什么它有效?!?!这怎么可能?如果您返回到 createAuthenticationToken() 控制器方法,您可以看到它按以下顺序执行这两个操作:
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetailsWrapper userDetailsWrapper = userDetailsService.loadCompleteUserByUsername(authenticationRequest.getUsername());
它首先执行authenticate()方法(应该检查发送的用户名和密码是否正确),然后调用检索用户信息的服务方法。
请问 authenticate() 方法如何检查原始负载中发送的凭据是否正确?
通常,AuthenticationManager
的实现是 ProviderManager
,它将遍历所有已配置的 AuthenticationProvider
,并尝试使用提供的凭据进行身份验证。
其中一个 AuthenticationProvider
是 DaoAuthenticationProvider
,它支持 UsernamePasswordAuthenticationToken
并使用 UserDetailsService
(您有一个 customUserDetailsService
)来检索用户并使用配置的 PasswordEncoder
.
password
关于Authentication Architecture的参考文档中有更详细的解释。