no message

This commit is contained in:
fanxb 2019-03-13 23:14:35 +08:00
parent 3917829968
commit 6dc9c359e3
16 changed files with 111 additions and 278 deletions

View File

@ -35,15 +35,8 @@ public class JwtController {
} }
@PostMapping("/checkJwt") @PostMapping("/checkJwt")
public ReturnEntity checkJwt(@RequestBody JSONArray tokenList) { public ReturnEntity checkJwt(String token) {
return ReturnEntity.successResult(service.checkJwt(tokenList)); return ReturnEntity.successResult(service.checkJwt(token));
}
@GetMapping("/refreshJwt")
public ReturnEntity refreshJwt() {
String oldToken = HttpUtil.getData(JwtService.JWT_KEY);
String newToken = service.refreshJwt(oldToken);
return ReturnEntity.successResult(newToken);
} }
@GetMapping("/inValid") @GetMapping("/inValid")

View File

@ -1,34 +0,0 @@
package com.infinova.sso.entity;
/**
* 类功能简述
* 类功能详述
*
* @author fanxb
* @date 2019/3/7 16:46
*/
public class JwtInfo {
private String secret;
private long lastRefreshTime;
public JwtInfo(String secret, long lastRefreshTime) {
this.secret = secret;
this.lastRefreshTime = lastRefreshTime;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public long getLastRefreshTime() {
return lastRefreshTime;
}
public void setLastRefreshTime(long lastRefreshTime) {
this.lastRefreshTime = lastRefreshTime;
}
}

View File

@ -1,21 +1,16 @@
package com.infinova.sso.service; package com.infinova.sso.service;
import com.alibaba.fastjson.JSONArray;
import com.auth0.jwt.JWT;
import com.infinova.sso.entity.JwtInfo;
import com.infinova.sso.entity.User; import com.infinova.sso.entity.User;
import com.infinova.sso.exception.CustomException; import com.infinova.sso.exception.CustomException;
import com.infinova.sso.util.HttpUtil;
import com.infinova.sso.util.JwtUtil; import com.infinova.sso.util.JwtUtil;
import com.infinova.sso.util.RedisUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.servlet.http.Cookie;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* 类功能简述 * 类功能简述
@ -33,18 +28,14 @@ public class JwtService {
private Logger logger = LoggerFactory.getLogger(JwtService.class); private Logger logger = LoggerFactory.getLogger(JwtService.class);
/** /**
* 存储jwt与secret对应关系 * jwt token超时时间单位ms
*/ */
private static Map<String, JwtInfo> jwtMap = new ConcurrentHashMap<>(); private static int expireTime;
/** @Value("${jwt_expire_time}")
* jwt token超时时间单位s public void setExpireTime(int expireTime) {
*/ JwtService.expireTime = expireTime * 60 * 1000;
private static final int TIME_OUT = 60 * 60; }
/**
* 在此时间段内一个jwt不能重复刷新
*/
private static final int REFRESH_INTERVAL = 5 * 60 * 1000;
/** /**
* Description:登录获取token * Description:登录获取token
@ -58,77 +49,48 @@ public class JwtService {
//进行登录校验 //进行登录校验
try { try {
if (user.getName().equalsIgnoreCase(user.getPassword())) { if (user.getName().equalsIgnoreCase(user.getPassword())) {
String token = this.generateNewJwt(user.getName()); return this.generateNewJwt(user.getName());
setCookie(token, TIME_OUT);
return token;
} else { } else {
logger.info("账号密码错误:{}{}", user.getName(), user.getPassword()); logger.info("账号密码错误:{}{}", user.getName(), user.getPassword());
throw new CustomException("账号密码错误"); throw new CustomException("账号密码错误");
} }
} catch (Exception e) { } catch (Exception e) {
logger.info("账号密码错误:{},{}", user.getName(), user.getPassword()); logger.info("账号密码错误:{},{}", user.getName(), user.getPassword());
throw new CustomException("账号密码错误"); throw new CustomException(e, "账号密码错误");
} }
} }
/**
* Description:刷新token
*
* @return java.lang.String
* @author fanxb
* @date 2019/3/5 11:06
*/
public String refreshJwt(String token) {
try {
JwtInfo info = jwtMap.get(token);
Long nowTime = System.currentTimeMillis();
if (nowTime - info.getLastRefreshTime() < REFRESH_INTERVAL) {
throw new CustomException("token刷新间隔过短");
}
info.setLastRefreshTime(nowTime);
String id = JwtUtil.decode(token, info.getSecret()).get("loginId").asString();
String newToken = generateNewJwt(id);
setCookie(newToken, TIME_OUT);
return newToken;
} catch (Exception e) {
throw new CustomException(e, "token校验失败");
}
}
/** /**
* Description: 生成新的jwt,并放入jwtMap中 * Description: 生成新的jwt,并放入jwtMap中
* *
* @return java.lang.String * @return java.lang.String
* @author fanxb * @author fanxb
* @date 2019/3/5 10:44 * date 2019/3/5 10:44
*/ */
private String generateNewJwt(String name) { private String generateNewJwt(String name) {
String secret = UUID.randomUUID().toString().replaceAll("-", ""); String secret = UUID.randomUUID().toString().replaceAll("-", "");
String token = JwtUtil.encode(name, secret, TIME_OUT); String token = JwtUtil.encode(name, secret, expireTime);
jwtMap.put(token, new JwtInfo(secret, 0)); RedisUtil.set(token, secret, expireTime);
return token; return token;
} }
/** /**
* Description:检查jwt有效性,返回失效jwt * Description:检查jwt有效性
* *
* @return List<String> 失效jwt列表 * @return Boolean
* @author fanxb * @author fanxb
* @date 2019/3/4 18:47 * @date 2019/3/4 18:47
*/ */
public List<String> checkJwt(JSONArray tokens) { public Boolean checkJwt(String jwt) {
List<String> res = new ArrayList<>(); try {
tokens.forEach(item -> { String secret = RedisUtil.redisTemplate.opsForValue().get(jwt);
String jwt = (String) item; JwtUtil.decode(jwt, secret);
try { return true;
String secret = jwtMap.get(jwt).getSecret(); } catch (Exception e) {
JwtUtil.decode(jwt, secret); e.printStackTrace();
} catch (Exception e) { return false;
e.printStackTrace(); }
res.add(jwt);
}
});
return res;
} }
/** /**
@ -138,43 +100,7 @@ public class JwtService {
* @date 2019/3/4 19:58 * @date 2019/3/4 19:58
*/ */
public void inValid(String jwt) { public void inValid(String jwt) {
jwtMap.remove(jwt); RedisUtil.delete(jwt);
setCookie("", 0);
} }
/**
* Description:定时检查过期的jwt并从map中删除
*
* @author fanxb
* @date 2019/3/5 18:16
*/
@Scheduled(fixedRate = 10 * 1000)
private void cleanJwtMap() {
Date now = new Date();
Set<String> keys = jwtMap.keySet();
keys.forEach(item -> {
if (now.getTime() > JWT.decode(item).getExpiresAt().getTime()) {
jwtMap.remove(item);
logger.info("清理掉了:{}", item);
}
});
}
/**
* Description: 写入jwt token到cookie
*
* @param token token
* @param maxAge 失效时间 单位s
* @return void
* @author fanxb
* @date 2019/3/5 18:43
*/
private void setCookie(String token, int maxAge) {
Cookie cookie = new Cookie(JWT_KEY, token);
cookie.setMaxAge(maxAge);
cookie.setPath("/");
HttpUtil.getResponse().addCookie(cookie);
}
} }

