记一次系统单点登录、模拟web系统登录方式的开发过程,使用AES加密
1.系统原始登录方式
访问登录页
输入账号密码登录后
2.从登录页找进去,从代码层面查看系统登录逻辑
常规登录方式为前端ajax请求LoginService服务-->返回200则跳转到home系统首页
查看LoginService登录逻辑
后台获取ajax传递的信息-->比较验证码-->查询用户表-->如果存在用户-->比较密码-->如果密码正确-->登录信息存入session
3. 单点登录设计
因为单点登录的用户为第三方系统用户,我这边的系统是不存在的该用户的,正常的创建用户的流程
维护机构角色-->维护用户-->维护用户所属角色
单点登录的用户我不可能说让他们把需要把用户预先把我这边的系统创建好,并分配角色
对于我这边系统没注册的用户我需要后台代码实现注册用户,绑定角色
我这边的系统为多机构版本,首先单点登录的机构我预先维护好一个角色,然后新用户进来我后台实现注册用户,绑定角色
约定第三方单点登录需要的信息
拼接好以下字符串
userCode=huuweq&userName=张三1号&orgId=65
使用AES算法加密拼接好的字符串,加密完成后进行URL编码生成userToken
4.代码记录
4.1 SsoAction.java
package bsoft.wsyyPlatform.action;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import bsoft.lis.base.Service;
import bsoft.lis.login.LoginService;
import bsoft.lis.tools.MD5;
import bsoft.wsyyPlatform.service.SsoService;
import bsoft.wsyyPlatform.utils.AESUtil;
import bsoft.wsyyPlatform.utils.SimpleQueryHelper;
/**
*
* @author gaom
* 2021-09-13
* 武汉东西湖卫生局需求 单点登录入口
*
*/
@Controller
public class SsoAction {
public static final Logger logger=LoggerFactory.getLogger(SsoAction.class);
@Autowired
public SsoService ssoService;
//调用登录服务加签使用的key
public static final String invoke_loginService_key="B8736EF7AD0A233FB5E0C7D8A2EEBEE6";
//单点登录aes加密key
public static final String sso_key="ffbbfcd692e84d6b82af1b5c0e6f5446";
public static final Set<String> sso_orglist=new HashSet<String>();
static{
//开放单点登录的机构
String[] sso_ary=new String[]{"65","66","67","68","69","70","71","72","73","74","75","76"};
for(String sso_str:sso_ary){
sso_orglist.add(sso_str);
}
}
@RequestMapping(value="ssoAction_ssoLogin.do")
public void ssoAction_ssoLogin(HttpServletRequest request,HttpServletResponse response){
//String logonName=request.getParameter("logonName");
//String orgId=request.getParameter("orgId");
//用户token
SimpleQueryHelper sh=new SimpleQueryHelper();
String userToken=request.getParameter("userToken");
response.setContentType("text/html;charset=UTF-8");
Map<String,Object> reqMap=new HashMap<String,Object>();
Map<String,Object> resMap=new HashMap<String,Object>();
try{
userToken=request.getParameter("userToken");
if(userToken==null||userToken.length()==0){
resMap.put("code", 301);
resMap.put("message", "error,userToken must not be empty!");
sh.outputObject(response, resMap);
return;
}
logger.info("ssoAction_ssoLogin userToken:"+userToken);
//解密
String userMessage=AESUtil.decrypt(userToken,sso_key);
logger.info("ssoAction_ssoLogin userToken:"+userToken+"--> userMessage:"+userMessage);
//String userMessage="userCode=huuweq&userName=张三1号&orgId=65";
String[] userMessageAry= userMessage.split("&");
String userCode=(userMessageAry[0].split("="))[1];
String userName=(userMessageAry[1].split("="))[1];
String orgId=(userMessageAry[2].split("="))[1];
if(!sso_orglist.contains(orgId)){
resMap.put("code", 301);
resMap.put("message", "error,orgId:"+orgId+" not support SSO!");
sh.outputObject(response, resMap);
return;
}
reqMap.put("userCode", userCode);
reqMap.put("userName", userName);
reqMap.put("orgId", orgId);
//单点登录检查用户和角色 不存在则新建
ssoService.ssoCheck(reqMap, resMap);
logger.info("ssoService.ssoCheck resMap:"+resMap.toString());
int code=Integer.parseInt(resMap.get("code")+"");
if(code!=200){
//用户角色检查失败
sh.outputObject(response, resMap);
return;
}
//用户 角色检查好后 执行单点登录
ssoService.ssoLogin(reqMap, resMap);
code=Integer.parseInt(resMap.get("code")+"");
if(code!=200){
//用户角色检查失败
sh.outputObject(response, resMap);
return;
}
//调用系统登录服务
HashMap<String, Object> resMap_loginService = ssoService.invokeLoginServoce(
request, response, userCode);
String code_service=resMap_loginService.get(Service.RES_CODE)+"";
String message_service=resMap_loginService.get(Service.RES_MESSAGE)+"";
logger.info("ssoAction_ssoLogin loginService.execute--> code_service:"+code_service+" message_service:"+message_service);
if(code_service.length()>0&&(!"null".equals(code_service))){
//报错
resMap.put("code", 301);
resMap.put("message", "ssoAction_ssoLogin loginService.execute ");
sh.outputObject(response, resMap);
return;
}
//调用LoginService登录方法成功后,会生成用户session,直接重定向到系统首页即可
response.sendRedirect("/wsyyPlatform/NewFrame/home.html");
}catch(Exception e){
e.printStackTrace();
sh.outputException(response, "SSO exception", e);
logger.error("ssoAction_checkLogin userToken:"+userToken+" Exception :"+e.getMessage());
}
}
public static void main(String[] args) {
String userMessage="userCode=zqjwsy&userName=张三1号&orgId=71";
try {
String encryptStr = AESUtil.encrypt(userMessage, sso_key);
String encrypt_encodeStr= URLEncoder.encode(encryptStr);
System.out.println("AES加密:"+encryptStr);
System.out.println("URL编码:"+encrypt_encodeStr);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4.2 SsoService.java
package bsoft.wsyyPlatform.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import bsoft.lis.login.LoginService;
import bsoft.lis.tools.MD5;
import bsoft.wsyyPlatform.action.SsoAction;
import bsoft.wsyyPlatform.dao.SsoDao;
/**
* 单点登录服务
* @author gaom
* 2021-09-13
*/
@Service
public class SsoService {
@Autowired
public SsoDao ssoDao;
/**
* 单点登录检查用户角色服务
* 新用户则创建 不存在角色则创建角色
* @param reqMap
* @param resMap
*/
public void ssoCheck(Map<String,Object> reqMap,Map<String,Object> resMap){
String userCode=reqMap.get("userCode")+"";
String userName=reqMap.get("userName")+"";
String orgId=reqMap.get("orgId")+"";
//获取机构角色
List<String> orgRoleList=ssoDao.getOrgRole(orgId);
if(orgRoleList.size()==0){
resMap.put("code", 301);
resMap.put("message", "单点登录失败,reqMap:"+reqMap.toString()+" 没有找到机构初始角色");
return;
}
String orgRoleId=orgRoleList.get(0);
List<Map<String,Object>> userList=ssoDao.getUserList(userCode);
if(userList.size()==0){
//初次登录 初始化用户
String yhid=ssoDao.createUser(userCode, userName, orgId);
//初始化角色
ssoDao.createUserRole(yhid, orgId, orgRoleId);
resMap.put("code", 200);
resMap.put("message", "单点登录用户认证成功,新用户,已创建用户和角色,reqMap:"+reqMap.toString());
return;
}
//已有用户
Map<String,Object> userMap=userList.get(0);
String userMap_jgid=userMap.get("JGID")+"";
if(!userMap_jgid.equals(orgId)){
resMap.put("code", 301);
resMap.put("message", "单点登录失败,reqMap:"+reqMap.toString()+" 单点登录的用户所属机构和用户已绑定机构冲突 单点登录机构:"+orgId+" 用户已绑定机构:"+userMap_jgid);
return;
}
//机构一致 验证是否有角色
String userMap_yhid=userMap.get("YHID")+"";
List<Map<String,Object>> userRoleList=ssoDao.getUserRole(userMap_yhid);
if(userRoleList.size()==0){
//没角色
//维护角色
ssoDao.createUserRole(userMap_yhid, orgId, orgRoleId);
resMap.put("code", 200);
resMap.put("message", "单点登录用户认证成功,已存在用户,没角色,已创建角色,reqMap:"+reqMap.toString());
return;
}
resMap.put("code", 200);
resMap.put("message", "单点登录用户认证成功,已存在用户,已存在角色,reqMap:"+reqMap.toString());
return;
}
/**
* 单点登录服务
* 用户角色检查完成后 执行登录操作
* @param reqMap
* @param resMap
*/
public void ssoLogin(Map<String,Object> reqMap,Map<String,Object> resMap){
String userCode=reqMap.get("userCode")+"";
String userName=reqMap.get("userName")+"";
String orgId=reqMap.get("orgId")+"";
List<Map<String,Object>> userList=ssoDao.getUserList(userCode);
if(userList.size()==0){
//异常 理论上验证用户通过 不会出现 查不到用户
resMap.put("code", 301);
resMap.put("message", "单点登录失败,reqMap:"+reqMap.toString()+" 用户ssoLogin登录失败,无效的用户名!");
return;
}
resMap.put("code", 200);
resMap.put("userMap", userList.get(0));
}
/**
* 调用系统登录服务
* @param request
* @param response
* @param userCode
* @return
*/
public HashMap<String, Object> invokeLoginServoce(
HttpServletRequest request, HttpServletResponse response,
String userCode) {
LoginService loginService= bsoft.wsyyManager.utils.AppContextHolder.getBean("LoginService", LoginService.class);
HashMap<String, Object> reqMap_loginService=new HashMap();
HashMap<String, Object> resMap_loginService=new HashMap();
reqMap_loginService.put("userid", userCode);
reqMap_loginService.put("checkSource", "SSO");//登录来源 SSO为单点登录
String invoke_loginService_sign=MD5.getMD5(userCode+SsoAction.invoke_loginService_key).toUpperCase();//签名
reqMap_loginService.put("invoke_loginService_sign", invoke_loginService_sign);
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext());
loginService.initServerlet(request, response, webApplicationContext);
loginService.execute(reqMap_loginService,resMap_loginService);
return resMap_loginService;
}
}
4.3 SsoDao.java
package bsoft.wsyyPlatform.dao;
import java.util.List;
import java.util.Map;
import org.hibernate.Session;
import org.hibernate.transform.Transformers;
import org.springframework.stereotype.Repository;
import bsoft.wsyyPlatform.utils.DaoUtil;
/**
* 单点登录Dao层
* @author gaom
* 2021-09-13
*/
@Repository
public class SsoDao extends DaoUtil{
/**
* 根据用户代码获取用户信息
* @param userCode
* @return 用户信息
*/
@SuppressWarnings("unchecked")
public List<Map<String,Object>> getUserList(String userCode){
Session session=null;
try{
session=getMySessionFactory_tjxt().openSession();
List<Map<String,Object>> userList=session.createSQLQuery(" select * from yypt_xtyhb where YHDM=:YHDM ")
.setParameter("YHDM", userCode)
.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP)
.list();
return userList;
}finally{
if(session!=null){
session.close();
}
}
}
/**
* 根据机构ID获取该机构已维护的角色
* @param orgId
* @return 机构已维护好的角色
*/
@SuppressWarnings("unchecked")
public List<String> getOrgRole(String orgId){
Session session=null;
try{
session=getMySessionFactory_tjxt().openSession();
//取每个机构新建的第一个角色 第一个角色一般为医院管理员
List<String> orgRoleList=session.createSQLQuery(" select cast(ROLEID as char) from pt_role where JGID=:JGID order by ROLEID asc limit 0,1 ")
.setParameter("JGID", orgId)
.list();
return orgRoleList;
}finally{
if(session!=null){
session.close();
}
}
}
/**
* 根据用户ID获取用户是否已维护角色
* @param userCode
* @return 用户角色
*/
@SuppressWarnings("unchecked")
public List<Map<String,Object>> getUserRole(String yhid){
Session session=null;
try{
session=getMySessionFactory_tjxt().openSession();
List<Map<String,Object>> userRoleList=session.createSQLQuery(" select * from PT_USERROLE where YHID=:YHID ")
.setParameter("YHID", yhid)
.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP)
.list();
return userRoleList;
}finally{
if(session!=null){
session.close();
}
}
}
/**
* 首次单点登录 需创建用户
* @param userCode
* @param userName
* @param orgId
* @return
*/
@SuppressWarnings("unchecked")
public String createUser(String userCode,String userName,String orgId){
Session session=null;
try{
session=getMySessionFactory_tjxt().openSession();
int thenUserId=1;
Object o=session.createSQLQuery(" select max(CAST(yhid as SIGNED )) from yypt_xtyhb ").uniqueResult();
if(o!=null){
thenUserId=Integer.parseInt(o+"")+1;
}
String createUserSql="insert into YYPT_XTYHB(YHID,YHDM,YHXM,YHMM,ZXBZ,PYJM,JGID)" +
"values" +
"(:YHID,:YHDM,:YHXM,:YHMM,:ZXBZ,:PYJM,:JGID)";
session.createSQLQuery(createUserSql)
.setParameter("YHID", thenUserId)
.setParameter("YHDM", userCode)
.setParameter("YHXM", userName)
//密码明文 ghyu531 单点注册的用户统一使用该密码
.setParameter("YHMM", "86417ee52e1928e952ec701f98452ae2")
.setParameter("ZXBZ", "0")
.setParameter("PYJM", "")
.setParameter("JGID", orgId)
.executeUpdate();
return thenUserId+"";
}finally{
if(session!=null){
session.close();
}
}
}
/**
* 创建用户对应的角色
* @param yhid
* @param orgId
* @param orgRoleId
* @return
*/
@SuppressWarnings("unchecked")
public int createUserRole(String yhid,String orgId,String orgRoleId){
Session session=null;
try{
session=getMySessionFactory_tjxt().openSession();
int thenTid=1;
Object o=session.createSQLQuery(" select max(tid) from PT_USERROLE ").uniqueResult();
if(o!=null){
thenTid=Integer.parseInt(o+"")+1;
}
String createUserSql=" insert into PT_USERROLE(TID,YHID,JGID,ROLEID)values(:TID,:YHID,:JGID,:ROLEID) ";
int execRtn=session.createSQLQuery(createUserSql)
.setParameter("TID", thenTid)
.setParameter("YHID", yhid)
.setParameter("JGID", orgId)
.setParameter("ROLEID", orgRoleId)
.executeUpdate();
return execRtn;
}finally{
if(session!=null){
session.close();
}
}
}
}
4.4 AESUtil.java
package bsoft.wsyyPlatform.utils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.security.SecureRandom;
/**
* description:AES 对称可逆解密算法
* AEs单钥 加密效率高 hash算法 不可以用于签名
* RSA可以用于签名 而且RSA签名算法也是双钥 常用的签名算法还有 MD5 签名 因为MD5是不可逆加密
* date: 2021/8/5
* author: gaom
* version: 1.0
*/
public class AESUtil {
private static final int BYTE_LENGTH = 128;
private static final String $UTF8 = "UTF-8";
public static void main(String[] args) {
String userMessage="userCode=huuweq&userName=张三1号&orgId=65";
try {
String encryptStr = AESUtil.encrypt(userMessage, "ffbbfcd692e84d6b82af1b5c0e6f5446");
String encrypt_encodeStr= URLEncoder.encode(encryptStr);
System.out.println("AES加密:"+encryptStr);
System.out.println("URL编码:"+encrypt_encodeStr);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* AES加密
* AES为对称加密算法 及加密和解密的 key一样 和RSA非对称加密各有优缺点
* AES单钥 加密效率高 hash算法 不可以用于签名
* RSA双钥 加密效率低,除了加密 还有签名功能
* @param content 需要加密的明文
* @param key 钥匙
* @return
* @throws Exception
*/
public static String encrypt(String content, String key) throws Exception {
try {
byte[] contentByte=content.getBytes($UTF8);
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes());
keyGenerator.init(BYTE_LENGTH, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
byte[] encodeFormat = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(encodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
// 正式执行加密操作
byte[] encryptByte=cipher.doFinal(contentByte);
//二进制不方便web传输 转为base64编码方便传输
String base64EncryptData=org.apache.commons.codec.binary.Base64.encodeBase64String(encryptByte);
return base64EncryptData;
} catch (Exception e) {
System.out.println("encrypt异常 :"+e.getMessage());
throw new Exception(e);
}
}
/**
*AES解密
* @param base64Content 已经base64编码的密文 加密默认返回byte 转为base64方便web传输
* @param key 钥匙
* @return
* @throws Exception
*/
public static String decrypt(String base64Content, String key) throws Exception {
try {
//base64 解码
byte[] contentByte=org.apache.commons.codec.binary.Base64.decodeBase64(base64Content);
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes());
kgen.init(BYTE_LENGTH, secureRandom);
SecretKey secretKey = kgen.generateKey();
byte[] encodeFormat = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(encodeFormat, "AES");
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance("AES");
// 用密匙初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
// 正式执行解密操作
byte[] decryptByte=cipher.doFinal(contentByte);
//返回byte转为string
String decryptData=new String(decryptByte,$UTF8);
return decryptData;
} catch (Exception e) {
System.out.println("decrypt异常 :"+e.getMessage());
throw new Exception(e);
}
}
}
4.5 LoginService.java修改,新增支持单点登录
package bsoft.lis.login;
import javax.servlet.http.HttpSession;
import com.oa.output.*;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import bsoft.lis.base.*;
import bsoft.lis.tools.*;
import bsoft.wsyyPlatform.action.SsoAction;
import com.oa.base.*;
public class LoginService extends BaseBean {
@Override
public void execute(HashMap<String, Object> req, HashMap<String, Object> res) {
try {
super.execute(req, res);
String userid2 = "";
String UnitCode2 = "";
String userid = (String) req.get("userid");// 用户名
String password = (String) req.get("password");// 密码
//2021-9-22 gaom add 增加支持单点登录的逻辑
String checkSource = req.get("checkSource")+"";// 登录来源 SSO为单点登录
String invoke_loginService_sign = req.get("invoke_loginService_sign")+"";// 签名
String sign_this=MD5.getMD5(userid+SsoAction.invoke_loginService_key).toUpperCase();
String validation = req.get("validation")+"";// 是否验证码
//2021-9-22 单点登录
if("SSO".equals(checkSource)){
//单点登录需要签名认证,只有SsoAction单点登录入口才有签名key,防止非法绕过SsoAction采用单点登录
if(!invoke_loginService_sign.equals(sign_this)){
res.put(Service.RES_CODE, 400);
res.put(Service.RES_MESSAGE, "SSO无效的签名!");
return;
}
//单点登录不需要图片验证码
validation="false";
}
String UnitCode = "";
UnitCode = (String) req.get("UnitCode");// 机构或单位编号
String sVerifyCode = (String) req.get("sVerifyCode");// 验证码
String VerifyCode2 = "";
String ip = this.getHttpServletRequest().getLocalAddr();
VerifyCode2 = RandomValidateCode.RANDOMCODEKEYMap.get(ip)
+ "";
if (validation.equalsIgnoreCase("true")) {
Object lo = this.getSession().getAttribute("RANDOMVALIDATECODEKEY");
String VerifyCode = "";
if (lo == null){
VerifyCode = "";
}
else{
VerifyCode = lo.toString();
}
if (!sVerifyCode.equalsIgnoreCase(VerifyCode)) {
VerifyCode2=VerifyCode2.toUpperCase();
sVerifyCode=sVerifyCode.toUpperCase();
if (!VerifyCode2.equals(sVerifyCode)) {
//if (VerifyCode2.indexOf(sVerifyCode) < 0) {
res.put(Service.RES_CODE, 400);
res.put(Service.RES_MESSAGE, "验证码不正确!");
return;
}
}
}
this.ConnectLisDB();
User lUser = new User();
int il_ret = lUser.of_login(checkSource,userid, password, UnitCode, this);
if (il_ret < 0) {
res.put(Service.RES_CODE, 400);
res.put(Service.RES_MESSAGE, lUser.returnmsg);
return;
}
} catch (Exception e) {
logger.info(ErrorMsg.info(e));
res.put(Service.RES_CODE, 400);
res.put(Service.RES_MESSAGE, ErrorMsg.info(e));
return;
}
finally
{
this.CloseDB();
}
}
}
5.最终的效果
访问单点登录URL
userToken生成方式见 SsoAction的main方法
http://127.0.0.1:8085/wsyyPlatform/ssoAction_ssoLogin.do?userToken=6U7VW97GmfeJU0alQkUBoaHm25fLLlpMhw2gtjIS8Dvd%2Fyf5UDJu%2F13q8uphEq0V
单点登录成功重定向到首页