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
/**
* @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過期)便會重新向到登錄頁面,重新登陸。