View File

@ -33,7 +33,7 @@ public class JwtUtil {
Algorithm algorithm = Algorithm.HMAC256(secret); Algorithm algorithm = Algorithm.HMAC256(secret);
String token = JWT.create() String token = JWT.create()
//设置过期时间为一个小时 //设置过期时间为一个小时
.withExpiresAt(new Date(System.currentTimeMillis() + timeOut * 1000)) .withExpiresAt(new Date(System.currentTimeMillis() + timeOut))
//设置负载 //设置负载
.withClaim("name", name) .withClaim("name", name)
.sign(algorithm); .sign(algorithm);
@ -45,7 +45,7 @@ public class JwtUtil {
* *
* @param token token * @param token token
* @param secret secret * @param secret secret
* @return java.util.Map<java.lang.String , com.auth0.jwt.interfaces.Claim> * @return java.util.Map<java.lang.String , com.auth0.jwt.interfaces.Claim>
* @author fanxb * @author fanxb
* @date 2019/3/4 18:14 * @date 2019/3/4 18:14
*/ */

View File

@ -1,5 +1,5 @@
server: server:
port: 8081 port: 8080
servlet: servlet:
context-path: /sso context-path: /sso
spring: spring:
@ -9,7 +9,7 @@ spring:
type: redis type: redis
redis: redis:
database: 0 database: 0
host: 10.82.27.177 host: 192.168.1.100
port: 6379 port: 6379
password: password:
# 连接超时时间ms) # 连接超时时间ms)
@ -25,4 +25,8 @@ spring:
max-idle: 8 max-idle: 8
# 最小空闲链接数 # 最小空闲链接数
min-idle: 0 min-idle: 0
mvc:
static-path-pattern: /static/**
# jwt过期时间单位分钟
jwt_expire_time: 60

View File

@ -5,59 +5,41 @@
<title>认证中心</title> <title>认证中心</title>
</head> </head>
<div style="text-align: center;">这里是认证中心主页</div> <div style="text-align: center;">这里是认证中心主页</div>
<div>
<button onclick="clearToken()">清除token</button>
</div>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="main.js"></script> <script src="main.js"></script>
<script src="https://cdn.bootcss.com/Base64/1.0.2/base64.js"></script> <script src="https://cdn.bootcss.com/Base64/1.0.2/base64.js"></script>
<script> <script>
$('#loginStatus').html(getUserName())
function clearToken() {
delete window.localStorage.token;
}
var order = getUrlParam("order"); var order = getUrlParam("order");
switch (order) { switch (order) {
case "checkLogin": case "checkLogin":
checkLogin(); checkLogin();
break; break;
case "updateToken":
updateToken();
break;
default: default:
console.log("不支持的order:" + order); console.log("不支持的order:" + order);
} }
function checkLogin() { function checkLogin() {
var token = getToken(); var token = localStorage.getItem("token");
if (token == null) { if (token == null || token.length == 0) {
goLogin(); goLogin();
} else { } else {
//有token检查token是否还有效 //有token检查token是否还有效
$.get("/checkToken?token=" + getToken(), function (res) { $.get("/sso/checkJwt?token=" + localStorage.getItem("token"), function (res) {
if (res.code === 1) { if (res.code === 1) {
alert('登录,跳转到回调页面'); alert('登录,跳转到回调页面');
window.location.href = getUrlParam("redirect") + "&token=" + getToken(); window.location.href = getUrlParam("redirect") + "&token=" + getToken();
}else { } else {
goLogin(); goLogin();
} }
}) })
} }
} }
function goLogin(){ function goLogin() {
// alert("无认证信息,即将跳转到登录页面"); alert("无认证信息,即将跳转到登录页面");
window.location.href = encodeURI("/login.html?redirect=" + getUrlParam("redirect")); window.location.href = encodeURI("./login.html?redirect=" + getUrlParam("redirect"));
} }
function updateToken() {
var token = getUrlParam("token");
setToken(token);
}
</script> </script>
</body> </body>
</html> </html>

View File

@ -11,26 +11,23 @@
<button id="login" onclick="doLogin()">登录</button> <button id="login" onclick="doLogin()">登录</button>
</div> </div>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="main.js"></script> <script src="main.js"></script>
<script src="https://cdn.bootcss.com/Base64/1.0.2/base64.min.js"></script> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script> <script>
function doLogin() { function doLogin() {
var name = $('#name').val(); var name = $('#name').val();
var password = $('#password').val(); var password = $('#password').val();
password = window.btoa(password);
console.log(name, password);
$.ajax({ $.ajax({
type: 'post', type: 'post',
url: "/sso/login", url: "/sso/login",
contentType: "application/json", contentType: "application/json",
dataType: 'json', dataType: 'json',
data: JSON.stringify({loginId: name, password: password}), data: JSON.stringify({name: name, password: password}),
success: function (res) { success: function (res) {
if (res.code === 1) { if (res.code === 1) {
setToken(res.data); setToken(res.data);
// alert("登录成功,跳转到回调地址"); alert("登录成功,跳转到回调地址");
// window.location.href = getUrlParam("redirect") + "&token=" + res.data; window.location.href = getUrlParam("redirect") + "&token=" + res.data;
} else { } else {
alert("账号密码错误"); alert("账号密码错误");
} }

View File

@ -1,21 +1,3 @@
function getToken() {
return window.localStorage.token || null;
}
function setToken(token) {
window.localStorage.token = token;
}
function getUserName() {
var token = getToken();
if (token == null) {
return "未登录";
} else {
var info = token.split(".")[1];
return JSON.parse(window.atob(info)).name;
}
}
function getUrlParam(name) { function getUrlParam(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象 var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
var r = window.location.search.substr(1).match(reg); //匹配目标参数 var r = window.location.search.substr(1).match(reg); //匹配目标参数

View File

@ -5,6 +5,7 @@ import com.example.sysa.entity.UserContext;
import com.example.sysa.filter.LoginFilter; import com.example.sysa.filter.LoginFilter;
import com.example.sysa.util.HttpClient; import com.example.sysa.util.HttpClient;
import com.example.sysa.util.UserContextHolder; import com.example.sysa.util.UserContextHolder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -18,16 +19,19 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class Main { public class Main {
@Value("${sso_server}")
private String serverHost;
@RequestMapping("/test") @RequestMapping("/test")
public ReturnEntity test() { public ReturnEntity test() {
return new ReturnEntity(1, "通过验证", null); return new ReturnEntity(1, "通过验证", null);
} }
@RequestMapping("/logout") @RequestMapping("/logout")
public ReturnEntity logout() throws Exception{ public ReturnEntity logout() throws Exception {
UserContext context = UserContextHolder.get(); UserContext context = UserContextHolder.get();
HttpClient.get("http://localhost:8080/clearToken?token="+context.getToken()); HttpClient.get(serverHost + "/clearToken?token=" + context.getToken());
LoginFilter.tokenMap.remove(context.getToken());
return null; return null;
} }
} }

View File

@ -44,4 +44,12 @@ public class ReturnEntity {
public void setData(Object data) { public void setData(Object data) {
this.data = data; this.data = data;
} }
public static ReturnEntity successResult(Object data) {
return new ReturnEntity(1, "", data);
}
public static ReturnEntity failedResult(String message) {
return new ReturnEntity(0, message, null);
}
} }

View File

@ -6,6 +6,9 @@ import com.example.sysa.entity.ReturnEntity;
import com.example.sysa.entity.UserContext; import com.example.sysa.entity.UserContext;
import com.example.sysa.util.HttpClient; import com.example.sysa.util.HttpClient;
import com.example.sysa.util.UserContextHolder; import com.example.sysa.util.UserContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils; import org.springframework.util.Base64Utils;
@ -29,11 +32,10 @@ import java.util.concurrent.ConcurrentHashMap;
@WebFilter(urlPatterns = "/**", filterName = "loginFilter") @WebFilter(urlPatterns = "/**", filterName = "loginFilter")
public class LoginFilter implements Filter { public class LoginFilter implements Filter {
public static final Map<String, Long> tokenMap = new ConcurrentHashMap<>(); private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 10s内认为报错在tokenMap中的token有效避免频繁请求认证中心验证token有效性 @Value("${sso_server}")
*/ private String serverHost;
private static final int EXPIRE_TIME = 10 * 1000;
@Override @Override
public void init(FilterConfig filterConfig) throws ServletException { public void init(FilterConfig filterConfig) throws ServletException {
@ -53,26 +55,8 @@ public class LoginFilter implements Filter {
filterChain.doFilter(servletRequest, servletResponse); filterChain.doFilter(servletRequest, servletResponse);
return; return;
} }
boolean isOk = false; String token = httpServletRequest.getParameter("token");
String token = ""; if (this.check(token)) {
try {
//如果token未缓存或距离上次检查有效性超过1分钟向认证中心请求检查有效性
token = httpServletRequest.getParameter("token");
Long lastCheckTime = tokenMap.get(token);
if (lastCheckTime == null || System.currentTimeMillis() - lastCheckTime > EXPIRE_TIME) {
JSONObject object = HttpClient.get("http://localhost:8080/checkToken?token=" + token);
if (object.getInteger("code") == 1) {
isOk = true;
tokenMap.put(token, System.currentTimeMillis());
}
} else {
//token有效且未过期
isOk = true;
}
} catch (Exception e) {
e.printStackTrace();
}
if (isOk) {
//token和用户id保存到userContextholder //token和用户id保存到userContextholder
String str = new String(Base64Utils.decodeFromString(token.split("\\.")[1])); String str = new String(Base64Utils.decodeFromString(token.split("\\.")[1]));
UserContext context = new UserContext(JSON.parseObject(str).getString("name"), token); UserContext context = new UserContext(JSON.parseObject(str).getString("name"), token);
@ -84,6 +68,19 @@ public class LoginFilter implements Filter {
response.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8");
response.getWriter().write(JSON.toJSONString(new ReturnEntity(-1, "未登录", null))); response.getWriter().write(JSON.toJSONString(new ReturnEntity(-1, "未登录", null)));
} }
}
private boolean check(String jwt) {
try {
if (jwt == null || jwt.trim().length() == 0) {
return false;
}
JSONObject object = HttpClient.get(serverHost + "/checkJwt?token=" + jwt);
return object.getInteger("code") == 1;
} catch (Exception e) {
logger.error("向认证中心请求失败", e);
return false;
}
} }
} }

