I heard that wechat search “Java fish” will change strong oh!
This article is in Java Server, which contains my complete series of Java articles, can be read for study or interview
(1) Preface
In individual projects, people can be authenticated through cookies and sessions. However, with the development of distributed projects, session authentication in individual projects seems to become unavailable. In a cluster project, for example, we start multiple services, and the session exists in the JVM executing the current service, so authentication information that accesses the first node is not available in the second node.
So this article will take you through the handling of distributed sessions.
(2) Single Session authentication items
I will introduce today’s content through a project. In order to save space, I will replace the simple code with words, and the core code will be put up. If you need all the code in the running process, please reply in the comment section.
First of all, I simply set up a single environment under the personnel login authentication system.
2.1 Creating a SpringBoot project
Create a new SpringBoot project and introduce related dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<! -- Mybatis database dependencies -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
Copy the code
Configure the service port, database connection mode, and some path configuration for Mybatis in application.yml
server:
port: 8189
spring:
datasource:
url: jdbc:mysql://localhost:3306/mycoding? serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
type-aliases-package:
mapper-locations: classpath:mapper/*.xml
Copy the code
2.2 Creating an Entity Class
Create a User entity class User that needs to be used:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User implements Serializable {
private String username;
private String password;
private String levelId;
private String nickname;
private String phone;
private int status;
private Timestamp createTime;
}
Copy the code
Add a database generation statement to the user table:
CREATE TABLE `user` (
`id` int(40) NOT NULL AUTO_INCREMENT,
`level_id` int(4) NOT NULL,
`username` varchar(100) NOT NULL,
`password` varchar(100) NOT NULL,
`nickname` varchar(100) NOT NULL,
`phone` varchar(100) NOT NULL,
`status` int(4) NOT NULL DEFAULT '1',
`create_time` datetime DEFAULT NULL.PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
Copy the code
2.3 Writing login logic
First, the front end sends the user name and password to the back end through POST request. First, it determines whether the user name and password match the database. If so, it inserts the current user information into the session and returns the result of successful login; otherwise, it returns the result of unsuccessful login.
First we write a BaseController to get the basic request and response information
public class BaseController {
public HttpServletRequest getRequest(a){
return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
}
public HttpServletResponse getResponse(a){
return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
}
public HttpSession getHttpSession(a){
returngetRequest().getSession(); }}Copy the code
Write UserController to inherit from BaseController and implement login logic here.
@RestController
@RequestMapping("/sso")
public class UserController extends BaseController {
@Autowired
private UserService userService;
@PostMapping("/login")
public CommonResult login(@RequestParam String username,@RequestParam String password){
// Check whether the user exists in the database
User user = userService.login(username, password);
// If yes
if(user! =null){
getHttpSession().setAttribute("user",user);
return new CommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),username+"Successful landing.");
}
return new CommonResult(ResponseCode.USER_NOT_EXISTS.getCode(),ResponseCode.USER_NOT_EXISTS.getMsg(),""); }}Copy the code
2.4 Interceptor Intercepts the unlogged state
How to determine whether the user has logged in? We need to intercept all requests except/SSO. Create an IntercepterConfiguration class that intercepts all requests except/SSO.
@Configuration
public class IntercepterConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
List list=new ArrayList();
list.add("/sso/**");
registry.addInterceptor(authInterceptorHandler())
.addPathPatterns("/ * *")
.excludePathPatterns(list);
}
@Bean
public AuthInterceptorHandler authInterceptorHandler(a){
return newAuthInterceptorHandler(); }}Copy the code
With the interceptor, we also need to process the intercepted request. The processing method is to verify whether the current session can be found. If there is a session, we will allow it, indicating that the login has been done.
@Slf4j
public class AuthInterceptorHandler implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("Enter interceptor.");
if(! ObjectUtils.isEmpty(request.getSession().getAttribute("user"))) {return true;
}
response.setHeader("Content-Type"."application/json");
response.setCharacterEncoding("UTF-8");
String result = new ObjectMapper().writeValueAsString(new CommonResult(ResponseCode.NEED_LOGIN.getCode(), ResponseCode.NEED_LOGIN.getMsg(), ""));
response.getWriter().println(result);
return false; }}Copy the code
2.5 validation
To test creating a new IndexController, get the user name from session and return it.
@RestController
public class IndexController extends BaseController{
@RequestMapping(value = "/index",method = RequestMethod.GET)
public CommonResult index(a){
User user = (User) getHttpSession().getAttribute("user");
return newCommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),user.getUsername()); }}Copy the code
To start the project, directly access localhost: port /index in Postman:
Because not login, was blocked, next login first:
Localhost: port /index:
At this point, a single login authentication service is actually complete.
(c) What if the project becomes cluster deployment?
When the user usage is slowly changed more, found that the server can not support fast, so the leadership of a command, do cluster! Then simulate a cluster (two nodes to test), check the Idea configuration to allow multiple applications to run:
Then start the project, modify the server.port port, and then start the project
I started two projects running on ports 8188 and 8189, which required nginx load balancing to polling each request to access two nodes. I’ll omit the manual access to simulate Nginx here.
At this time, there was a problem. After I logged in on port 8188, the authentication was successful, but once the request was sent to port 8189, I needed to authenticate again. Two servers is fine. If there are dozens of servers, users need to log in dozens of times. It doesn’t make sense.
(4) Distributed session
There are many ways to solve the above problem:
1. For example, on nginx, if the request policy is changed to IP matching policy, one IP will only access one node (usually not adopted).
2, for example, set up a unified authentication service, all requests go to the unified authentication (CAS, etc.). The unified authentication method of CAS is used in the project I am doing now, but it is rather complicated, so I will not introduce it here.
3. Today we introduce a convenient and practical way to implement distributed Session–SpringSession.
The principle behind SpringSessions is simple: Instead of storing sessions in the JVM, they are stored in a public place. Such as mysql, Redis. It’s obviously the most efficient in Redis.
4.1 Importing Dependencies
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Copy the code
Because you are using Redis, you need to introduce redis dependencies as well.
4.2 configure redis
Or the generic Redis configuration class, to ensure that the serialization of the transfer, this section of the generic, directly copy past good.
@Configuration
public class RedisConfiguration {
// A custom redistemplate
@Bean(name = "redisTemplate")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
// Create a RedisTemplate Object that returns key string and value Object for convenience
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// Set the JSON serialization configuration
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new
Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper=new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
// Serialization of string
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
// Key uses string serialization
template.setKeySerializer(stringRedisSerializer);
//value uses Jackson's serialization
template.setValueSerializer(jackson2JsonRedisSerializer);
// Hashkey uses string serialization
template.setHashKeySerializer(stringRedisSerializer);
// HashValue uses Jackson's serialization
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
returntemplate; }}Copy the code
4.3 Configuring SpringSession in the Configuration File
Add session storage mode and REDis IP port to application.yml:
spring:
redis:
host: 192.16878.128.
port: 6379
session:
store-type: redis
Copy the code
4.4 open springsession
Create a new configuration class RedisHttpSessionConfiguration, and set a maximum time of survival, and here is set to 1 hour
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class RedisHttpSessionConfiguration {}Copy the code
4.5 validation
With a single login, you can now access the corresponding INDEX interface directly on both clusters. And you can already see the session we plugged in in Redis.
(5) Summary
With current technology, there are many ways to implement distributed sessions. Basically, the idea is to store session data in a unified location. See you next time!