平台认证认证使用Spring生态系统中用于实现Oauth2.0 认证和授权的框架。 其中实现了Oahth2.0授权服务器和JSON Web Tokens (JWT)。 Oauth2.0具有模块化、安全性、扩展性和广泛使用等特点,使其成为构建安全认证和授权的理想选择。
部分原理介绍
OAuth2ClientAuthenticationFilter
OAuth2ClientAuthenticationFilter 是用于处理Oauth2.0客户端身份验证和令牌交换的关键组件。 处理Oauth2.0客户端凭证,当应用程序使用客户端凭证授权流程获取访问令牌时。 负责将客户端ID和客户端密钥发送到授权服务器以获取令牌。
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!this.requestMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
try {
Authentication authenticationRequest = this.authenticationConverter.convert(request);
if (authenticationRequest instanceof AbstractAuthenticationToken) {
((AbstractAuthenticationToken) authenticationRequest).setDetails(
this.authenticationDetailsSource.buildDetails(request));
}
if (authenticationRequest != null) {
validateClientIdentifier(authenticationRequest);
Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult);
}
filterChain.doFilter(request, response);
} catch (OAuth2AuthenticationException ex) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Client authentication failed: %s", ex.getError()), ex);
}
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
}
}
OAuth2TokenEndpointFilter 是Spring Security Oauth2.0 框架中的一个关键过滤器。 主要用于处理Oauth2.0授权服务器的令牌断点请求。 在处理令牌请求之前,该过滤器会验证客户端的身份,确保客户端是否合法的并且具有授权权限。
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!this.tokenEndpointMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
try {
String[] grantTypes = request.getParameterValues(OAuth2ParameterNames.GRANT_TYPE);
if (grantTypes == null || grantTypes.length != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.GRANT_TYPE);
}
Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request);
if (authorizationGrantAuthentication == null) {
throwError(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, OAuth2ParameterNames.GRANT_TYPE);
}
if (authorizationGrantAuthentication instanceof AbstractAuthenticationToken) {
((AbstractAuthenticationToken) authorizationGrantAuthentication)
.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, accessTokenAuthentication);
} catch (OAuth2AuthenticationException ex) {
SecurityContextHolder.clearContext();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Token request failed: %s", ex.getError()), ex);
}
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
}
}
AuthenticationConverter
AuthenticationConverter 是一个接口,用于将来自Oauth2.0 令牌的信息转换为Spring Security中的 Authentication 对象。 一般情况下,开发人员需要实现自定义的 AuthenticationConverter 接口来适应其应用程序的需求。 并确保令牌中的信息能够被正确提取并用于身份验证。
public interface AuthenticationConverter {
Authentication convert(HttpServletRequest request);
}
IncloudUsernamePasswordValidationConverter 提取用户名和密码用于密码验证处理器把密码转换为明文。
private String usernameParameter = "username";
private String passwordParameter = "password";
private String loginTypeParameter = "loginType";
private final RegisteredClientMapper registeredClientMapper;
@Autowired
UserDetailsService userDetailsService;
@Override
public IncloudAuthenticationToken convert(HttpServletRequest request) {
String username = obtainUsername(request);
Optional.ofNullable(username).orElseThrow(() -> {
log.error("用户名不能为空!");
return new IncloudOAuth2AuthenticationException("用户名不能为空!");
});
String password = obtainPassword(request);
password = (password != null) ? password : "";
if (StrUtil.isNotEmpty(password)) {
password = password.replace(" ", "+"); //处理一下AES加密有+号的问题
}
String authorization = request.getHeader("Authorization");
String basic_ = null;
try {
basic_ = new String(Base64.getDecoder().decode(authorization.replaceAll("Basic ", "")), "UTF-8");
} catch (UnsupportedEncodingException e) {
log.error("提交的认证信息有误,请使用Authorization Basic形式提交!");
throw new IncloudOAuth2AuthenticationException("提交的认证信息有误,请使用Authorization Basic形式提交!");
}
String clientId = StrUtil.subBefore(basic_, ":", false);
RegisteredClient registeredClient = registeredClientMapper.getByClientId(clientId);
String decrypetPasword = null;
if (ObjectUtil.isNotEmpty(registeredClient) && registeredClient.getIsPwdRequired().equals(YesNo.YES.code)) {
decrypetPasword = decodeByRSA(clientId, password, registeredClient.getEncryptionPrivateKey4Pwd(), registeredClient.getEncryptionPublicKey4Pwd());
}
if (clientId.contains(ClientInfo.INCLOUD.getName())) {
decrypetPasword = decodeByRSA(clientId, password, SecretReadUtil.readSecretLocal().getPrivateKey(), SecretReadUtil.readSecretLocal().getPublicKey());
}
log.info("convert Before {} After {}", password, decrypetPasword);
UsernamePasswordAuthenticationToken result = IncloudAuthenticationToken.unauthenticated(username, decrypetPasword);
IncloudAuthenticationToken incloudAuthenticationToken = new IncloudAuthenticationToken(result.getPrincipal(), result.getCredentials());
incloudAuthenticationToken.setClientId(clientId);
if (clientId.contains(ClientInfo.INCLOUD.getName())) {
incloudAuthenticationToken.requiredPwd(StrUtil.isNotEmpty(decrypetPasword) ? YesNo.YES.code : clientId.contains(ClientInfo.INCLOUD_INTERNAL.getName()) ? YesNo.NO.code : YesNo.YES.code);
} else {
incloudAuthenticationToken.requiredPwd(ObjectUtil.isNotEmpty(registeredClient) ? registeredClient.getIsPwdRequired() : YesNo.YES.code);
}
result.setDetails(getAuthenticationDetailsSource().buildDetails(request));
userDetailsService.set(request.getHeader(loginTypeParameter));
return incloudAuthenticationToken;
}
String decodeByRSA(String clientId, String content, String privateKey, String publicKey) {
try {
RSA rsa = SecureUtil.rsa(privateKey, publicKey);
return rsa.decryptStr(content, KeyType.PrivateKey);
} catch (Exception e) {
if(!clientId.equals(ClientInfo.INCLOUD_INTERNAL.getName())){
log.error("用户密码解密失败,rsa解密失败!");
throw new IncloudOAuth2AuthenticationException("用户密码解密失败,rsa解密失败!");
}
}
return null;
}
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
IncloudUsernamePasswordAuthenticationProvider
IncloudUsernamePasswordAuthenticationProvider 用户名密码认证器, 用于实现基于数据库的用户身份验证,通过与数据库集成,提供了一种标准和安全的方式来验证用户的身份。 以实现全面的身份验证和授权机制。
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
try {
IncloudAuthenticationToken incloudAuthentication = (IncloudAuthenticationToken) authentication;
//通过clientId获取到提过的来的客户端注册信息,最终完成生成token后,把token信息和客户端信息都存到redis中;
RegisteredClient registeredClient = registeredClientRepository.findByClientId((incloudAuthentication).getClientId());
RegisteredClientVo registeredClientVo = NumberUtil.isNumber(registeredClient.getClientId()) ? registeredClientService.findById(Long.valueOf(registeredClient.getClientId())) : registeredClientService.findByClientId(registeredClient.getClientId());
if(ObjectUtil.isNotEmpty(registeredClientVo)){
userDetailsService.set(new UserInfo
(registeredClientVo.getIsPwdRequired(),incloudAuthentication.getPrincipal().toString(),
registeredClientVo.getClientId(),registeredClientVo.getClientName()));
}
Authentication authenticate = super.authenticate(incloudAuthentication);
if (authenticate.isAuthenticated()) {
return getAccessToken(authenticate, registeredClient);
}
log.error("用户名密码校验失败!");
throw new AuthenticationSecurityException("用户名密码校验失败!");
} catch (Exception e) {
log.error("用户名密码校验失败!");
throw new AuthenticationSecurityException("用户名密码校验失败!", e);
}
}
UserDetailsService
UserDetailsService 实现了 UserDetailsService 接口。它从数据库中加载用户相信信息, 并将其封装为 LoginAppUser对象返回。这个用户详细对象包含了用户名、密码、权限等信息。 供Spring Security使用。
@Override
public UserDetails loadUserByUsername(String username) {
log.info("UserDetailsService loadUserByUsername username:{}", username);
LoginAppUser loginAppUser = getLoginAppUser(username);
if (ObjectUtil.isEmpty(loginAppUser)) {
log.warn("未获取到用户信息:{}", username);
throw new IncloudException("用户不存在,或账号密码错误!");
} else if (!loginAppUser.isEnabled()) {
throw new IncloudException("【用户已失效,请联系管理员核对身份信息】");
}
return loginAppUser;
}
DaoAuthenticationProvider
DaoAuthenticationProvider 主要是用于验证用户的身份,包括用户名和密码。 它将接受到的用户名和密码与数据库中存储的用户凭据进行对比。以确认是否身份验证成功。 它通过与用户详情服务一起工作。提供了一种标准和安全的方式来验证用户的身份,以及确保密码安全存储。
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
AuthenticationUsernamePasswordSuccessHandler
AuthenticationUsernamePasswordSuccessHandler是实现了AuthenticationSuccessHandler的一个是嫌累。 用于处理成功身份验证事件。它定义了处理成功身份验证后的行为,通常包括重定向到某个页面,生成令牌等操作。
public interface AuthenticationSuccessHandler {
default void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
this.onAuthenticationSuccess(request, response, authentication);
chain.doFilter(request, response);
}
void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException;
}