View File

@ -1,2 +0,0 @@
server.port=8081
spring.mvc.static-path-pattern=/static/**

View File

@ -0,0 +1,6 @@
server:
port: 8081
spring:
mvc:
static-path-pattern: /static/**
sso_server: http://localhost:8080/sso

View File

@ -5,25 +5,30 @@
<title>系统A</title> <title>系统A</title>
</head> </head>
<body> <body>
<div style="align-content: center">这里是系统A主页面</div> <div id="currentHost"></div>
<div style="align-content: center">当前登录用户:<span id="loginStatus"></span></div> <div style="align-content: center">当前登录用户:<span id="loginStatus"></span></div>
<div> <div>
<button onclick="test()">测试接口访问</button> <button onclick="test()">测试接口访问</button>
<button onclick="clearToken()">注销登录</button> <button onclick="clearToken()">注销登录</button>
</div> </div>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/static/main.js"></script>
<script src="https://cdn.bootcss.com/Base64/1.0.2/base64.min.js"></script> <script src="https://cdn.bootcss.com/Base64/1.0.2/base64.min.js"></script>
<script> <script>
$('#loginStatus').text(getUserName()); $('#currentHost').text(location.origin);
if (getToken() == null) { var token = getUrlParam("token");
goToLoginServer(); if (token != null && token.trim().length > 0) {
localStorage.setItem("token", token);
} }
test(); test();
var info = localStorage.getItem("token").split(".")[1];
var userName = JSON.parse(window.atob(info)).name;
$('#loginStatus').text(userName);
//测试token是否有效
function test() { function test() {
$.get("/test?token=" + getToken(), function (res) { $.get("/test?token=" + localStorage.getItem("token"), function (res) {
if (res.code === 1) { if (res.code === 1) {
} else { } else {
goToLoginServer(); goToLoginServer();
@ -34,14 +39,21 @@
//注销登录 //注销登录
function clearToken() { function clearToken() {
$.get("/logout?token=" + getToken(), function (res) { $.get("/logout?token=" + getToken(), function (res) {
localStorage.removeItem("token");
goToLoginServer(); goToLoginServer();
}) })
} }
function goToLoginServer() { function goToLoginServer() {
alert("当前无登录信息,跳转到认证中心"); alert("当前无登录信息,跳转到认证中心");
location.href = encodeURI("http://localhost:8080?order=checkLogin&redirect=" + window.location.origin location.href = encodeURI("http://localhost:8080/sso/static/index.html?order=checkLogin&redirect=" + window.location.href);
+ "/static/redirect.html?redirect=" + window.location.href); }
function getUrlParam(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
var r = window.location.search.substr(1).match(reg); //匹配目标参数
if (r != null) return unescape(r[2]);
return null; //返回参数值
} }
</script> </script>

View File

@ -1,24 +0,0 @@
function getToken() {
return window.localStorage.token || null;
}
function setToken(token) {
window.localStorage.token = token;
}
function getUserName() {
var token = getToken();
if (token == null) {
return "未登录";
} else {
var info = token.split(".")[1];
return JSON.parse(window.atob(info)).name;
}
}
function getUrlParam(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
var r = window.location.search.substr(1).match(reg); //匹配目标参数
if (r != null) return unescape(r[2]);
return null; //返回参数值
}

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录成功回调地址</title>
</head>
<body>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/static/main.js"></script>
<script src="https://cdn.bootcss.com/Base64/1.0.2/base64.min.js"></script>
<script>
var token = getUrlParam("token");
setToken(token);
// alert("认证成功,准备跳回登录登录前访问地址");
window.location.href = getUrlParam("redirect");
</script>
</body>
</html>