incloud开发文档 5.1.0 Help

认证及授权原理

平台认证认证使用Spring生态系统中用于实现Oauth2.0 认证和授权的框架。 其中实现了Oahth2.0授权服务器和JSON Web Tokens (JWT)。 Oauth2.0具有模块化、安全性、扩展性和广泛使用等特点,使其成为构建安全认证和授权的理想选择。

Spring Authorization Server

平台基于Spring Authorization Server实现了平台的认证和授权功能。 Spring Authorization Server是一个开源的认证和授权服务器,它是Spring Security团队开发的一个新项目,旨在为OAuth 2.0和OpenID Connect(OIDC)提供一个功能齐全的、易于扩展的、生产就绪的解决方案。 可以参考官网:https://spring.io/projects/spring-authorization-server

Auth

部分原理介绍

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; }
Last modified: 20 一月 2025