首先,在搞代码之前,我们要了解什么是JWT?
这个一开始我也不太明白,后来经过百度阅读看了很多文章才明白,其实JWT就像一个工具,可以对字符串进行签名(加密)和解签(解密)以及验证字符串。这里只是简单的对JWT主要功能描述一下就行,更具体的了解JWT需要自行百度。
第二:Token为什么要存放在Redis?存储格式又是怎么样的?
当用户登录成功时,将Token放在Redis,同时设置该Redis key的过期时间 = JWT Token过期时间,那么等这个存放某Token的key过期之后,Redis会自动删除这个Redis key(那我就不用刻意的维护Token)。
第三:SSO登陆逻辑(这是根据自己的实际项目绘制的逻辑图,若有问题勿喷。嘿嘿!)
SSO注销登录逻辑:
效果展示:
1.子系统A点击登录:
SSO验证登录成功后,将token存到Redis,如图示例:
然后SSO使用ajax(jsonp)发送命令给所有子系统,让他们添加token到本地Cookie中,然后重定向回子系统
如图:登陆成功
查看Cookie有没有Token?
子系统A(10.1.8.12)
子系统B(10.1.0.192);
以上是在子系统A(10.1.8.12)上完成了登录成功,根据SSO的定义,那么,我访问子系统B(10.1.0.192)就应该是免登录的,接下来验证一下,访问子系统B(10.1.0.192)
可以看到,在子系统A上登录之后,在子系统B上就免登陆了,也就是成功实现了跨域SSO单点登陆
功能演示到此完毕。接下来看看我是如何实现SSO跨域单点登录的。
1.子系统访问受保护资源,子系统拦截器拦截请求,如下:
public class LoginFilter implements Filter {
private final Gson gson = new Gson();
String NBC_SSO_LOGIN_URL = PropertiesUtils.getProperty("NBC_SSO_SERVER_LOGIN_URL");
String TokenLogin_HttpURL = PropertiesUtils.getProperty("NBC_SSO_SERVER_HTTPURL_FOR_TOKEN_CHECK");
String NBC_USER_TOKEN = PropertiesUtils.getProperty("NBC_SSO_CLIENT_COOKIENAME_FOR_TOKEN");
String NBC_SSO_CLIENT_COOKIE_TIMEOUT = PropertiesUtils.getProperty("NBC_SSO_CLIENT_COOKIE_TIMEOUT");
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
System.out.println("============ doFilter LoginFilter ============");
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HttpSession session = request.getSession();
ServletContext application = session.getServletContext();
boolean IsLogin = false;
Object UID = session.getAttribute("UID");
if(UID!=null && application.getAttribute(UID.toString())!=null && application.getAttribute(UID.toString()).equals(session.getId())) {
IsLogin = true;
}
if(UID!=null) {
System.err.println("=============application uid:"+application.getAttribute(UID.toString()));
}
System.err.println("IsLogin:"+IsLogin);
//已登陆则放行
if(IsLogin) {
chain.doFilter(request, response);
}else {
//校验配置文件
boolean error = false;
StringBuffer errorInfo = new StringBuffer("");;
String errorURL = request.getContextPath() +"/error/commonError";
String[] arr1 = new String[] {NBC_USER_TOKEN,NBC_SSO_LOGIN_URL,NBC_SSO_CLIENT_COOKIE_TIMEOUT};
String[] arr2 = new String[] {"NBC_SSO_CLIENT_COOKIENAME_FOR_TOKEN","NBC_SSO_SERVER_LOGIN_URL","NBC_SSO_CLIENT_COOKIE_TIMEOUT"};
for(int i=0;i0) {
ssoLoginUrl = NBC_SSO_LOGIN_URL+"&returnRUL="+returnRUL;
}else {
ssoLoginUrl = NBC_SSO_LOGIN_URL+"?returnRUL="+returnRUL;
}
}
}catch(Exception e) {
e.printStackTrace();
}
System.err.println("登陆拦截");
//若请求中有token参数,则校验token有效性
String token = req.getParameter("token");
if(token!=null && !token.equals("")) {
SSOCheckResult checkResult = SsoCheck.checkToken(token, TokenLogin_HttpURL, request, response);
System.err.println("tokenOK 0 ?"+checkResult.isSuccess());
System.err.println(gson.toJson(checkResult));
if(checkResult.isSuccess()) {
session.setAttribute("IsLogin",true);
NBC_ID_UsersVO user = checkResult.getUser();
if(user!=null) {
System.err.println("校验Cookie token,token有效,返回用户信息userInfo:"+gson.toJson(user));
String fuserId = String.valueOf(user.getFuserID());
session.setAttribute("UID", fuserId);
session.setAttribute("UserName",user.getUserName_CHS() );
application.setAttribute(fuserId, session.getId());
}
chain.doFilter(request, response);
}else {
CookieUtils.clearCookie(NBC_USER_TOKEN, request, response);
response.sendRedirect(ssoLoginUrl);
}
}else {
SSOCheckResult checkResult = SsoCheck.checkToken(null, TokenLogin_HttpURL, request, response);
System.err.println("tokenOK 1 ?"+checkResult.isSuccess());
System.err.println(gson.toJson(checkResult));
if(checkResult.isSuccess()) {
token = checkResult.getToken();
session.setAttribute("IsLogin",true);
NBC_ID_UsersVO user = checkResult.getUser();
if(user!=null) {
System.err.println("校验Cookie token,token有效,返回用户信息userInfo:"+gson.toJson(user));
String fuserId = String.valueOf(user.getFuserID());
session.setAttribute("UID", fuserId);
session.setAttribute("UserName",user.getUserName_CHS() );
application.setAttribute(fuserId, session.getId());
}
// boolean existCookie = CookieUtils.existCookie(NBC_USER_TOKEN,request);
// if(!existCookie) {
// CookieUtils.addCookie(NBC_USER_TOKEN, token, Integer.parseInt(NBC_SSO_CLIENT_COOKIE_TIMEOUT) , request, response);
// }
chain.doFilter(request, response);
}else {
//clear cookie and redirect to login
response.sendRedirect(ssoLoginUrl);
}
}
}
}
@Override
public void destroy() {
System.out.println("============ Destroy Filter LoginFilter ============");
}
@Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("============ Init Filter LoginFilter ============");
}
}
以上拦截器中涉及到使用HttpURLConnection访问SSO校验Token有效性。SsoCheck.java
public class SsoCheck {
private static final Gson gson = new Gson();
private static String NBC_USER_TOKEN = PropertiesUtils.getProperty("NBC_SSO_CLIENT_COOKIENAME_FOR_TOKEN");
public static SSOCheckResult checkToken(String token, String httpURL,HttpServletRequest request, HttpServletResponse response) {
System.err.println("=============== SSOCheckResult checkToken ===============");
String clientIp = ComputerInfoUtil.getIP(request);
SSOCheckResult result = new SSOCheckResult(false,500,"未知错误",null,token);
//如果不存在token参数,则在Cookie中查找
if(token==null || token.equals("")) {
//Cookie中是否有Token
Cookie[] cookies = request.getCookies();
if(cookies!=null && cookies.length>0) {
for(Cookie cookie : cookies) {
if(cookie.getName().equals(NBC_USER_TOKEN)) {
token = cookie.getValue();
}
}
}
}
if(token==null || token.equals("")) {
return new SSOCheckResult(false,404,"参数错误[token]");
}
result.setToken(token);
String encoding = "UTF-8";
String returnURL = request.getRequestURL().toString();
try {
//创建URL对象
URL url = new URL(httpURL);
//创建连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// true -- will setting parameters
conn.setDoOutput(true);
// true--will allow read in from
conn.setDoInput(true);
// will not use caches
conn.setUseCaches(false);
// setting serialized
//conn.setRequestProperty("Content-type", "application/x-java-serialized-object");
conn.setRequestProperty("Content-type", "application/json; charset=UTF-8");
conn.setRequestProperty("Charsert", "UTF-8");
conn.setRequestMethod("POST");// default is GET
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Charsert", "UTF-8");
// 1 min
conn.setConnectTimeout(60000);
// 1 min
conn.setReadTimeout(60000);
//send args
conn.addRequestProperty("token",(token==null)?"":URLEncoder.encode( token, "UTF-8"));
conn.addRequestProperty("clientIp",(clientIp==null)?"":URLEncoder.encode( clientIp, "UTF-8"));
conn.addRequestProperty("returnURL",returnURL==null?"":URLEncoder.encode( returnURL, "UTF-8"));
// connect to server (tcp)
conn.connect();
//获得响应码
int code = conn.getResponseCode();//200代表Http OK
String TokenOK = (conn.getHeaderField("TokenOK")==null)?"":conn.getHeaderField("TokenOK");
String userInfo = (conn.getHeaderField("userInfo")==null)?"":conn.getHeaderField("userInfo");
result.setErrorCode(code);
if(code==HttpURLConnection.HTTP_OK && TokenOK.equals("true")){
userInfo = userInfo.equals("")?userInfo:URLDecoder.decode(userInfo, encoding);
userInfo = userInfo.replace("\"", "").replace("\\", "\"");
NBC_ID_UsersVO user = new NBC_ID_UsersVO();
if(!userInfo.equals("")) {
user = gson.fromJson(userInfo, NBC_ID_UsersVO.class);
}
result = new SSOCheckResult(true,0,"",user,token);
}
//关闭连接
conn.disconnect();
}catch(Exception e) {
e.printStackTrace();
result.setSuccess(false);
result.setErrorCode(500);
result.setErrorMsg("服务器异常:"+e.getMessage());
}
return result;
}
}
好了,子系统的逻辑已经处理好,接下来就差SSO端来校验子系统发出的Http校验Token请求了。
那么接下来,需要创建SSO认证中心项目。
创建springboot项目,pom.xml主要引入JWT、Jedis、HttpClient等依赖,以下是我的pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.0.BUILD-SNAPSHOT
war
com.nbc
com.nbc
0.0.1-SNAPSHOT
NBC_SSO
Spring Boot SSO Demo
1.8
UTF-8
5.23.0-RC1
1.9.4
2.8.2
0.9.5.2
2.7.5
5.1.0.Final
5.2.0.Final
5.2.0.Final
5.1.0.Final
1.0.1.Final
4.5.2
1.0.3
2.9.0
4.12
1.2
3.4.0
2.9.9
2.4
20190722
3.12
1.7
2.0.12
5.2.0.BUILD-SNAPSHOT
4.0
4.0.0
1.0.5
junit
junit
${junit.version}
test
com.google.code.gson
gson
${gson.version}
org.json
json
${org.json.version}
com.fasterxml.jackson.core
jackson-core
${jackson.version}
com.fasterxml.jackson.core
jackson-databind
${jackson.version}
net.sf.cssbox
pdf2dom
${pdf2dom.version}
org.apache.pdfbox
pdfbox
${pdfbox.version}
org.apache.pdfbox
pdfbox-tools
${pdfbox.version}
redis.clients
jedis
${jedis.version}
net.sf.json-lib
json-lib
${net.sf.json.version}
jdk15
com.auth0
java-jwt
${jwt.version}
org.apache.httpcomponents
httpclient
${httpclient.version}
org.projectlombok
lombok
true
com.microsoft.sqlserver
sqljdbc4
${sqlserver.version}
com.mchange
c3p0
${c3p0.version}
org.springframework
spring-orm
${spring.version}
org.hibernate
hibernate-core
${hibernate.version}
org.hibernate
hibernate-entitymanager
${hibernate.version}
org.hibernate
hibernate-osgi
${hibernate.version}
org.hibernate
hibernate-envers
${hibernate.version}
org.hibernate
hibernate-c3p0
${hibernate.version}
org.hibernate
hibernate-proxool
${hibernate.version}
org.hibernate
hibernate-infinispan
${hibernate.version}
org.hibernate
hibernate-ehcache
${hibernate.version}
javax.servlet
javax.servlet-api
${servlet.api.version}
provided
javax.servlet
jstl
${jstl.version}
org.apache.tomcat.embed
tomcat-embed-jasper
org.apache.tomcat
tomcat-jsp-api
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
nbs-sso
org.springframework.boot
spring-boot-maven-plugin
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
spring-snapshots
Spring Snapshots
https://repo.spring.io/snapshot
true
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
spring-snapshots
Spring Snapshots
https://repo.spring.io/snapshot
true
相关依赖自行拷贝到你的项目。
如何整合Springmvc和Hibernate这个我就不多说,自己搞(因为我的SSO已经在用了,只能展示部分核心代码)。当然,需要完整的SSO代码也可以联系我,但要有偿喔(Q:2225629171)
SSO登陆模块:
@RequestMapping(value="/login",method=RequestMethod.GET)
public String to_login(Model model,HttpServletRequest req,HttpSession session) {
System.err.println("================ SSO Login ==============");
String returnURL = "";
try {
returnURL = req.getParameter("returnURL");
if(returnURL!=null && !returnURL.equals("")) {
returnURL = URLDecoder.decode(returnURL, "UTF-8");
}
}catch(Exception e) {
e.printStackTrace();
}
System.err.println("returnURL:"+returnURL);
model.addAttribute("user",new z_CF_UsersVO());
model.addAttribute("returnURL",returnURL);
return "login";
}
@RequestMapping(value="/login",method=RequestMethod.POST,produces = "application/json; charset=utf-8")
@ResponseBody
public String do_login(Model model,HttpServletRequest req,HttpServletResponse resp,HttpSession session)
throws Exception {
JSONObject json = new JSONObject();
String sessionId = session.getId();
String ipv4 = ComputerInfoUtil.getIP(req);
String login_method = req.getParameter("login_method");
String username = req.getParameter("username");
String password = req.getParameter("password");
//1.校验参数
System.err.println("执行校验参数");
String[] params = new String[] {login_method,username,password};
boolean paramOK = loginService.checkParam(params);
if(!paramOK) {
json.put("success", false);
json.put("info", "参数错误:"+gson.toJson(params));
return json.toString();
}
//2.执行登陆校验
PublicModel result = loginService.login(sessionId,ipv4,login_method,username,password);
if(!result.isSuccess()) {
json.put("success", false);
json.put("info", "执行登陆校验时发生异常:"+result.getErrorMsg());
return json.toString();
}
LoginResult loginResult = result.getObj();
if(loginResult==null) {
json.put("success", false);
json.put("info", "执行登陆校验时发生异常:loginResult=null");
return json.toString();
}
System.err.println("loginResult.getCode():"+loginResult.getCode());
if(!String.valueOf(loginResult.getCode()).equals(String.valueOf(LoginCode.First_Login)) && !String.valueOf(loginResult.getCode()).equals(String.valueOf(LoginCode.Permit_Login))) {
System.err.println("========= 返回错误信息 ==========");
json.put("success", true);
json.put("loginResult", gson.toJson(loginResult));
return json.toString();
}
//登陆有效
//判断Redis服务是否已启动
System.err.println("查询Redis服务是否已启动");
boolean IsStarted = jedisClient.IsRedisStarted();
if(!IsStarted) {
json.put("success", false);
json.put("info", "Redis服务未启动,请及时联系管理员,谢谢您的配合!");
return json.toString();
}
System.err.println("Redis服务已启动");
//获取新的用户登陆信息
z_CF_UsersVO currentUser = new z_CF_UsersVO();//将部分信息封装返回
currentUser.setFuserID(loginResult.getFuserId());
currentUser.setUserName_EN(loginResult.getUserName_EN());
currentUser.setUserName_CHS(loginResult.getUserName_CHS());
Map claims = new HashMap();
claims.put("fuserId", String.valueOf(loginResult.getFuserId()));
claims.put("userInfo",gson.toJson(currentUser));
String newToken = JwtUtil.genToken(claims,Long.parseLong(NBC_SSO_SERVER_JWT_TOKEN_EXPIRE));
currentUser.setToken(newToken);
//注销该用户在其他地方的登录信息
System.err.println("注销该用户旧的登录信息");
String key = "";
String old_userInfo = "";
try {
if(currentUser!=null) {
key = NBC_SSO_SERVER_JEDIS_KEY_PREFIX+String.valueOf(currentUser.getFuserID());
if(key!=null && !key.equals("")) {
old_userInfo = jedisClient.get(key);
}
System.err.println("old_userInfo:"+old_userInfo);
if(old_userInfo!=null && !old_userInfo.equals("")) {
jedisClient.del(key);
}
}
}catch(Exception e) {
System.err.println("注销用户登陆信息时发生异常:"+e.getMessage());
e.printStackTrace();
}
//注册该用户最新的登陆信息
if (newToken != null && key!=null && !key.equals("")) {
jedisClient.set(key , gson.toJson(currentUser));
jedisClient.expire(key,Integer.parseInt(NBC_SSO_SERVER_JEDIS_KEY_TIMEOUT));
}
System.err.println("newToken:"+newToken);
json.put("success", true);
json.put("loginResult", gson.toJson(loginResult));
json.put("token", newToken);
System.err.println("返回数据:"+json.toString());
return json.toString();
}
SSO校验中心:(SsoCheckController.java)
@Controller
@RequestMapping("/ssocheck")
public class SsoCheckController {
private static final Gson gson = new Gson();
private static String NBC_SSO_SERVER_REGEX_OF_IP = PropertiesUtils.getProperty("NBC_SSO_SERVER_REGEX_OF_IP");
private static String NBC_SSO_SERVER_JEDIS_KEY_PREFIX = PropertiesUtils.getProperty("NBC_SSO_SERVER_JEDIS_KEY_PREFIX");
@Autowired
private JedisClient jedisClient;
@Autowired
private LogService logService;
@RequestMapping(value = "/tokencheck")
public void ssocheck(HttpServletRequest request, HttpServletResponse response,HttpSession session) {
System.err.println("=============== server tokencheck ===============");
String sessionId = session.getId();
String token = request.getHeader("token");
System.err.println("tokencheck token:"+token);
String clientIp = request.getHeader("clientIp");
String returnURL = request.getHeader("returnURL");
String errorURL = request.getContextPath()+"/error/commonError";
String loginURL = request.getContextPath()+"/login?returnURL="+returnURL;
String sonSysIp = ComputerInfoUtil.getIP(request);
JSONObject json = new JSONObject();
List event = new ArrayList();
event.add("客户端请求SSO认证中心校验Token");
try {
//returnURL = getDecoderString(returnURL,encoding);
if(token==null || token.equals("")) {
event.add("警告:token参数为空,服务器已重定向页面至登陆页面:"+loginURL);
logService.ssocheckToLog(sessionId,clientIp, false, event);
response.sendRedirect(loginURL);
return;
}
Pattern pattern = Pattern.compile(NBC_SSO_SERVER_REGEX_OF_IP);
if(clientIp==null || clientIp.equals("")) {
event.add("警告:clientIp参数为空,服务器已重定向页面至登陆页面:"+loginURL);
logService.ssocheckToLog(sessionId,clientIp, false, event);
response.sendRedirect(loginURL);
return;
}else if(!pattern.matcher(clientIp).matches()){
event.add("警告:clientIp参数非法,该参数源自子系统ipv4:"+sonSysIp);
event.add("服务器已重定向页面至登陆页面:"+loginURL);
logService.ssocheckToLog(sessionId,clientIp, false, event);
response.sendRedirect(loginURL);
return;
}
String encoding = "UTF-8";
token = getDecoderString(token,encoding);
event.add("请求参数Token:"+token);
boolean tokenOK = JwtUtil.checkToken(token);
System.err.println("校验Token:"+((tokenOK==true)?"有效":"无效"));
if(!tokenOK) {
event.add("警告:Token认证不通过,该客户端存在伪造Token的可能性,请及时做好应对措施。");
event.add("服务器已重定向页面至登陆页面:"+loginURL);
logService.ssocheckToLog(sessionId,clientIp, false, event);
response.sendRedirect(loginURL);
return;
}
String redis_userInfo = "";
String fuserId = JwtUtil.getClaim("fuserId", token);
if(fuserId!=null) {
String key = NBC_SSO_SERVER_JEDIS_KEY_PREFIX+AesUtil.decode(fuserId);
System.err.println("key:"+key);
redis_userInfo = jedisClient.get(key);
}
System.err.println("redis_userInfo:"+redis_userInfo);
if(redis_userInfo==null || redis_userInfo.equals("")) {
//token不存在于服务器redis
event.add("警告:Token不存在于服务器,服务器已重定向页面至登陆页面:"+loginURL);
logService.ssocheckToLog(sessionId,clientIp, false, event);
response.sendRedirect(loginURL);
return;
}
//token有效并且服务器有该用户登陆信息
//校验token和登陆信息的token是否一致
z_CF_UsersVO user = gson.fromJson(redis_userInfo, z_CF_UsersVO.class);
if(!token.equals(user.getToken())) {
event.add("token有效并且服务器有该用户登陆信息,但token和服务器的token不一致(可能是该用户已在其他地方登陆)");
logService.ssocheckToLog(sessionId,clientIp, false, event);
response.sendRedirect(loginURL);
return;
}
String TokenOK1 = "true";
String userInfo = AesUtil.decode(JwtUtil.getClaim("userInfo", token));
String userInfo1 = (userInfo==null)?"":URLEncoder.encode(gson.toJson(userInfo), encoding);
json.put("TokenOK", TokenOK1);
json.put("userInfo", userInfo);
event.add("Token认证通过,即将返回信息给客户端:"+json.toString());
logService.ssocheckToLog(sessionId,clientIp, true, event);
response.setHeader("TokenOK", TokenOK1);
response.setHeader("userInfo",userInfo1);
}catch(Exception e) {
try {
String info = "SSO认证中心校验Token时发生异常:"+e.getMessage();
//checktoken记录日志
event = new ArrayList();
event.add(info);
event.add("服务器已重定向页面至错误页面:"+errorURL);
logService.ssocheckToLog(sessionId,clientIp, false, event);
request.getSession().setAttribute("error", info);
response.sendRedirect(errorURL);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
}
}
private String getEncoderString(String str,String encoding) {
String result = "";
try {
if(str!=null && !str.equals("") && encoding!=null && !encoding.equals("")) {
result = URLEncoder.encode( str, encoding);
}
}catch(Exception e) {
e.printStackTrace();
}
return result;
}
private String getDecoderString(String str,String encoding) {
String result = "";
try {
if(str!=null && !str.equals("") && encoding!=null && !encoding.equals("")) {
result = URLDecoder.decode( str, encoding);
}
}catch(Exception e) {
e.printStackTrace();
}
return result;
}
}
JWT工具类:
@Slf4j
public class JwtUtil {
// public static void main(String[] args) {
// long expire = 1000 * 60;
// Map map = new HashMap();
// map.put("fuserId", "2");
// map.put("username", "admin");
// String token = genToken(map,expire);
// System.err.println(token);
// Map claims = getClaims(token);
// for(String key : claims.keySet()) {
// //System.err.println(key);
// //System.err.println(key+":"+AesUtil.decode(claims.get(key)));
// }
// //String claim = getClaim("fuserId",token);
// //System.err.println(AesUtil.decode(claim));
//
// }
private static final Gson gson = new Gson();
//密钥
private static String SECRET = readSecret();;
//发行人
private static String ISSUER = "NBC_USER";
public static String getClaim(String claimName, String token) {
String[] params = new String[] {claimName,token};
for(String param : params) {
if(param==null || param.equals("")) {
return null;
}
}
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
//解密
JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
DecodedJWT jwt = verifier.verify(token);
Map map = jwt.getClaims();
Map resultMap = new HashMap<>();
map.forEach((k,v) -> resultMap.put(k, v.asString()));
return resultMap.get(claimName);
}catch(Exception e) {
e.printStackTrace();
}
return null;
}
public static Map getClaims(String token) {
if(token==null || token.equals("")) {
return null;
}
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
//解密
JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
DecodedJWT jwt = verifier.verify(token);
Map map = jwt.getClaims();
Map resultMap = new HashMap<>();
map.forEach((k,v) -> resultMap.put(k,v.asString()));
return resultMap;
}catch(Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 生成jwt
*/
public static String genToken(Map claims,long expire) {
try {
//过期时间
Date expireDate = new Date(System.currentTimeMillis() + expire);
//使用HMAC256进行加密
Algorithm algorithm = Algorithm.HMAC256(SECRET);
//创建jwt
JWTCreator.Builder builder = JWT.create()
.withIssuer(ISSUER)//发行人
.withExpiresAt(expireDate); //过期时间点
//传入参数
if(claims!=null) {
claims.forEach((key,value)-> {
String encodeValue = AesUtil.encode(value);
builder.withClaim(key, encodeValue);
});
}
//签名加密
return builder.sign(algorithm);
} catch (Exception e) {
//log.info("jwt 生成问题");
}
return null;
}
/**
* 解密jwt并验证是否正确
*/
public static boolean checkToken (String token) {
if(token==null || token.equals("")) {
return false;
}
try {
//使用hmac256加密算法
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
.withIssuer(ISSUER)
.build();
DecodedJWT decodedJWT = verifier.verify(token);
//获得token的头部,载荷和签名,只对比头部和载荷
String [] headPayload = token.split("\\.");
//获得jwt解密后头部
String header = decodedJWT.getHeader();
//获得jwt解密后载荷
String payload = decodedJWT.getPayload();
return (header.equals(headPayload[0]) && payload.equals(headPayload[1]));
} catch (Exception e) {
//log.info("jwt解密出现错误,jwt或私钥或签证人不正确");
e.printStackTrace();
}
return false;
}
/**
* 读取密钥
* @return 密钥信息
*/
private static String readSecret() {
String secret = "";
try {
Properties properties = new Properties();
InputStream inputStream = ClassLoader.getSystemResourceAsStream("jwt.properties");//加载resource目录下的配置文件
properties.load(inputStream);
secret = properties.getProperty("JWT_SECRET");
}catch(Exception e) {
e.printStackTrace();
}
return secret;
}
}
AesUtil.java:
@Slf4j
public class AesUtil {
//加密密钥
private static String SECRET = readSecret();;
/**
* 加密
* @return 加密后内容
*/
public static String encode (String content) {
Key key = getKey();
byte[] result = null;
try{
//创建密码器
Cipher cipher = Cipher.getInstance("AES");
//初始化为加密模式
cipher.init(Cipher.ENCRYPT_MODE,key);
//加密
result = cipher.doFinal(content.getBytes("UTF-8"));
//将二进制转换成16进制
StringBuffer sb = new StringBuffer();
for (int i = 0; i < result.length; i++) {
String hex = Integer.toHexString(result[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
} catch (Exception e) {
//log.info("aes加密出错");
}
return null;
}
/**
* 解密
* @return 解密后内容
*/
public static String decode (String content) {
//将16进制转为二进制
if (content.length() < 1)
return null;
byte[] result = new byte[content.length()/2];
for (int i = 0;i< content.length()/2; i++) {
int high = Integer.parseInt(content.substring(i*2, i*2+1), 16);
int low = Integer.parseInt(content.substring(i*2+1, i*2+2), 16);
result[i] = (byte) (high * 16 + low);
}
Key key = getKey();
byte[] decrypt = null;
try{
//创建密码器
Cipher cipher = Cipher.getInstance("AES");
//初始化为解密模式
cipher.init(Cipher.DECRYPT_MODE,key);
//解密
decrypt = cipher.doFinal(result);
} catch (Exception e) {
//log.info("aes解密出错");
}
assert decrypt != null;
return new String(decrypt);
}
/**
* 根据私钥内容获得私钥
*/
private static Key getKey () {
SecretKey key = null;
try {
//创建密钥生成器
KeyGenerator generator = KeyGenerator.getInstance("AES");
//初始化密钥
generator.init(128,new SecureRandom(SECRET.getBytes()));
//生成密钥
key = generator.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return key;
}
/**
* 读取密钥
* @return 密钥信息
*/
public static String readSecret () {
String secret = "";
try {
Properties properties = new Properties();
//加载resource目录下的配置文件
InputStream inputStream = ClassLoader.getSystemResourceAsStream("aes.properties");
properties.load(inputStream);
secret = properties.getProperty("AES_SECRET");
} catch (IOException e) {
//log.info("读取密钥文件错误");
}
return secret;
}
}
SSO登录成功后,跳转至SSO登陆成功处理页面loginSuccess.jsp(ajax发送命令,让子系统添加token到Cookie后,重定向页面回子系统指定的LoginOK页面)
@RequestMapping(value="/loginSuccess",method=RequestMethod.GET)
public String loginSuccess(Model model,HttpServletRequest req,HttpSession session) {
String domains = "";
String returnURL = (req.getParameter("returnURL")==null)?"":req.getParameter("returnURL");
String token = (req.getParameter("token")==null)?"":req.getParameter("token");
try {
List list = new ArrayList();
List list0 = domainRepository.findAll();
list0.forEach((domain)->{
z_CF_DomainVO vo = new z_CF_DomainVO();
BeanUtils.copyProperties(domain, vo);
list.add(vo);
});
domains = ScriptDecoder.escape(gson.toJson(list));
}catch(Exception e) {
e.printStackTrace();
}
session.setAttribute("domains",domains );
session.setAttribute("returnURL", returnURL);
session.setAttribute("token", token);
return "public/loginSuccess";
}
@RequestMapping(value="/return",method=RequestMethod.POST)
public String returnPage(Model model,HttpServletRequest req,HttpServletResponse resp,HttpSession session) {
String returnURL = (req.getParameter("returnURL")==null)?"":req.getParameter("returnURL");
String token = (req.getParameter("token")==null)?"":req.getParameter("token");
String type = (req.getParameter("type")==null)?"":req.getParameter("type");
session.setAttribute("type", type);
session.setAttribute("returnURL", returnURL);
session.setAttribute("token", token);
return "public/return";
}
loginSuccess.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
loginSuccess
<%
System.err.println("=============== SSO loginSuccess.jsp ===============");
%>
return.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
System.err.println("================= return.jsp =================");
String returnURL = (session.getAttribute("returnURL")==null)?"":session.getAttribute("returnURL").toString();
String token = (session.getAttribute("token")==null)?"":session.getAttribute("token").toString();
String type = (session.getAttribute("type")==null)?"":session.getAttribute("type").toString();
if(!returnURL.equals("")){
String returnType = "";
switch(type.toLowerCase()){
case "login":
returnType = "登陆";
if(!token.equals("")){
if(returnURL.indexOf("?")>0){
returnURL = returnURL+"&token="+token;
}else{
returnURL = returnURL+"?token="+token;
}
}
break;
case "logout":
returnType = "注销";
break;
default:
break;
}
System.err.println("类型:"+returnType);
System.err.println("服务器即将重定向回子系统:"+returnURL);
response.sendRedirect(returnURL);
}
%>
好了,SSO大部分核心代码已经贴出来了,接下来就看你们的编码功底了。有什么相关问题也可以找我讨论(q:2225629171)
本次SSO实现跨域单点登陆讲解完毕。