Token介绍
session是存储服务器端,cookie是存储在客户端,获取session里的信息是通过存放在会话cookie里的session id获取的。而session是存放在服务器的内存中里,所以session里的数据不断增加会造成服务器的负担。而基于 token 的身份验证是无状态的,我们不用将用户信息存在服务器或 Session 中。token具有无状态、可拓展、安全、多平台与跨域的优势。
责任链设计模式介绍
责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。
Token验证流程思路
1.前端提交表单 后端验证用户名密码
2.登录成功 设置toekn更新数据库
3.全局过滤器 调用责任链处理request请求
4.责任链无异常正常放行 有异常进入全局处理
关于Token的问题
1.token存到哪里?
前后端分离 前端使用document.cookie=xxx去存token
前后端不分离jsp 前端可以在表单中插入一个隐藏字段,该字段就是保存在页面端的token字串,同时把该字符串存入session中。等到用户提交表单时,会一并提交该隐藏的token字符串。到后端中使用response.addCookie() 去存token。
2.前台如何发送token?
第一种 字符串形式
$.ajax({
url: '/user/addUser',
type: 'post',
dataType: 'json',
data: token = "f5699740724d4656898d8530bff783b5"
})
第二种 JSON形式
$.ajax({
url: '/user/addUser',
type: 'post',
dataType: 'json',
headers: {
"Content-Type": "application/json",
"token": "f5699740724d4656898d8530bff783b5"
}
})
具体代码实现
前端提交登录表单
<script type="text/javascript">
//页面加载完成之后 执行
$(document).ready(function () {
//监听按钮的点击事件
$("#btnLogin").click(function () {
//获取用户名的值 和密码的值
var userName = $("#userName").val();
var password = $("#password").val();
// var verifyCode = $("#code").val();
//发送ajax请求到控制器去进行用户身份的校验
//待做......
//发送Ajax请求 来完成 用户的登陆操作
var data={
"user":{
"username":userName,
"password":password
}
};
$.ajax({
"url":"/user/login.action",
"type":"POST",
"data":JSON.stringify(data),
"dataType":"json",
"success":function (response) {
//接下来将上面的数据转换成JSON对象
//将返回的用户信息存储到Cookie中
//这里在保存的时候需要将 上面的JSON对象转换成字符串
var resp=JSON.stringify(response);
console.info(response);
console.info(resp);
if(response.id == 1000){
setCookie("userInfo",JSON.stringify(response.data),response.data.expireTime);
setCookie("token",JSON.stringify(response.data.token),response.data.expireTime);
//页面跳转
location.href="/html/index.html";
}else{
//说明登陆失败
alert("登陆失败错误信息是:"+resp.message);
}
},
"error":function (ajax,state) {
alert("执行失败:"+state)
}
});
})
})
/**
* 将数据写到Cookie中去
* @param cname
* @param cvalue
* @param exdays
*/
function setCookie(cname, cvalue, expire) {
var d = new Date();
d.setTime(d.getTime() + expire);
var expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
</script>
前端获取token(用来获取token配合发送)
//获取这个Cookie的值
function getCookie(name) {
let strCookie = document.cookie;
console.log("获取到的cookie:" + strCookie)
let arrCookie = strCookie.split(";");
for (let i = 0; i < arrCookie.length; i++) {
let arr = arrCookie[i].split("=");
if (arr[0] == name) {
return arr[1];
}
}
return "";
}
后端验证用户名密码 若登录成功 设置toekn更新数据库
Controller
@ResponseBody
@RequestMapping("/logout.action")
public DataResult logout(@RequestBody User user) throws SQLException {
System.out.println("前台用户---》"+user);
us.logout(user);
DataResult dr = new DataResult();
return dr;
}
Service
@Override
public User login(User user) throws SQLException {
//user包含需要的信息---委员:用户名,密码,电话号码 isAdmin 管理员---用户名和电话号码 isAdmin
//先判断空
if(user==null){
throw new BussinessException(ResponseCode.NO_USER_EXCEPTION.getId(),ResponseCode.NO_USER_EXCEPTION.getMessage());
}
if(StringUtil.isEmpty(user.getUsername())){
throw new BussinessException(ResponseCode.EMPTY_USERNAME_EXCEPTION.getId(),ResponseCode.EMPTY_USERNAME_EXCEPTION.getMessage());
}
//根据用户名和电话号码查找用户
User queryUser = userDao.getUserByName(user.getUsername());
if(queryUser==null){
//说明用户名错了
throw new BussinessException(ResponseCode.NO_USERNAME_EXCEPTION);
}
//通过查询的用户来验证
if(queryUser.getIsAdmin()==0){
if(StringUtil.isEmpty(user.getPhone())){
throw new BussinessException(ResponseCode.EMPTY_PHONE_EXCEPTION.getId(),ResponseCode.EMPTY_PHONE_EXCEPTION.getMessage());
}
if(!queryUser.getPhone().equals(user.getPhone())){
throw new BussinessException(ResponseCode.USERNAME_PHONE_NOTMATCH_EXCEPTION.getId(),ResponseCode.USERNAME_PHONE_NOTMATCH_EXCEPTION.getMessage());
}
}
//判断密码是否正确
if(!queryUser.getPassword().equals(user.getPassword())){
throw new BussinessException(ResponseCode.ERROR_PASSWORD_EXCEPTION.getId(),ResponseCode.ERROR_PASSWORD_EXCEPTION.getMessage());
}
//说明登录成功
//修改密码为null
queryUser.setToken(UUID.randomUUID().toString().replace("-",""));
queryUser.setExpireTime(1000L*60*60*24+System.currentTimeMillis());
queryUser.setLastLogTime(new Date());
userDao.updateUser(queryUser);
queryUser.setPassword(null);//返回前台之前把密码置空,不影响数据库
//修改用户
return queryUser;
}
责任链
责任链方法接口
public interface IRequstChain {
void handleRequest(HttpServletRequest request) throws SQLException;
}
token验证链
public class AuthChain implements IRequstChain {
Logger log = LoggerFactory.getLogger(AuthChain.class);
@Override
public void handleRequest(HttpServletRequest request) throws SQLException {
//完成验证
//获取token
String token = null;
//先去cookie取token
String cookie = request.getHeader("Cookie");
String[] split = cookie.split(";");
for (String s : split) {
String st = s.trim();
if(st.startsWith("token=")){
token = st.replace("token=","").replace("\"","");
}
}
if (token == null) {
token = request.getHeader("token");
}
log.info("获取到前台token--->{}",token);
//查询--根据token查找用户
UserDao userDao = new UserDaoImpl();
User user = userDao.getUserByToken(token);
if (user == null) {
throw new BussinessException(ResponseCode.NO_TOKEN_EXCEPTION);
}
//过期没有
if(user.getExpireTime()-System.currentTimeMillis()<0){
throw new BussinessException(ResponseCode.EXPIRE_TOKEN_EXCEPTION);
}
log.info("token验证通过");
}
}
模拟的两个其他链
public class LimitChain implements IRequstChain{
@Override
public void handleRequest(HttpServletRequest request) throws SQLException {
System.out.println("处理限流");
}
}
public class OtherChain implements IRequstChain {
@Override
public void handleRequest(HttpServletRequest request) throws SQLException {
System.out.println("其他处理情况");
}
}
ChainMannager 读配置文件,按顺序调用所有的链条来执行
public class ChainManager {
private static List<String> keys = new ArrayList<>();//按顺序存一个键的集合
private static Map<String,IRequstChain> chainMap = new HashMap<>();
static {
InputStream is = ChainManager.class.getClassLoader().getResourceAsStream("chain.properties");
Properties prop = new Properties();
try {
prop.load(is);
Set<Object> keyset = prop.keySet();
for (Object key : keyset) {
String k = (String)key;
if(k.equals("order")){
//解析顺序
String order = prop.getProperty(k);
String[] split = order.split(",");
for (String s : split) {
keys.add(s);
}
}else{
String className = prop.getProperty(k);
IRequstChain obj = (IRequstChain) Class.forName(className).newInstance();
chainMap.put(k,obj);
}
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
//执行所有的链条
public static void executeChain(HttpServletRequest request) throws SQLException {
//循环所有的key
for (String key : keys) {
chainMap.get(key).handleRequest(request);
}
}
}
读取的配置文件chain.properties
order = auth,limit,other
other = com.tpa.chain.OtherChain
auth = com.tpa.chain.AuthChain
limit = com.tpa.chain.LimitChain
过滤器中doFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取request里面的url,去把登录和获取密码放行
HttpServletRequest req = (HttpServletRequest)request;
StringBuffer requestURL = req.getRequestURL();
System.out.println("requesturl--->"+requestURL.toString());
String requestURI = req.getRequestURI();
log.info("访问路径----》{}",requestURI);
if(requestURI.equals("/user/login.action")||requestURI.equals("/user/getPassword.action")){
//放行
chain.doFilter(request,response);
return;//不要漏了
}
//到这里调用责任链来处理req请求
try {
ChainManager.executeChain(req);
//正常放行
chain.doFilter(request,response);
} catch (Exception e) {
handleExp(e,(HttpServletResponse) response);
}
}
过滤器中异常处理
private void handleExp(Exception e, HttpServletResponse resp) {
System.out.println("出异常了");
e.printStackTrace();
DataResult dr = null;
if(e instanceof SQLException){
dr = new DataResult(ResponseCode.SQL_EXCEPTION);
}else if(e instanceof BussinessException){
dr = new DataResult(((BussinessException) e).getId(),e.getMessage());
}else{
dr = new DataResult(ResponseCode.OTHER_EXCEPTION);
}
resp.setContentType("text/html;charset=utf-8");
try {
PrintWriter out = resp.getWriter();
out.write(JSON.toJSONString(dr));
out.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}