智慧互安认证平台-账号映射-若依框架接入教程
对接说明
1、注册【智慧互安认证系统】 2、应用管理->创建应用(认证类型默认不填)->新增映射账号->下载密钥文件
若依框架改造
后端
1、新增统一认证工具类
package com.detn.common.utils.authLogin;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.detn.common.exception.TClientException;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 统一认证信息
*/
public class AuthLoginUtils {
/**
* 接口请求地址(不可变)
*/
private static final String BASEURL = "http://kf.ct35.top";
/**
* 统一认证页面地址(不可变)
*/
private static final String AUTH_LOGIN_URL = "http://kf.ct35.top/authLogin?token=";
/**
* APPID
*/
private static final String APPID = "1774927884659";
/**
* 应用密钥
*/
private static final String SECRET = "310dbb58acb04596a5b67ce550163e07";
/**
* 公钥(用于参数加密)
*/
private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCNQgfTDrq7sLxlNSZjGSk7OGGQZCd66qOLn7UlXQjiMoWdFl2A6xk+nk25g/XvgM/zJIP/MejAUJdlgQl8qrOGffEkMJFdJaBuIfWbfNupmTIphlmXDq/Fkeji9qU5RNJfyOJNC2kTXnAoHIfMOyMXm32bNHwqA+Mr+ug8VapVlQIDAQAB";
/**
* 私钥(用于参数解密)
*/
private static final String PRIVATE_KEY = "MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBAM8BPUXg6yCuqqn2ENpB9HOemd9aMENiqUW6uMZhlNPSmySzU1rqN+p/leE2MjtczW4ynbwWEZ6tKu2bE8t6JF9qGKI3MvUAozNZ3zZF9DNw7U5+gnWpD1NVF79fOupdwluavHp5HLLszxlgKMD3CwI612M7txfWYJZjRmwMMO7tAgMBAAECgYEAjTe2OkavgRNHgD1Kz+qwvnOTW2jmhgH3ooc4aLXHVx5VEpt3oQ0p6kLx17smV5L+3k+PhfG0QYC3SXNC9Mw1pNuO8O7qlK54arK1INzVR8LExQF0afjC/5ta3B1GakoobI5uYMo3WPx9eq/+yPo/ufOY51+dGIFZ9jLcuxuiseECQQDwxmINB6JgMI+8KLPFoyMmO8Z3vWr/LYSCEP2omseENhQf/ywOrpljT3rXo8KOoPzyWCOax6dPrKAlIcECKDbVAkEA3BgxWMxTjdaIUeWl4cPkvASNflAfKVAARXTHCadtfss4Wjd2NRwL1Ic71TuNpyYjhRhjaxvoBgQcMHzBe5GTuQJBAKDOPgopwR6xEC5AmOA0dnNAlOq/zUgkJE2h/2euB/abD5HXIJulrLgRCyAeNQCBMK7CgvUKxwmFZ2elRXylUwkCQQCz8vfNwWmDK2dThm7FEtQeQZ6qKrn/nhBKuXrO0tw8cc1GPdhg/ehSQKtT4RglFJKwDEuk1DE1UgD2R9otvM3JAkEA1EWyGh/z6PjEOZ7WADaPsyn3E7vbul1IhwbFI04w9YvJDSUWtUWP/TBvEstq3YxfBOnmGx2iyBF4Vj2nodJiyw==";
/**
* 获取token令牌地址
*/
private static final String GET_USER_TOKEN_URL = "/prod-api/open/api/getUserToken";
/**
* 获取统一认证地址
* @return
*/
public static String getAuthLoginUrl(){
try{
JSONObject p = new JSONObject();
p.put("appId", APPID);
p.put("secret", SECRET);
String resultData = doPostJson(BASEURL+GET_USER_TOKEN_URL,JSONObject.toJSONString(p),null);
JSONObject resultDataJSON = JSON.parseObject(resultData);
if (resultDataJSON.getInteger("code") == 200){
return AUTH_LOGIN_URL+resultDataJSON.getJSONObject("data").getString("token");
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 公钥加密方法
*
* @param text 将要加密的信息
* @return Base64编码的字符串
* @throws Exception
*/
public static String encryptByPublicKey(String text) {
try {
// 返回按照 X.509 标准进行编码的密钥的字节
// x509EncodedKeySpec2 是一种规范、规格
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(PUBLIC_KEY));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// 让密钥工厂按照指定的 规范 生成公钥
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
Cipher cipher = Cipher.getInstance("RSA");
// 对加密初始化,使用加密模式,公钥加密
// Cipher.ENCRYPT_MODE 可以用 1 代替
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(text.getBytes());
// 返回 Base64编码的字符串
return Base64.encodeBase64String(result);
} catch (Exception e) {}
return null;
}
/**
* 私钥解密方法
*
* @param text 待解密的信息
* @return 字符串
* @throws Exception
*/
public static String decryptByPrivateKey(String text) {
try {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(PRIVATE_KEY));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(Base64.decodeBase64(text));
return new String(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* post请求
* @param requestUrl 请求URL
* @param paramsJson 请求参数
* @param headers 头部参数
* @return
*/
private static String doPostJson(String requestUrl, String paramsJson, Map<String, String> headers) {
if (org.apache.commons.lang3.StringUtils.isBlank(requestUrl)) {
throw new IllegalArgumentException("请求URL不能为空");
}
HttpURLConnection conn = null;
String var9;
try {
conn = getHttpConnection(requestUrl, headers);
OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8);
try {
if (org.apache.commons.lang3.StringUtils.isNotBlank(paramsJson)) {
out.write(paramsJson);
out.flush();
System.out.println("POST请求发送成功 - URL: " + requestUrl + ", 参数: " + paramsJson);
}
} catch (Throwable var19) {
try {
out.close();
} catch (Throwable var18) {
var19.addSuppressed(var18);
}
throw var19;
}
out.close();
int var23 = conn.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(var23 >= 200 && var23 <= 299 ? conn.getInputStream() : conn.getErrorStream(), StandardCharsets.UTF_8));
try {
StringBuilder responseBuilder = new StringBuilder();
String line;
while((line = in.readLine()) != null) {
responseBuilder.append(line);
}
String response = responseBuilder.toString();
System.out.println("POST请求响应 - URL: " + requestUrl + ", 响应码: " + var23 + ", 结果: " + response);
if (var23 < 200 || var23 > 299) {
throw new IOException(String.format("HTTP请求失败,响应码: %d,错误信息: %s", var23, response));
}
var9 = response;
} catch (Throwable var20) {
try {
in.close();
} catch (Throwable var17) {
var20.addSuppressed(var17);
}
throw var20;
}
in.close();
} catch (IOException e) {
System.out.println("POST请求失败 - URL: " + requestUrl + ", 参数: " + paramsJson + ", 异常: " + e.getMessage());
e.printStackTrace();
throw new RuntimeException("HTTP POST请求执行失败", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return var9;
}
/**
* 封装请求参数
* @param requestUrl
* @param headers
* @return
* @throws IOException
*/
private static HttpURLConnection getHttpConnection(String requestUrl, Map<String, String> headers) throws IOException {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(30L));
conn.setReadTimeout((int) TimeUnit.SECONDS.toMillis(10L));
conn.setUseCaches(false);
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
if (headers != null && !headers.isEmpty()) {
headers.forEach((key, value) -> {
if (org.apache.commons.lang3.StringUtils.isNotBlank(key) && org.apache.commons.lang3.StringUtils.isNotBlank(value)) {
conn.setRequestProperty(key, value);
}
});
}
return conn;
}
/**
* 判断链接有效期
* @param expireTime 登录链接到期时间
* @return true 表示未过期,false 表示已过期
* @throws TClientException
*/
public static boolean checkUrlExpirationTime(String expireTime) throws TClientException {
// 判断是否为空
if (expireTime == null || expireTime.isEmpty()) {
return false;
}
// 判断时间是否过期
try {
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
java.time.LocalDateTime expireDateTime = java.time.LocalDateTime.parse(expireTime, formatter);
if (java.time.LocalDateTime.now().isAfter(expireDateTime)) {
return false;
}
} catch (java.time.format.DateTimeParseException e) {
return false;
}
return true;
}
}
java2、新增对外接口控制层
package com.detn.web.controller.common;
import com.alibaba.fastjson2.JSONObject;
import com.detn.common.constant.CacheConstants;
import com.detn.common.constant.Constants;
import com.detn.common.core.domain.AjaxResult;
import com.detn.common.core.domain.entity.SysUser;
import com.detn.common.core.domain.model.LoginUser;
import com.detn.common.core.redis.RedisCache;
import com.detn.common.exception.TClientException;
import com.detn.common.utils.Result;
import com.detn.common.utils.SecurityUtils;
import com.detn.common.utils.authLogin.AuthLoginUtils;
import com.detn.framework.web.service.SysLoginService;
import com.detn.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/open/api/zhHaAuthLogin")
public class ZhHaAuthLoginController {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysLoginService sysLoginService;
@Autowired
private RedisCache redisCache;
/**
* 获取认证登录地址
* @return
*/
@PostMapping("/getAuthLoginUrl")
public Result getAuthLoginUrl(){
Result result = new Result();
String authLoginUrl = AuthLoginUtils.getAuthLoginUrl();
result.setData(authLoginUrl);
return result;
}
/**
* 统一认证登录地址
* @param data
* @throws TClientException
*/
@PostMapping("/authLogin")
public AjaxResult authLogin(@RequestBody String data) throws TClientException {
data = JSONObject.parseObject(data).getString("token");
data = AuthLoginUtils.decryptByPrivateKey(data);
JSONObject object = JSONObject.parseObject(data);
if (object.getString("appId") == null || "".equals(object.getString("appId"))){
return AjaxResult.error("统一认证登录失败,请求参数异常");
}
//链接有效期验证
String expireTime = object.getString("expireTime");
if (!AuthLoginUtils.checkUrlExpirationTime(expireTime)){
return AjaxResult.error("链接已过期!");
}
String username = object.getString("username");
//登录方式 1-映射、2-透传
Integer loginType = object.getInteger("loginType");
//认证类型 0-账号密码 1-邮箱、2-短信、3-QQ、4-微信、5-支付宝
Integer certificationType = object.getInteger("certificationType");
AjaxResult ajax = AjaxResult.success();
if (loginType == 1){
//映射
String token = null;
token = sysLoginService.authLogin(username);
ajax.put(Constants.TOKEN, token);
}else{
//穿透
String password = object.getString("password");
String token = null;
SysUser sysUser = null;
SysUser sysUserDo = null;
//先查询用户是否存在,不存在的话,先手动注册一个
sysUserDo = sysUserMapper.selectUserByUserName(username);
//如果账号不存在,自动注册一个账号
if (sysUserDo == null){
if (password == null || "".equals(password)){
password = configService.selectConfigByKey("sys.user.initPassword");
}
//新增用户
sysUserDo = new SysUser();
sysUserDo.setUserName(username);
sysUserDo.setNickName(username);
sysUserDo.setPassword(SecurityUtils.encryptPassword(password));
userService.registerUser(sysUserDo);
//角色绑定
sysUserDo.setRoleIds(new Long[]{2L});
userService.insertUserRole(sysUserDo);
}
//根据认证类型处理登录操作
sysUser = sysUserMapper.selectUserByUserName(username);
if (sysUser != null){
/*
可选择校验,但是多平台的情况下要注意做好处理
if (!SecurityUtils.matchesPassword(password, sysUser.getPassword())){
return AjaxResult.error("账号或密码错误!");
}
*/
token = sysLoginService.authLogin(username);
ajax.put(Constants.TOKEN, token);
}else {
return AjaxResult.error("账号或密码错误!");
}
}
return ajax;
}
/**
* 统一认证重置密码
* @param data
* @return
* @throws TClientException
*/
@PostMapping("/resetPassword")
public Result resetPassword(@RequestBody String data) throws TClientException {
data = JSONObject.parseObject(data).getString("token");
String decryptData = AuthLoginUtils.decryptByPrivateKey(data);
JSONObject object = JSONObject.parseObject(decryptData);
String appId = object.getString("appId");
if (appId == null || "".equals(appId)) {
throw new TClientException("统一认证重置密码,请求参数异常");
}
String username = object.getString("username");
if (username == null || "".equals(username)) {
throw new TClientException("统一认证重置密码,用户名不能为空");
}
//查询用户
SysUser sysUser = sysUserMapper.selectUserByUserName(username);
if (sysUser == null){
throw new TClientException("统一认证重置密码,用户不存在");
}
String password = object.getString("password");
if (password == null || "".equals(password)) {
throw new TClientException("统一认证重置密码,密码不能为空");
}
int row = userService.resetUserPwd(username, SecurityUtils.encryptPassword(password));
if (row > 0){
Result result = new Result();
result.setSccess("重置成功");
return result;
}else{
throw new TClientException("统一认证重置密码,重置失败");
}
}
/**
* 统一认证退出登录接口
* @param data
* @return
* @throws TClientException
*/
@PostMapping("/authLogOut")
public AjaxResult authLogOut(@RequestBody String data) throws TClientException {
data = JSONObject.parseObject(data).getString("token");
String decryptData = AuthLoginUtils.decryptByPrivateKey(data);
JSONObject object = JSONObject.parseObject(decryptData);
String appId = object.getString("appId");
if (appId == null || "".equals(appId)) {
throw new TClientException("统一认证退出登录,请求参数异常");
}
String username = object.getString("username");
if (username == null || "".equals(username)) {
throw new TClientException("统一认证退出登录,用户名不能为空");
}
logoutByUsername(username);
return AjaxResult.success("用户已执行注销登录");
}
/**
* 根据用户名强制退出登录(踢下线)
*/
public void logoutByUsername(String username) {
Collection<String> keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*");
for (String key : keys)
{
try{
LoginUser user = redisCache.getCacheObject(key);
if(user.getUsername().equals(username)){
redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + user.getToken());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
java3、封装统一认证登录方法
方法:sysLoginService.authLogin(username);
/**
* 统一认证登录
*
* @param username 用户名
* @return 结果
*/
public String authLogin(String username)
{
LoginUser loginUser = new LoginUser();
SysUser sysUser = userService.selectUserByUserName(username);
try
{
//自定义封装
loginUser.setUserId(sysUser.getUserId());
loginUser.setDeptId(sysUser.getDeptId());
loginUser.setUser(userService.selectUserByUserName(sysUser.getUserName()));
loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
else
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
}
finally
{
AuthenticationContextHolder.clearContext();
}
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createToken(loginUser);
}
java4、拦截器调整
位置:com.detn.framework.config.SecurityConfig
新增:"/open/api/zhHaAuthLogin/**"
httpSecurity
// CSRF禁用,因为不使用session
.csrf().disable()
// 禁用HTTP响应标头
.headers().cacheControl().disable().and()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/register", "/captchaImage","/open/api/zhHaAuthLogin/**").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
// 添加Logout filter
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
java前端
新增JS
名称:api/authLogin.js
import request from '@/utils/request'
export function getAuthLoginUrl() {
return request({
url: '/open/api/zhHaAuthLogin/getAuthLoginUrl',
headers: {
isToken: false
},
method: 'post'
})
}
JavaScript登录页面改造
注意:请备份原页面后再调整
页面:/login.vue
<template>
<div class="login">
</div>
</template>
<script>
import { getAuthLoginUrl } from "@/api/authLogin";
export default {
name: "Login",
data() {
return {
};
},
created() {
this.getUrl()
},
methods: {
getUrl() {
getAuthLoginUrl().then(res => {
if (res.data && res.data) {
window.location.href = res.data
}
})
},
}
};
</script>
html新增认证登录页面
文件名称:/authLogin.vue
<template>
<div></div>
</template>
<script>
export default {
name: "AuthLogin",
data() {
return {
loading: false,
redirect: "/index"
};
},
created() {
// 页面加载完成后,立即执行自动登录
this.autoLoginByUrlToken();
},
methods: {
/**
* 核心逻辑:从URL获取token(加密数据),自动完成登录
*/
autoLoginByUrlToken() {
const urlParams = new URLSearchParams(window.location.search);
const encryptToken = urlParams.get("token");
if (!encryptToken) {
this.$message.error("未检测到登录凭证,请从认证系统跳转");
this.$router.push("/login");
return;
}
this.$store.dispatch("authLogin", encryptToken).then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {});
}
}
};
</script>
html新增 authLogin 登录方法
位置:store/modules/user.js
顶部添加 authLogin方法 import { login, logout, getInfo, authLogin } from '@/api/login' // 登录 authLogin({ commit }, token) { return new Promise((resolve, reject) => { authLogin(token).then(res => { setToken(res.token) commit('SET_TOKEN', res.token) resolve() }).catch(error => { reject(error) }) }) },
html
位置:api/login.js
// 登录方法 export function authLogin(token) { const data = { token } return request({ url: '/open/api/zhHaAuthLogin/authLogin', headers: { isToken: false }, method: 'post', data: data }) }
html
新增 authLogin 路由
位置:router/index.js
{ path: '/authLogin', component: () => import('@/views/authLogin'), hidden: true },
新增 authLogin 页面白名单
位置:/permission.js
const whiteList = ['/login', '/authLogin', '/auth-redirect','/register']
智慧互安认证平台
应用配置说明
1、应用名称
统一认证页面会显示应用名称,一般填写自己的系统名称
2、认证类型
默认不选则自动会是密码认证(密码认证不管选不选都会有),其余认证需要购买资源包才可以使用。
3、认证策略
单点登录功能,如无特殊需求,选择不限制即可
4、登录方式
1、账号映射:需要提前配置好映射账号才能进行登录
2、账号透传:账号直接透传给接入系统,如果有验证码是由认证平台进行发送和验证,只会透传账号和密码,或者邮箱手机号等,不会传入验证码,接入平台可以根据传入信息自动注册账号并实现自动登录。
5、登录地址
该登录地址是指接入系统的登录跳转页面地址,比如上方的 authLogin.vue 页面,访问地址如:http://localhost/authLogin
6、注销地址
该地址填写注销接口地址,请求方式:POST JSON ,一般认证平台用于认证策略时调用,地址:http://localhost/open/api/zhHaAuthLogin/authLogOut
注意
1、登录地址有效期为1分钟,超时需要重新登录
👇🏼👇🏼👇🏼扫描关注微信订阅号 或者 扫码加入交流群 了解更多技术分享,工作室官网:www.uvstu.com
| 微信订阅号 | QQ交流群 |
![]() |
![]() |

