SpringBoot 实现基于Token的登录验证功能
一、知识储备
1、基于服务器的验证
我们都知道HTTP
协议是无状态的,这种无状态意味着程序需要验证每一次请求,从而辨别客户端的身份。在这之前,程序都是通过在服务端存储的登录信息来辨别请求的。这种方式一般都是通过存储Session
来完成。
基于服务器验证方式所暴露的一些问题:
Session
:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。- 可扩展性:在服务端的内存中使用
Session
存储登录信息,伴随而来的是可扩展性问题。 CORS
(跨域资源共享):当我们需要让数据跨多台设备上使用时,跨域资源的共享会是一个让人头疼的问题。在使用AJAX
抓取另一个域的资源,就可能会出现禁止请求的情况。CSRF
(跨站请求伪造):用户在访问银行网站时,他们很容易收到跨站请求伪造的攻击,并且能够被利用其访问其他的网站。
在这些问题中,可扩展性是最突出的。因此我们有必要去寻求一种更为行之有效的方法。
2、基于Token的验证原理
基于Token的身份验证是无状态的,我们不将用户信息存在服务器中。这种概念解决了在服务端存储信息时的许多问题。NoSession
意味着我们的程序可以根据需要去增减机器,而不用去担心用户是否登录。
3、基于Token的验证过程
过程如下:
- 用户通过用户名和密码发送请求
- 服务器端程序进行验证
- 服务器端程序返回一个带签名的token返回给客户端
- 客户端存储token,并且每次访问API都携带
Token
到服务器端 - 服务端验证token,效验成功则返回请求数据,效验失败则返回错误码
4、Token验证的优势
-
无状态、可扩展
在客户端存储的
Token
是无状态的,并且能够被扩展。基于这种无状态和不存储Session
信息,负载均衡器能够将用户信息从一个服务器传到其他服务器上。 -
安全性
请求中发送
token
而不再是发送cookie
能够防止CSRF
(跨站请求伪造)。即使在客户端使用cookie
存储token
,cookie
也仅仅是一个存储机制而不是用于认证。不将信息存储在Session
中,让我们少了对Session
的操作。token
是有时效的,一段时间之后用户需要重新验证。 -
可扩展性
tokens
能够创建与其他程序共享权限的程序 -
多平台跨域
二、代码实战
1、Model层-User
public class User {
private int id;
private String username;
private String password;
private String token;
}
2、mapper层
UserMapper
@Mapper
public interface UserMapper {
User getByUsernamePassword(String username,String password);
User getByToken(String token);
int update(User user);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.token.mapper.UserMapper">
<select id="getByUsernamePassword" resultType="com.example.token.model.User">
select * from user where username = #{username} and password = #{password}
</select>
<select id="getByToken" resultType="com.example.token.model.User">
select * from user where token = #{token}
</select>
<update id="update" parameterType="com.example.token.model.User">
update user
<set>
token = #{token,jdbcType=VARCHAR}
</set>
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>
3、Util工具类
public class Util {
public static String getUUID(){
return UUID.randomUUID().toString().replace("-","");
}
}
Utile工具类的getUUID方法用户随机生成Token
4、Service层-LoginService
@Service
public class LoginService {
@Autowired
private UserMapper userMapper;
public String login(String username,String password){
User user = userMapper.getByUsernamePassword(username, password);
if (user!=null){
String token = Util.getUUID();
user.setToken(token);
userMapper.update(user);
return token;
}
return "";
}
public Boolean isLogin(String token){
User user = userMapper.getByToken(token);
if (user!=null){
return true;
}
return false;
}
}
5、Controller层-LoginController
@Controller
public class LoginController {
@Autowired
private LoginService loginService;
@GetMapping("/admin")
public String admin(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
Boolean isLogin = false;
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("login_token")){//有储存token的cookie
isLogin = loginService.isLogin(cookies[i].getValue());
break;
}
}
//如果token存在且有效
if (isLogin == true){
return "dashboard";
}else {//返回登录页面
return "index";
}
}
@PostMapping("/admin/login")
public String login(String username,String password,HttpServletResponse response){
String token = loginService.login(username, password);
if (token!=""){//登录成功,并生成了token
Cookie cookie = new Cookie("login_token", token);
cookie.setMaxAge(3 * 24 * 60 * 60);//有效期3天
cookie.setPath("/admin");//必须要设置
cookie.setDomain("");
cookie.setHttpOnly(false);
//将cookie对象加入response响应
response.addCookie(cookie);
return "dashboard";
}
//登录失败
return "error";
}
}
三、Token验证思路总结
- Token的生成可以用UUID类随机生成,或者也可以用JWS(JSON Web Token)
- 用户登录后台网页时,输入的网址应该是
http://localhost:8080/admin
- Controller层收到
/admin
请求时,应该判断请求是否携带token- 若携带了token,则调用LoginService方法将携带的token与数据库中token比对。若比对成功,则直接进入后台页面
dashboard.html
- 若未携带token或者token比对失败,则返回后台登录页面
index.html
- 若携带了token,则调用LoginService方法将携带的token与数据库中token比对。若比对成功,则直接进入后台页面
- 用户在后台登录页面
index.html
输入用户名username
和密码password
后,发起/admin/login
请求 - Controller层接收到请求后,比对数据库中的
username
和password
,如果比对成功,则随机生成token,并将token放入Cookie
中,连同其它响应信息一并返回给客户端浏览器 - 浏览器保存存放token的
Cookie
,下一次输入http://localhost:8080/admin
网址时,则经过token
验证后不需要走后台登录页面,直接进入后台主页