【基于SSM后台文档管理系统】 ---- (一) 登录实现以及用Token机制保存登录状态
程序员文章站
2022-07-02 23:45:38
...
目录
1. 登录
1.1 登录流程设计
1.2 前端代码部分设计
-
发送AJAX请求
var userName = $("#userName").val(); var password = $("#password").val(); var data = {"userName": userName, "password": password} $.ajax({ type: "POST",//方法类型 dataType: "json",//预期服务器返回的数据类型 url: "users/login", contentType: "application/json; charset=utf-8", data: JSON.stringify(data), success: function (result) { } });
-
保存Token并跳转页面
通过AJAX请求获取到返回的数据,然后对数据进行存储setCookie("token", result.data.userToken); alert("登录成功"); window.location.href = "/";
-
setCookie() 方法
/** * 写入cookie * * @param name * @param value */ function setCookie(name, value) { // 设置了30天后Token过期,只是为了项目方便演示,可自行设置时间 var Days = 30; var exp = new Date(); exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000); document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString() + ";path=/"; }
1.3 后端代码部分设计
-
AdminUserDao.xml
通过用户名和密码查询用户记录<select id="getAdminUserByUserNameAndPassword" resultMap="AdminUserResult"> select id,user_name,user_token from tb_admin_user where user_name = #{userName} and password_md5 = #{passwordMD5} and is_deleted = 0 ORDER BY id DESC limit 1 </select>
-
AdminUserServiceImpl
每次登录后根据时间戳和用户ID生成新的tokenpublic AdminUser updateTokenAndLogin(String userName, String password) { AdminUser adminUser = adminUserDao.getAdminUserByUserNameAndPassword(userName, MD5Util.MD5Encode(password, "UTF-8")); if (adminUser != null) { //登录后即执行修改token的操作 String token = getNewToken(System.currentTimeMillis() + "", adminUser.getId()); if (adminUserDao.updateUserToken(adminUser.getId(), token) > 0) { //返回数据时带上token adminUser.setUserToken(token); return adminUser; } } return null; }
-
AdminUserController
@RequestMapping(value = "/login", method = RequestMethod.POST) public Result login(@RequestBody AdminUser user) { Result result = ResultGenerator.genFailResult("登录失败"); if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) { result.setMessage("请填写登录信息!"); } AdminUser loginUser = adminUserService.updateTokenAndLogin(user.getUserName(), user.getPassword()); if (loginUser != null) { result = ResultGenerator.genSuccessResult(loginUser); } return result; }
总结: 前端页面获取用户输入的账户和密码并提交至后端控制器。后端进行逻辑判断,不成功则返回登录失败,成功则生成新的 Token 值并更新数据库,同时将 Token 返回至前端,前端再进行存储保存。
2. 登录状态的保持
即 Token
值是否存在及 Token
值是否有效
2.1 Session机制和Token机制
Session机制
- session是服务端存储的一个对象,主要用来存储所有访问过该服务端的客户端的用户信息从而实现保持用户会话状态。服务器重启时,内存会被销毁,存储的用户信息也就消失了。
- 不同的用户访问服务端的时候会在session对象中存储键值对,“键”用来存储开启这个用户信息的"钥匙",在登录成功后,"钥匙"通过cookie返回给客户端,客户端存储为
sessionId
记录在cookie中。当客户端再次访问时,会默认携带cookie中的sessionId来实现会话机制。 - session存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上(前后端项目协议、域名、端口号都一致,即在一个项目下)
Token机制
- 适用于项目级的前后端分离(前后端代码运行在不同的服务器下)
- 登录成功后,会在响应主体中将{token:‘字符串’}返回给客户端。客户端通过
cookie
、sessionStorage、localStorage都可以进行存储。再次请求时不会默认携带,需要在请求拦截器位置给请求头中添加认证字段Authorization携带token
信息,服务器端就可以通过token
信息查找用户登录状态。
2.2 为什么选择Token机制
Session弊端
- 服务器压力增大
通常session是存储在内存中的,每个用户通过认证之后都会将session
数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大 - CSRF跨站伪造请求攻击
session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击 - 扩展性不强
如果将来搭建了多个服务器,虽然每个服务器都执行的是同样的业务逻辑,但是session数据是保存在内存中的(不是共享的),用户第一次访问的是服务器1,当用户再次请求时可能访问的是另外一台服务器2,服务器2获取不到session信息,就判定用户没有登陆过
Token优势
- 用户登录认证后,会对当前用户数据进行加密,生成一个加密字符串token,返还给客户端(服务器端不保存)
- 浏览器会将接收到的token值存储在
Local Storage
中,(通过js代码写入Local Storage
,通过js获取,并不会像cookie一样自动携带) - 再次访问时服务器端对token值的处理:服务器对浏览器传来的token值进行解密,解密完成后进行用户数据的查询,如果查询成功,则通过认证,实现状态保持,所以,即时有了多台服务器,服务器也只是做了token的解密和用户数据的查询,不需要在服务端去保留用户的认证信息或者会话信息,这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利,解决了session扩展性的弊端。
2.1 前端代码实现
/**
* 检查cookie
*/
function checkCookie() {
if (getCookie("token") == null) {
$('#tip').html("正在跳转至登录页面...");
alert("未登录!");
window.location.href = "login.html";
}
}
2.2 后端代码实现
Token 值是否有效则通过后端代码实现,由于大部分接口都需要进行登录验证,如果每个方法都添加查询用户数据的语句则有些多余,因此对方法做了抽取,通过自定义参数解析器的形式来返回用户信息
-
添加切面依赖
<!-- Begin: aspectj相关jar包--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${aspectj.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> <!-- End: aspectj相关jar包-->
-
注解代码
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TokenToUser { /** * 当前用户在request中的名字 * * @return */ String value() default "user"; }
-
自定义注解解析器
// 自定义参数解析器 public class TokenToUserMethodArgumentResolver implements HandlerMethodArgumentResolver { @Resource private AdminUserService adminUserService; public TokenToUserMethodArgumentResolver() { } /** * 判断是否是我们需要解析的参数类型 * @param parameter * @return */ public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(TokenToUser.class)) { return true; } return false; } /** * 真正解析的方法 * @param parameter * @param mavContainer * @param webRequest * @param binderFactory * @return * @throws Exception */ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { if (parameter.getParameterAnnotation(TokenToUser.class) instanceof TokenToUser) { AdminUser user = null; String token = webRequest.getHeader("token"); if (null != token && !"".equals(token)) { // 根据token值获取用户信息 user = adminUserService.getAdminUserByToken(token); } return user; } return null; } }
-
注册自定义参数解析器
<!--xml方式配置自定义参数解析器,使 TokenToUser 注解生效--> <mvc:argument-resolvers> <bean class="cn.whc.docsys.controller.handler.TokenToUserMethodArgumentResolver"/> </mvc:argument-resolvers>
-
具体应用,在AmindUserController的添加用户、修改密码、删除用户都需要验证有没有登录才能进行操作
3. 参考
1. token和session的区别:
https://www.cnblogs.com/belongs-to-qinghua/p/11353228.html
2. 为什么要使用token,token与session区别是什么
https://www.cnblogs.com/Koaler/p/11892214.html
3. Spring MVC自定义注解参数解析器
https://www.cnblogs.com/jmcui/p/11909524.html