Session登录
登录流程
浏览器第一次向服务器发出请求,服务器接收到请求后会检测这个请求中是否包含一个叫做JSESSIONID的cookie,如果包含,就在自己的JVM缓存中查找此cookie是否对应一个session对象,如果没有则为其创建一个,用来维持对话。如果请求中没有叫做JSESSIONID的cookie,那么就说明该用户是第一次发出请求,那么服务器会为其生成一个名为JSESSIONID的cookie以及对应的session对象,并且服务器会将这个名为JSESSIONID的cookie返回给浏览器。这样浏览器再次发出请求时就会自动带上这个cookie,以便维持本次会话。
弊端
传统的Session登录需要服务器位置session对象,从而维持对话状态。这样做当然可以,当时当用户量激增的时候,每个用户在线用户都要对应一个session对象,那么势必会对服务器的性能造成影响。
Token登录
Token登录是目前市面上比较常用的登录验证方式。与一般的Session不同,用户在登录的时候,如果此用户的账号、密码都正确,那么后台会根据用户信息(用户名、密码等等)为该用户生成一个加密字符串。对,Token本质上就是一个记录了用户信息的加密字符串。加密是处于安全的考虑,防止Token字符串被破解及篡改。
那么这个Token字符串该如何返回给用户呢?我知道的有两种方式,一种是将其放到cookie中,这样这要这个cooke不过其,那么浏览器每次发出请求时都会携带这个token字符串。服务器便能根据这个字符串获取用户信息。从而进行一系列的操作,如登录验证、权限管理等。
实现
这里仅仅是简单的对其实现,采用jwt的方案。
1.添加依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
2.编写Token工具类
@Component
public class TokenUtil{
/**
* 生成token
* @param user
* @return
*/
public String generateToken(User user) {
Date start = new Date();
long currentTime = System.currentTimeMillis() + 60* 60 * 1000;//一小时有效时间
Date end = new Date(currentTime);
String token = "";
token = JWT.create()
.withAudience(user.getId().toString())
.withAudience(user.getUsername())
.withIssuedAt(start)
.withExpiresAt(end)
.sign(Algorithm.HMAC256(user.getPassword()));
return token;
}
/**
*
* @param token
* @param key
* @return userId
* 获取制定token中某个属性值
*/
public static String get(String token, String key) {
List<String> list= JWT.decode(token).getAudience();
String userId = JWT.decode(token).getAudience().get(0);
return userId;
}
/**
* 获取request
* @return
*/
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
return requestAttributes == null ? null : requestAttributes.getRequest();
}
/**
*
* @param request
* @return
* 获取token
*/
public String getToken(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
for (Cookie c :
cookies) {
if (c.getName().equals("token")) {
return c.getValue();
}
}
return null;
}
}
3.自定义拦截器类
实现HandlerInterceptor接口,并实现preHandle方法。preHandle方法会在每一个Controller方法执行前执行,因此我们可以在该方法中获取token,从而进行一系列操作。在这里对其进行简单的实现,通过request对象获取名为token的cookie,如果存在则放行,如果不存在则拦截,并重定向到登录界面。
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private TokenUtil tokenUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
// 查看请求中是否存在token,如果不存在直接跳转到登陆页面
String token = tokenUtil.getToken(request);
if (StringUtils.isEmpty(token)) {
response.sendRedirect("/login");
return false;
}
return true;
}
}
4.实现拦截
@Configuration
public class MVCConfig implements WebMvcConfigurer {
/**
* 静态资源映射
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
// 表示拦截所有请求
.addPathPatterns("/**")
// 表示取消对特定路径的拦截
.excludePathPatterns("/login")
.excludePathPatterns("/loginCheck")
// 这里一定不要写成/**/*.js的形式,spring boot无法识别
// 取消对static目录下静态资源的拦截
.excludePathPatterns("/static/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
5.业务代码
Controller
@Controller
public class UserController {
@Autowired
private IUserService userService;
@GetMapping("/login")
public String toLoginPage(){
return "login";
}
@PostMapping("/loginCheck")
@ResponseBody
public R login(@RequestBody User user, HttpServletResponse response){
R result = userService.loginCheck(user, response);
return result;
}
Service
package com.yuqiang.shop.service.impl;
import com.yuqiang.shop.entity.Page;
import com.yuqiang.shop.entity.User;
import com.yuqiang.shop.mapper.UserMapper;
import com.yuqiang.shop.service.IUserService;
import com.yuqiang.shop.utill.R;
import com.yuqiang.shop.utill.TokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* @author: OnlyOne
* @create: 2020-12-22 14:42
* @description:
**/
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
@Autowired
TokenUtil tokenUtil;
@Override
public R loginCheck(User user, HttpServletResponse response) {
User user2 = userMapper.selectByName(user.getUsername());
if (user2 == null) {
return R.error().message("该用户不存在!");
}
if (!user2.getPassword().equals(user.getPassword())) {
return R.error().message("密码错误!");
}
String token = tokenUtil.generateToken(user2);
Cookie cookie = new Cookie("token", token);
// 设置cookie的作用域:为”/“时,以在webapp文件夹下的所有应用共享cookie
cookie.setPath("/");
response.addCookie(cookie);
return R.ok().message("登录成功!");
}
}
这样当用户成功登录时,便会获得一个名为token的cookie,以后用户所有的请求都会带上这个token。如果请求中没有token(未登录或者token对应的cookie过期)便会重新向到登录页面,重新登陆。