1 session
First of all, Http protocol is stateless, which needs to be solved through Session (Session). Session technology mainly means that the server sends an ID to the browser, and the browser saves the related ID. For the server, Session is a Map structure. Find the corresponding Value of the ID from the request, which is formed between different requests. As for the storage of ID, cookies become a good carrier without interfering with the experience, storing ID in the form of key-value, where ID corresponds to JsessionID. Although cookies are useful, sometimes users will disable them in the browser, which leads to the ID cannot be stored. The general solution is to write the ID as a parameter in the URL through URL rewriting. For example: http://quguai.cn; Jsessionid = XXX, but this overwrite method will have fixed session attack, the attack is as follows:
- The hacker accesses the system first, so the server establishes the session and returns a sessionId to the browser.
- The hacker writes the sessionId into the URL and sends it to trusted users;
- When a user logs in using a rewritten URL, the server stores the user information as the value value and the sessionId as the key value.
- Hackers do not need to log in, directly through the sessionId for some private operations, that is, shared sessions;
2 Defends against fixed session attacks
The premise of the attack mode mentioned above is that after the user performs login operation, the user is stored in the value corresponding to the sessionId in the URL address. The solution is also very simple, that is, to create a new sessionId when the login request. It is enabled by default in SpringSecurity. There are four ways to defend against fixed conversations:
- None: no changes are made and the old session is used after login. (Not recommended)
- NewSession: a newSession created after login.
- MigrateSession: Creates a Session after login and copies the data in the old Session. (the default)
- ChangeSessionId: instead of creating a new session, use the session fixation attack provided by the Servlet container;
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SpringSessionBackedSessionRegistry sessionRegistry;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and().csrf().disable()
.formLogin().permitAll()
.and().sessionManagement()
.sessionFixation().none()
}
}
Copy the code
In Spring Security, you don’t have to worry about fixed session attacks even if it’s not configured, because Spring Security’s Http firewall intercepts invalid requests and automatically redirects us when we access a URL with a session.
3 Session Expiration
You can also configure requests to jump to when a session expires
.and().sessionManagement()
.invalidSessionUrl("/session/invalid") // Redirect url after Session expires
Copy the code
Or you can customize expiration policies
public class MyInvalidSessionStrategy implements InvalidSessionStrategy {
@Override
public void onInvalidSessionDetected(HttpServletRequest httpServletRequest, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("application/json; charset=utf-8");
response.getWriter().write("The Session is invalid"); }}Copy the code
.and().sessionManagement()
.invalidSessionStrategy(new MyInvalidSessionStrategy())
Copy the code
We can also modify the specific session expiration time
server:
servlet:
session:
timeout: 60s
Copy the code
If the session duration is less than one minute, the session duration is automatically corrected to one minute, which belongs to the SpringBoot configuration policy.
4 Session concurrency control
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SpringSessionBackedSessionRegistry sessionRegistry;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and().csrf().disable()
.formLogin().permitAll()
.and().sessionManagement()
.maximumSessions(1)// Maximum number of users}}Copy the code
The maximumSessions user sets the maximum number of sessions that a single user is allowed to have online at the same time. If there is no additional configuration, the new login session will kick out the old session, much like a series of communication tools such as QQ. When we want to block new sessions instead of culling them, we can configure them as follows. The actual scenario is the game login.
.and().sessionManagement()
.maximumSessions(1) // Maximum number of users
.maxSessionsPreventsLogin(true) // To organize new session connections, QQ login is False, game login is True
Copy the code
If we try to log out with an old session that is already logged in, then the new session should be logged in, but it doesn’t. It still tells us that the maximum number of sessions has been exceeded. Here, Spring Security triggers the clearing of Session information by listening to the Session destruction event, but we have not registered the relevant listener, so Spring Security cannot clear the Session information properly.
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher(a){
return new HttpSessionEventPublisher();
}
Copy the code
At this point, all the basic operations of session management are covered, but hereAnd a pitIn our actual scenario, the most important thing is to read the database to complete the corresponding operation. We use MySQL and Spring Data JPA to complete the corresponding operation in accordance with the way of completing Data authentication of the custom database in the previous article. The code is only posted below, without explanation.
package cn.quguai.entity;
@Data
@Entity
@Table(name = "t_user")
public class UserEntity implements UserDetails {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
private Boolean enable;
private String roles;
@Transient
private List<GrantedAuthority> authorityList;
public void setAuthorityList(List<GrantedAuthority> authorityList) {
this.authorityList = authorityList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorityList;
}
@Override
public String getUsername(a) {
return this.username;
}
@Override
public boolean isAccountNonExpired(a) {
return true;
}
@Override
public boolean isAccountNonLocked(a) {
return true;
}
@Override
public boolean isCredentialsNonExpired(a) {
return true;
}
@Override
public boolean isEnabled(a) {
return this.enable; }}Copy the code
package cn.quguai.service;
@Service
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByUsername(username);
if (userEntity == null) {
throw new UsernameNotFoundException("User does not exist");
}
userEntity.setPassword(passwordEncoder.encode(userEntity.getPassword()));
userEntity.setAuthorityList(AuthorityUtils.commaSeparatedStringToAuthorityList(userEntity.getRoles()));
returnuserEntity; }}Copy the code
package cn.quguai.dao;
@Repository
public interface UserRepository extends JpaRepository<UserEntity.Long> {
UserEntity findByUsername(String username);
}
Copy the code
When we use custom data to complete the operation, there will be no problem with login, but when we use session concurrency control, there will be a problem without restriction, and login is allowed. This is because SpringSecurity’s underlying storage of user information is stored in the form of HashMap, and Map uses user information as the key. When we log in using the same account, it will first determine whether to include the user information in the container, and then restrict it accordingly. However, our user information does not implement hashCode and Equals methods, so the same account will log in at the same time.
The hashmap requires that we override the equals and hashCode methods for key values
This resulted in a phenomenon that didn’t cause problems when testing using the official website example or the memory-based user model.
Specific can consult org. Springframework. Security. Core. Populated userdetails. The official implementation class User
public class UserEntity implements UserDetails {...@Override
public boolean equals(Object o) {
return o instanceof UserEntity && this.username.equals(((UserEntity) o).username);
}
@Override
public int hashCode(a) {
return this.username.hashCode(); }}Copy the code
5 Spring Session Solve the cluster Session problem
- Introducing dependent dependencies
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<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
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring? useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: LY0115..
jpa:
hibernate:
ddl-auto: update
show-sql: true
open-in-view: false
properties:
hibernate:
enable_lazy_load_no_trans: true
session:
store-type: redis
redis:
host: 192.1681.115.
port: 6379
Copy the code
package cn.quguai.config;
@EnableRedisHttpSession
public class HttpSessionConfig {
@Autowired
private RedisIndexedSessionRepository sessionRepository;
@Bean
public SpringSessionBackedSessionRegistry springSessionBackedSessionRegistry(a){
return newSpringSessionBackedSessionRegistry<>(sessionRepository); }}Copy the code
Note does not need the front HttpSessionEventPublisher Bean here, because here the Session management to SpringSession to management, namely relevant cleaning work can be automatically help us to finish the Session.
package cn.quguai.config;
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SpringSessionBackedSessionRegistry sessionRegistry;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and().csrf().disable()
.formLogin().permitAll()
.and().sessionManagement()
.maximumSessions(1)// Maximum number of users
.maxSessionsPreventsLogin(true); .sessionRegistry(sessionRegistry); }}Copy the code