package com.vci.ubcs.code.service.impl; import com.alibaba.fastjson.JSON; import com.vci.ubcs.code.entity.TokenUserObject; import com.vci.ubcs.code.service.IPasswordFreeLoginService; import com.vci.ubcs.starter.util.HttpUtils; import io.jsonwebtoken.Claims; import lombok.extern.slf4j.Slf4j; import org.apache.http.auth.AuthenticationException; import org.springblade.core.jwt.JwtUtil; import org.springblade.core.jwt.props.JwtProperties; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.redis.cache.BladeRedis; import org.springblade.core.secure.BladeUser; import org.springblade.core.tool.support.Kv; import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.ObjectUtil; import org.springblade.core.tool.utils.SpringUtil; import org.springblade.core.tool.utils.StringUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Map; import java.util.logging.Logger; import static com.vci.ubcs.starter.util.AESUtils.aesDecrypt; import static com.vci.ubcs.starter.util.AESUtils.aesEncrypt; import static org.springblade.core.secure.utils.AuthUtil.parseJWT; /** * 免密登录服务 * @author ludc * @date 2023/9/11 15:45 */ @Service @Slf4j public class PasswordFreeLoginServiceImpl implements IPasswordFreeLoginService { // 通过服务注册中心获取网关的端口号 @Autowired private DiscoveryClient discoveryClient; // 配置的免密登录的账号所属的租户id @Value("${password-free.pwd-free-tenant-id:000000}") private String pwdFreeTenantId; // 配置的token在redis中的生存时间 @Value("${password-free.token-redis-expire:36000}") private Long tokenRedisExpire; @Value("${password-free.pwd-free-addr:localhost}") private String pwdFreeAddr; @Value("${password-free.client-id:a104c4fd2f0e4958}") private String clientId;//应用ID @Value("${password-free.secret-key:9fbd170bd83eb869}") private String secretKey;//应用秘钥 @Autowired private BladeRedis bladeRedis; // 缓存名 public static final String PWD_FREE_LOGIN_TOKEN = "pwdFreeLogin:Token:"; private static JwtProperties jwtProperties; /** * 根据服务名获取端口号 * @param serviceId * @return */ public String getGatewayPort(String serviceId) { List instances = discoveryClient.getInstances(serviceId); if (!instances.isEmpty()) { ServiceInstance gatewayInstance = instances.get(0); return String.valueOf(gatewayInstance.getPort()); } return "8080"; } /** * 免密登录,改变当前webservice请求的header * @param userName 账号 * @param servletRequest * @return boolean * @throws AuthenticationException */ @Override public boolean pwdFreeLoginByBoolean(String userName, ServletRequest servletRequest) throws AuthenticationException { //进来先判断缓存中是否存在token // 请求来自己哪个ip地址 HttpServletRequest request = (HttpServletRequest) servletRequest; String ipAddr = request.getRemoteAddr(); // 先尝试从缓存当中取,存在就直接从缓存中获取 String authToken = bladeRedis.get(PWD_FREE_LOGIN_TOKEN+ipAddr+":"+userName); // 解析token存放进attr中 String token2 = JwtUtil.getToken(authToken); BladeUser user = this.getUser(token2); //不存在就请求 if(Func.isEmpty(authToken) || Func.isEmpty(user)){ String responseBody = this.passwordFreeLogin(userName); //拿到响应体其中包含token,用request中的ip地址作为键值,将token存入缓存 TokenUserObject tokenUserObject = null; try { tokenUserObject = JSON.parseObject(responseBody, TokenUserObject.class); }catch (Exception e){ throw new ServiceException("鉴权响应内容转换为TokenUserObject失败:"+e.getMessage()); } // 拼接token格式 authToken = "bearer " + tokenUserObject.getAccess_token(); // 将token存入缓存当中,过期时间为24小时 bladeRedis.setEx(PWD_FREE_LOGIN_TOKEN+ipAddr+":"+userName,"bearer "+tokenUserObject.getAccess_token(),tokenRedisExpire); token2 = JwtUtil.getToken(authToken); user = this.getUser(token2); } //request.setAttribute("Blade-Auth",token); request.setAttribute("_BLADE_USER_REQUEST_ATTR_",user); return true; } /** * 免密登录请求发送 * @param userName 账号 * @return 返回token * @throws AuthenticationException */ @Override public String passwordFreeLogin(String userName) throws AuthenticationException { // 免密登录接口地址 String loginUrl = "http://"+pwdFreeAddr+":"+this.getGatewayPort("ubcs-gateway")+"/ubcs-auth/oauth/token"; log.debug("当前免密登录调用地址:"+loginUrl); // 请求ubcs-auth服务获取token,先设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.set("Authorization", "Basic c3dvcmQ6c3dvcmRfc2VjcmV0"); headers.set("Tenant-Id", pwdFreeTenantId); //设置请求体参数 MultiValueMap parameters = new LinkedMultiValueMap(); parameters.add("username",userName); parameters.add("grant_type", "passwordfree"); parameters.add("scope", "all"); parameters.add("type", "account"); String responseBody = null; try { // 发送POST请求 responseBody = HttpUtils.post(loginUrl, parameters,headers); }catch (Exception e){ throw new AuthenticationException("调用鉴权服务ubcs-auth失败,原因:"+e.getMessage()); } return responseBody; } /** * 单点登录 * @param empCode * @return * @throws Exception */ @Override public String ssoFreeLogin(String empCode) throws Exception { if(Func.isBlank(empCode)){ throw new ServiceException("未获取到empCode参数"); } String enStr2; try { String enStr1 = aesDecrypt(empCode, secretKey); enStr2 = aesDecrypt(enStr1, clientId); }catch (Exception e){ throw new ServiceException("empCode参数解密失败!原因:"+e.getCause()); } // 解密 log.debug("单点登录参数解密后:"+enStr2); String token = this.passwordFreeLogin(enStr2); return token; } /** * 根据token获取用户信息 * @param token * @return */ private static BladeUser getUser(String token) { Claims claims = getClaims(token); if (claims == null) { return null; } else { String clientId = Func.toStr(claims.get("client_id")); Long userId = Func.toLong(claims.get("user_id")); String tenantId = Func.toStr(claims.get("tenant_id")); String oauthId = Func.toStr(claims.get("oauth_id")); String deptId = Func.toStrWithEmpty(claims.get("dept_id"), "-1"); String postId = Func.toStrWithEmpty(claims.get("post_id"), "-1"); String roleId = Func.toStrWithEmpty(claims.get("role_id"), "-1"); String account = Func.toStr(claims.get("account")); String roleName = Func.toStr(claims.get("role_name")); String userName = Func.toStr(claims.get("user_name")); String nickName = Func.toStr(claims.get("nick_name")); String tenantName = Func.toStr(claims.get("tenantName")); String email = Func.toStr(claims.get("email")); String deptName = Func.toStr(claims.get("deptName")); String secretGrade = Func.toStr(claims.get("secretGrade")); Kv detail = Kv.create().setAll((Map)claims.get("detail")); BladeUser bladeUser = new BladeUser(); bladeUser.setClientId(clientId); bladeUser.setUserId(userId); bladeUser.setTenantId(tenantId); bladeUser.setOauthId(oauthId); bladeUser.setAccount(account); bladeUser.setDeptId(deptId); bladeUser.setPostId(postId); bladeUser.setRoleId(roleId); bladeUser.setRoleName(roleName); bladeUser.setUserName(userName); bladeUser.setNickName(nickName); detail.put("tenantName", tenantName); detail.put("deptName", deptName); detail.put("email", email); detail.put("secretGrade", secretGrade); bladeUser.setDetail(detail); return bladeUser; } } /** * 解析token * @param authToken * @return */ private static Claims getClaims(String authToken) { Claims claims = null; String token = null; String tenantId; if (StringUtil.isNotBlank(authToken)) { token = JwtUtil.getToken(authToken); } if (StringUtil.isNotBlank(token)) { claims = parseJWT(token); } if (ObjectUtil.isNotEmpty(claims) && getJwtProperties().getState()) { tenantId = Func.toStr(claims.get("tenant_id")); String userId = Func.toStr(claims.get("user_id")); String accessToken = JwtUtil.getAccessToken(tenantId, userId, token); if (!token.equalsIgnoreCase(accessToken)) { return null; } } return claims; } private static JwtProperties getJwtProperties() { if (jwtProperties == null) { jwtProperties = (JwtProperties) SpringUtil.getBean(JwtProperties.class); } return jwtProperties; } }