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")){
isLogin = loginService.isLogin(cookies[i].getValue());
break;
}
}
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!=""){
Cookie cookie = new Cookie("login_token", token);
cookie.setMaxAge(3 * 24 * 60 * 60);
cookie.setPath("/admin");
cookie.setDomain("");
cookie.setHttpOnly(false);
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 - 用户在后台登录页面
index.html 输入用户名username 和密码password 后,发起/admin/login 请求 - Controller层接收到请求后,比对数据库中的
username 和password ,如果比对成功,则随机生成token,并将token放入Cookie 中,连同其它响应信息一并返回给客户端浏览器 - 浏览器保存存放token的
Cookie ,下一次输入http://localhost:8080/admin 网址时,则经过token 验证后不需要走后台登录页面,直接进入后台主页
|