# 登录认证扩展
框架提供了用户名密码、短信验证码、CA登录方式, 如果不满足项目实际要求,我们可以按照以下流程对认证模块进行扩展:
# 第一步,在pom文件引入hos-security-login
<dependency>
<groupId>com.mediway.hos</groupId>
<artifactId>hos-security-login</artifactId>
</dependency>
# 第二步,定义Param信息类
创建一个类用来将前端登录传的参数进行实体化,继承AbstractHosLoginParam类。
import com.mediway.hos.security.core.authentication.param.AbstractHosLoginParam;
import lombok.Data;
@Data
public class HosFSLoginParam extends AbstractHosLoginParam {
private String token;
}
# 第三步,定义token信息类
创建一个自定义的token类,继承AbstractHosAuthenticationToken<自定义Param类>。在该类中,定义构造函数并定义一个HosGrantType对象。 hosGrantType值和调用登录接口(/api/security/token?grantType=password)传的grantType值保持一致。
import com.mediway.hos.security.core.HosGrantType;
import com.mediway.hos.security.core.authentication.AbstractHosAuthenticationToken;
public class HosFSAuthenticationToken extends AbstractHosAuthenticationToken<HosFSLoginParam> {
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return null;
}
private HosFSAuthenticationToken() {
super();
}
public HosFSAuthenticationToken(HosFSLoginParam loginParam) {
super(loginParam, new HosGrantType("FSToken"));
}
}
# 第四步,定义Converter类
创建一个自定义的Converter类,该类继承AbstractHosAuthenticationConverter类。在该类中重写convert方法,将请求中的参数进行该认证方式需要的参数类转换。
import com.mediway.hos.security.core.HosLoginParameterNames;
import com.mediway.hos.security.core.util.JsonUtils;
import com.mediway.hos.security.core.util.SecurityUtil;
import com.mediway.hos.security.login.authorization.convert.AbstractHosAuthenticationConverter;
import org.springframework.security.core.Authentication;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class HosFSAuthenticationConverter extends AbstractHosAuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
String grantType = request.getParameter(HosLoginParameterNames.GRANT_TYPE);
if (!"FSToken".equals(grantType)) {
return null;
}
try {
HosFSLoginParam smsLoginParam = JsonUtils.getInstance().readValue(request.getInputStream(), HosFSLoginParam.class);
smsLoginParam.setHosGrantType(grantType);
HosFSAuthenticationToken fsAuthenticationToken = new HosFSAuthenticationToken(smsLoginParam);
return fsAuthenticationToken;
} catch (IOException e) {
logger.error("统一开放服务集成平台系统登录参数解析错误", e);
SecurityUtil.throwError("101-002-004-012", "统一开放服务集成平台系统登录参数解析错误");
}
return null;
}
}
# 第五步,定义Provider类
创建一个自定义的Provider类,该类继承 AbstractHosAuthenticationProvider<自定义Token类>类。重写以下四个方法:
1、loadHosUserDetails(自定义token类 authentication):必须重写,该方法用于根据认证传参获取用户信息。输入参数authentication是一个封装了
用户认证信息的Authentication对象,包括用户名、密码等信息。该方法返回一个查询成功的HosUser对象,或者抛出SecurityAuthenticationException
异常表示获取用户失败。
2、supports(Class<?> authentication):必须重写,该方法用于判断是否支持给定类型的认证请求。输入参数authentication是要被验证的对象类型,
通常是UsernamePasswordAuthenticationToken或JwtAuthenticationToken等。该方法返回一个boolean值,表示是否支持该类型的认证请求。
3、verifyParam(自定义token类 authentication, HosUser hosUser):非必须重写,该方法用于执行身份验证过程,可以不重写。
输入参数authentication是一个封装了用户认证信息的Authentication对象,包括用户名、密码等信息,hosUser是执行loadHosUserDetails返回的用户信息。
该方法不做任何返回,或者抛出AuthenticationException异常表示认证失败。
4、canMultiGrant():非必须重写,该方法用于设置是否需要多因素认证。父类默认设置的false,代表不需要多因素认证,如果该自定义登录需要进行对因素认证,则返回true。
```java
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.mediway.hos.security.core.TokenUniqueKey;
import com.mediway.hos.security.core.authentication.AbstractHosAuthenticationToken;
import com.mediway.hos.security.core.userdetails.HosUser;
import com.mediway.hos.security.core.userdetails.HosUserDetails;
import com.mediway.hos.security.core.util.CommonUtils;
import com.mediway.hos.security.core.util.HttpUtils;
import com.mediway.hos.security.core.util.SecurityUtil;
import com.mediway.hos.security.core.util.StringUtil;
import com.mediway.hos.security.enums.BaseBusinessExceptionEnum;
import com.mediway.hos.security.enums.VersionEnum;
import com.mediway.hos.security.login.HosUserDetailsService;
import com.mediway.hos.security.login.authorization.provider.AbstractHosAuthenticationProvider;
import com.mediway.hos.security.login.multiFactor.store.PostStore;
import com.mediway.hos.security.login.service.ConvertPostService;
import com.mediway.hos.security.login.service.SelectUserInfoService;
import com.mediway.hos.security.login.token.HosTokenAuthenticationTokenGenerator;
import com.mediway.hos.security.properties.HosSecurityProperties;
import com.mediway.hos.security.properties.HosVersionProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import java.util.*;
@Slf4j
public class HosFSAuthenticationProvider extends AbstractHosAuthenticationProvider<HosFSAuthenticationToken> {
private final HosUserDetailsService hosUserDetailsService;
private final HosFSProperties hosFSProperties;
private final HosVersionProperties hosVersionProperties;
private final ConvertPostService convertPostService;
private final HosTokenAuthenticationTokenGenerator hosTokenAuthenticationTokenGenerator;
private final PostStore postStore;
public HosFSAuthenticationProvider(HosTokenAuthenticationTokenGenerator hosTokenAuthenticationTokenGenerator,
HosSecurityProperties hosSecurityProperties,
PostStore postStore,
HosVersionProperties hosVersionProperties,
ConvertPostService convertPostService,
SelectUserInfoService selectUserInfoService,
HosUserDetailsService hosUserDetailsService,
HosFSProperties hosFSProperties) {
super(null, null, hosTokenAuthenticationTokenGenerator, hosSecurityProperties, postStore, hosVersionProperties, convertPostService, selectUserInfoService);
this.hosUserDetailsService = hosUserDetailsService;
this.hosFSProperties = hosFSProperties;
this.hosVersionProperties = hosVersionProperties;
this.convertPostService = convertPostService;
this.hosTokenAuthenticationTokenGenerator = hosTokenAuthenticationTokenGenerator;
this.postStore = postStore;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(HosFSAuthenticationToken.class);
}
/**
* 根据登录参数获取用户信息
*
* @param authentication
* @return
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
HosFSAuthenticationToken fsAuthenticationToken = (HosFSAuthenticationToken) authentication;
//获取token
String token=fsAuthenticationToken.getLoginParam().getToken();
//获取链接、系统标识system-identifier
if (StringUtil.isBlank(hosFSProperties.getUrl())){
SecurityUtil.throwError("000099-01","请联系管理员维护验证token接口地址");
}
if (StringUtil.isBlank(hosFSProperties.getSystemIdentifier())){
SecurityUtil.throwError("000099-02","请联系管理员维护系统标识");
}
JSONObject param=new JSONObject();
param.set("token", token);
Map<String, String> headers=new HashMap<String, String>();
headers.put("system-identifier", hosFSProperties.getSystemIdentifier());
String value = HttpUtils.postJson(hosFSProperties.getUrl(),headers, param.toString());
log.error("FS验证token===接口返回数据: " + value);
if(CommonUtils.isEmpty(value) || value.trim().startsWith("<")){
log.error("FS验证token===接口返回数据: " + value);
SecurityUtil.throwError("000099-03","验证token接口异常");
}
JSONObject result = JSONUtil.parseObj(value);
String personId ="";
String userCode="";
if(CommonUtils.equals(result.getStr("code"),"000000")){
//验证成功,获取登录信息
JSONObject data =result.getJSONObject("data");
personId = data.getStr("personId");
if (StringUtil.isBlank(personId)){
SecurityUtil.throwError("000099-05","用户名异常");
}else{
userCode=personId.split("@")[0];
}
}else{
SecurityUtil.throwError("000099-05",result.getStr("message"));
}
HosUserDetails hosUserDetails = hosUserDetailsService.loadUserByUsername(userCode);
//根据人员获取岗位
String pId = "";
if (!SecurityUtil.isAdmin(hosUserDetails.getHosUser().getAccountCode())) {
pId = hosUserDetails.getHosUser().getPersonId();
} else {
pId = "admin";
}
JSONObject post = null;
//如果当前版本不是简版而且传了岗位信息,需要获取对应的岗位,调用接口传了岗位信息,就以传的岗位登录
if (!CommonUtils.equals(hosVersionProperties.getVersion(), VersionEnum.SIMPLE.getCode())) {
String postStr = convertPostService.getPostByPostInfo(null, null, null, null, pId);
if (CommonUtils.isEmpty(postStr)) {
SecurityUtil.throwError(BaseBusinessExceptionEnum.POST_ERROR);
} else {
post = JSONUtil.parseObj(postStr, true);
}
}
TokenUniqueKey tokenUniqueKey = new TokenUniqueKey();
Map<String, AbstractHosAuthenticationToken> authenticationMap = new HashMap<>();
authenticationMap.put(fsAuthenticationToken.getClass().getName(), fsAuthenticationToken);
HosUser hosUser = hosUserDetails.getHosUser();
if (CommonUtils.isNotNull(post)) {
if (CommonUtils.equals(hosVersionProperties.getVersion(), VersionEnum.PROFESSIONAL.getCode())) {
JSONArray postRecords = (JSONArray) post.get("postRecords");
List<String> postSourceIds = new ArrayList<>();
if (CommonUtils.isNotEmpty(postRecords)) {
for (Object postRecord : postRecords) {
JSONObject object = (JSONObject) postRecord;
String id = object.getStr("postSourceId");
if (StringUtil.isNotBlank(id)) {
postSourceIds.add(id);
}
}
if (CommonUtils.isNotEmpty(postSourceIds)) {
//todo 调用基础平台实现的service,获取岗位id转换信息
String postIds = convertPostService.convertPostId(postSourceIds);
hosUser.setPostId(postIds);
log.info("第一次放入postId---------------------------------");
} else {
SecurityUtil.throwError(BaseBusinessExceptionEnum.POST_ERROR);
}
hosUser.setPostName(post.getStr("name"));
} else {
SecurityUtil.throwError(BaseBusinessExceptionEnum.POST_ERROR);
}
} else if (CommonUtils.equals(hosVersionProperties.getVersion(), VersionEnum.WROUGHT.getCode())) {
String postCode = post.getStr("postCode");
if (StringUtil.isBlank(postCode)) {
SecurityUtil.throwError(BaseBusinessExceptionEnum.POST_ERROR);
} else {
//todo 调用基础平台实现的service,获取岗位id转换信息
String postIds = convertPostService.convertPostIdByCode(postCode);
hosUser.setPostId(postIds);
hosUser.setPostName(post.getStr("postName"));
log.info("第一次放入postId---------------------------------");
}
}
hosUser.setPostOneVO(post);
} else {
hosUser.setAuthVersion(VersionEnum.SIMPLE.getCode());
}
return hosTokenAuthenticationTokenGenerator.generate(authenticationMap, hosUserDetails.getHosUser(), tokenUniqueKey);
}
}
# 第六步,配置类增加扩展登录
找到SecurityLoginConfig配置类的securityFilterChain()方法,修改HosLoginConfigurer配置:
@Bean
@Order(value = Ordered.HIGHEST_PRECEDENCE + 100)
SecurityFilterChain securityFilterChain(HttpSecurity http, HosSecurityProperties hosSecurityProperties, HosPostInfoService hosPostInfoService) throws Exception {
...
// 加载登录配置
HosLoginConfigurer hosLoginConfigurer = new HosLoginConfigurer();
// 加载统一认证配置
if (hosSecurityProperties.getOauth2().isEnable()) {
hosLoginConfigurer.hosOAuth2Login();
}
hosLoginConfigurer.loginTokenEndpoint(loginTokenEndpoint -> {
//注入自定义Converter
loginTokenEndpoint.authenticationConverter(new HosFSAuthenticationConverter());
//注入自定义Provider
loginTokenEndpoint.authenticationProvider(new HosFSAuthenticationProvider(HosConfigurerUtils.getHosTokenAuthenticationTokenGenerator(http),
HosConfigurerUtils.getHosSecurityProperties(http),
SpringContextUtils.getBean(PostStore.class),
SpringContextUtils.getBean(HosVersionProperties.class),
SpringContextUtils.getBean(ConvertPostService.class),
SpringContextUtils.getBean(SelectUserInfoService.class),
SpringContextUtils.getBean(HosUserDetailsService.class),
SpringContextUtils.getBean(HosFSProperties.class)
));
});
http.apply(hosLoginConfigurer);
...
return http.build();
}
# 第七步,测试
启用后端服务器,调用登录接口/api/security/token
。
参数说明:query中grantType等于第二步自定义token类中HosGrantType的值,body数据为第一步自定义参数的值。