目录
5.5.2 AuthenticationRequest.java
5.6.2 SimleAuthenticationInterceptor
5.7.2 AuthenticationServiceImpl
一、概念
Session 是一种HTTP存储机制,目的是为无状态的HTTP提供的持久机制。所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SID 的不可预测性,暂且认为是安全的。这是一种认证手段。
二、认证流程
-
用户输入其登录信息
-
服务器验证信息是否正确,并创建一个session,然后将其存储在数据库中
-
服务器为用户生成一个sessionId,将具有sesssionId的Cookie将放置在用户浏览器中
-
在后续请求中,会根据数据库验证sessionID,如果有效,则接受请求
-
一旦用户注销应用程序,会话将在客户端和服务器端都被销毁
三、鉴权流程
- 流程图如下
- 在整个流程中有两个拦截器。
- 第一个拦截器AuthInteceptor是为了每一次的请求的时候都先去session中取user对象,如果session中有,就放user对象到threadlocal中。这是为了业务处理的时候能直接获取用户对象。
- 第二个拦截器AuthActionInteceptor是先看页面是否需要登录,如果不需要登录,则直接进行业务逻辑处理;如果需要登录,则判断是否已经登录(UserContext.getUser()是否为空),没有登录则重定向到登录页面,登录后是会把用户信息放到session当中的 。
四、缺点
- 1).sessio需要存在服务器内存中,这样就不能跨实例共享。当下一次请求被分发到另一个实例的时候,就会造成重新登录。
- 2).在高并发情况下,session放在内存中受限于内存的大小
- 3).session依赖于浏览器的cookie机制,对于移动客户端就很难支持。移动端使用token
五、实现代码
5.1 项目结构
5.2 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.session.test</groupId>
<artifactId>sessiontest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sessiontest</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--thymeleaf模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<!--<version>1.4.0.RELEASE</version>-->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>
5.3 application.yml
server:
port: 8080
spring:
thymeleaf:
mode: LEGACYHTML5
main:
#默认支持名称相同的bean的覆盖
allow-bean-definition-overriding: true
user:
key: SESSION_USER_KEY
5.4 login.html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head><title>用户登录</title></head>
<body>
<form action="/login" method="post">
姓名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br> <input type="submit" value="登录">
</form>
</body>
</html>
5.5 entity
5.5.1 UserDto
package com.session.test.sessiontest.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Set;
/**
* @author sujm
* @date 2021/12/22 16:50
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {
private String id;
private String username;
private String password;
private String fullname;
private String mobile;
private Set<String> authorities;
}
5.5.2 AuthenticationRequest.java
package com.session.test.sessiontest.entity;
import lombok.Data;
/**
* @author sujm
* @date 2021/12/22 16:49
*/
@Data
public class AuthenticationRequest {
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
5.6 config
5.6.1 WebAppConfugurer
package com.session.test.sessiontest.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
@Bean
SimpleAuthenticationInterceptor simpleAuthenticationInterceptor() {
return new SimpleAuthenticationInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 可添加多个
registry.addInterceptor(simpleAuthenticationInterceptor()).addPathPatterns("/r/*");
}
}
5.6.2 SimleAuthenticationInterceptor
package com.session.test.sessiontest.config;
import com.session.test.sessiontest.entity.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Component
@Slf4j
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {
@Value("${user.key}")
private String userKey;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//读取会话信息
System.out.println(userKey);
Object object = request.getSession().getAttribute(userKey);
if (object == null) {
log.info("请登录");
writeContent(response, "请登录");
return false;
}
log.info("object ={}",object);
UserDto user = (UserDto) object;
//请求的url
String requestURI = request.getRequestURI();
if (user.getAuthorities().contains("p1") && requestURI.contains("/r1")) {
return true;
}
if (user.getAuthorities().contains("p2") && requestURI.contains("/r2")) {
return true;
}
log.info("权限不足,拒绝访问");
writeContent(response, "权限不足,拒绝访问");
return false;
}
private void writeContent(HttpServletResponse response, String msg) throws IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.print(msg);
writer.close();
}
}
5.7 service
5.7.1 AuthenticationService
package com.session.test.sessiontest.service;
import com.session.test.sessiontest.entity.AuthenticationRequest;
import com.session.test.sessiontest.entity.UserDto;
public interface AuthenticationService {
UserDto authentication(AuthenticationRequest authenticationRequest);
}
5.7.2 AuthenticationServiceImpl
package com.session.test.sessiontest.service;
import com.session.test.sessiontest.entity.AuthenticationRequest;
import com.session.test.sessiontest.entity.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.*;
@Service
@Slf4j
public class AuthenticationServiceImpl implements AuthenticationService {
//用户信息
private Map<String, UserDto> userMap = new HashMap<>();
{
Set<String> authorities1 = new HashSet<>();
authorities1.add("p1");
Set<String> authorities2 = new HashSet<>();
authorities2.add("p2");
userMap.put("zhangsan", new UserDto("1010", "zhangsan", "123", "张 三", "133443", authorities1));
userMap.put("lisi", new UserDto("1011", "lisi", "456", "李四", "144553", authorities2));
userMap.put("admin", new UserDto("1012", "admin", "123", "李四", "144553",null));
}
@Override
public UserDto authentication(AuthenticationRequest authenticationRequest) {
Optional.ofNullable(authenticationRequest).orElseThrow(() -> new RuntimeException("账号信息为空"));
String password = authenticationRequest.getPassword();
String username = authenticationRequest.getUsername();
if (StringUtils.isEmpty(password) || StringUtils.isEmpty(username)) {
throw new RuntimeException("用户名密码为空");
}
UserDto userDto = getUserDto(username);
if (ObjectUtils.isEmpty(userDto)) {
throw new RuntimeException("用户信息不存在");
}
return userDto;
}
public UserDto getUserDto(String username) {
return userMap.get(username);
}
}
5.8 Controller
5.8.1 LoginController
package com.session.test.sessiontest.controller;
import com.session.test.sessiontest.entity.AuthenticationRequest;
import com.session.test.sessiontest.entity.UserDto;
import com.session.test.sessiontest.service.AuthenticationService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
/**
* @author sujm
* @date 2021/12/22 17:02
*/
@Controller
public class LoginController {
@Resource
private AuthenticationService authenticationService;
@Value("${user.key}")
private String userKey;
@GetMapping("/goLogin")
public String goLogin() {
return "user/login";
}
@PostMapping(path = "/login", produces = "text/html;charset=UTF-8")
@ResponseBody
public String login(AuthenticationRequest authenticationRequest, HttpSession session) {
UserDto userDetails = authenticationService.authentication(authenticationRequest);
System.out.println("登录" + userKey);
session.setAttribute(userKey, userDetails);
return userDetails.getUsername() + "登录成功";
}
@GetMapping(value = "logout")
@ResponseBody
public String logout(HttpSession session) {
session.invalidate();
return "退出成功";
}
}
5.8.2 UserController
package com.session.test.sessiontest.controller;
import com.session.test.sessiontest.entity.UserDto;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RestController
public class UserController {
@Value("${user.key}")
private String userKey;
/*** 测试资源1 * @param session * @return
* 登陆成功之后,直接访问这个地址测试即可
* */
@GetMapping(value = "/r/r1")
public String r1(HttpSession session) {
String fullname = null;
Object userObj = session.getAttribute(userKey);
if (userObj != null) {
fullname = ((UserDto) userObj).getFullname();
} else {
fullname = "匿名";
}
return fullname + " 访问资源1";
}
@GetMapping(value = "/r/r2",produces = {"text/html;charset=UTF-8"})
public String r2(HttpSession session) {
String fullname = null;
Object userObj = session.getAttribute(userKey);
if (userObj != null) {
fullname = ((UserDto) userObj).getFullname();
} else {
fullname = "匿名";
}
return fullname + " 访问资源2";
}
}
5.9 效果
- 启动访问 http://127.0.0.1:8080/goLogin
两个用户 zhhangsan/123,lisi/456 - 登陆成功
- 登陆成功之后,直接访问http://127.0.0.1:8080/r/r1或http://127.0.0.1:8080/r/r2有无权限截图如下
zhangsan有r1的权限,lisi有r2的权限
六、参考文章
基于jwt和session用户认证的区别和优缺点_Java笔记虾-CSDN博客 基于Session的认证方式_一个人的江湖-CSDN博客_session认证 基于session和token的身份认证方案 - 开拖拉机的蜡笔小新 - 博客园