问题背景
客户需求:在 HZERO 后端根据用户账号获取对应的 Token。客户原思路是”根据账号查出密码 → 解密 → 调用登录接口获取 Token”。
一、为什么”查密码→解密”方案不可行
HZERO 用户密码使用 BCrypt 单向哈希算法存储,数据库中保存的是哈希值,不可逆、不可解密。因此”从数据库查出密码 → 解密 → 调用登录接口”这条路在技术上走不通。
二、根据是否有用户明文密码,分两种方案
方案一:有用户明文密码 — 标准 OAuth2 password 模式
如果调用方持有用户的明文密码(例如用户自己输入),HZERO 原生支持标准 OAuth2 Password Grant,直接调用即可获取 Token,无需二次开发:
请求:
POST /oauth/oauth/token
Authorization: Basic <Base64(client_id:client_secret)>
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=用户账号&password=明文密码
返回示例:
{
"access_token": "***",
"token_type": "bearer",
"refresh_token": "***",
"expires_in": 32399999,
"scope": "default"
}
注意事项:
- 端点路径是
/oauth/oauth/token(两层 oauth 路径,不是/oauth/token) client_id和client_secret使用项目实际配置的客户端凭据- 密码传输明文,建议接口走 HTTPS
- Token 有效期可通过 OAuth 服务端配置调整
方案二:无用户明文密码 — 自定义登录接口(二开)
如果调用方没有用户密码(例如服务间调用、管理端代操作等场景),需要在 hzero-oauth 服务中进行二次开发,实现自定义认证流程。
HZERO 已提供了标准扩展组件 LoginTokenService,核心开发步骤如下:
1. 自定义认证对象 AuthenticationToken
继承 AbstractAuthenticationToken,封装认证信息(如用户账号 + 加签字符串):
public class CustomAuthenticationToken extends AbstractAuthenticationToken {
private final Serializable principal; // 用户账号
private final String sign; // 加签字符串
// 未认证构造方法
public CustomAuthenticationToken(Serializable principal, String sign) {
super(null);
this.principal = principal;
this.sign = sign;
setAuthenticated(false);
}
// 已认证构造方法
public CustomAuthenticationToken(Serializable principal,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.sign = null;
super.setAuthenticated(true);
}
@Override
public Serializable getCredentials() { return null; }
@Override
public Serializable getPrincipal() { return this.principal; }
public String getSign() { return this.sign; }
}
2. 自定义认证器 AuthenticationProvider
实现 AuthenticationProvider 接口,在 authenticate() 方法中:
- 校验加签字符串(自定义安全逻辑)
- 通过
UserDetailsService加载用户 - 返回已认证的 Authentication
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) {
CustomAuthenticationToken token = (CustomAuthenticationToken) authentication;
String username = (String) token.getPrincipal();
String sign = token.getSign();
// 1. 校验加签字符串(自定义安全逻辑)
// TODO: 实现你的签名校验逻辑,例如 HMAC/RSA 验签
// 2. 加载用户
UserDetails user = userDetailsService.loadUserByUsername(username);
// 3. 返回已认证的 Authentication
return new CustomAuthenticationToken(username, user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return CustomAuthenticationToken.class.isAssignableFrom(authentication);
}
}
3. 自定义 LoginTokenService
继承 LoginTokenService,在 attemptAuthentication() 中从请求提取参数:
public class CustomLoginTokenService extends LoginTokenService {
public CustomLoginTokenService(TokenGranter tokenGranter,
ClientDetailsService clientDetailsService,
OAuth2RequestFactory oAuth2RequestFactory,
CustomAuthenticationProvider authenticationProvider) {
super(tokenGranter, clientDetailsService, oAuth2RequestFactory, authenticationProvider);
}
@Override
protected Authentication attemptAuthentication(HttpServletRequest request) {
String username = request.getParameter("username");
String sign = request.getParameter("sign");
return new CustomAuthenticationToken(username, sign);
}
}
4. 配置类
@Configuration
@AutoConfigureAfter(AuthorizationServerEndpointsConfiguration.class)
public class TokenConfiguration {
private final AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();
@Autowired
private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
@PostConstruct
public void init() {
for (AuthorizationServerConfigurer configurer : configurers) {
try {
configurer.configure(endpoints);
} catch (Exception e) {
throw new IllegalStateException("Cannot configure endpoints", e);
}
}
}
@Bean
public CustomLoginTokenService customLoginTokenService(
CustomAuthenticationProvider customAuthenticationProvider) {
return new CustomLoginTokenService(
endpoints.getTokenGranter(),
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(),
customAuthenticationProvider
);
}
}
5. 登录接口
@RestController
public class CustomLoginController {
@Autowired
private CustomLoginTokenService customLoginTokenService;
@PostMapping("/token/custom")
public ResponseEntity<AuthenticationResult> loginToken(HttpServletRequest request) {
AuthenticationResult result = customLoginTokenService.loginForToken(request);
return Results.success(result);
}
}
调用方式:
POST /oauth/token/custom
Content-Type: application/x-www-form-urlencoded
username=用户账号&sign=加签字符串&client_id=xxx&client_secret=***
三、详细文档参考
开放平台文档《定制登录获取令牌》:
https://open.hand-china.com/document-center/doc/product/10002/11014
文档中包含完整的类图说明和短信验证码登录的代码示例,可直接参考。另外文档目录中还有《非标单点登录示例》也可作为补充参考。