Last time, Xiao Hei introduced four kinds of distributed consistent Session implementation methods in his article, among which the most commonly used is the back-end centralized storage scheme, so that even if the Web application restarts or expands, there is no risk of Session loss.

Today, we will use this method to transform the Session storage mode and store it in Redis.

Implementation scheme

Let’s start by thinking about how we can implement centralized back-end Session storage ourselves without relying on any framework.

Here we assume that except for some pages on our website, such as the home page, you need to log in to access any other pages.

If we need to implement this requirement, we need to authenticate each request. The purpose of authentication is to determine whether the user is logged in and determine the role of the user.

If the user is not logged in, we need to force the request to the login page for logging in.

After a user logs in, the user information obtained during login needs to be stored in the Session. In this way, subsequent authentication requests only need to determine whether the Session exists.

Once you know the whole process, it’s actually not that hard to implement.

We can use aOP-like principles to determine whether user information exists in the Session after each request comes in, and jump to the login page if not.

The whole process is as follows:

We can do this with the Servelt Filter, but Spring already does this for us, so we don’t have to reinvent the wheel.

We can use Spring-Session and Spring-Security to implement the process of the above website.

Spring-session is a set of implementations provided by Spring to manage user sessions. After using Spring-Session, the default WEB container, such as Tomcat, The generated Session will be taken over by Spring-Session.

In addition, Spring-Session also provides several common back-end storage implementations, such as Redis, databases, etc.

With Spring-Session, it just solves the problem of Session backend centralized storage. However, we also need login authorization in the above process, which we can implement using Spring-Security.

Spring-security maintains a unified login authorization mode and can be used in conjunction with Spring-Session. After a user is authorized to log in, the obtained user information is automatically stored in spring-Session.

Well, without further ado, let’s look at the implementation code.

The following implementation is implemented using Spring Boot, and the spring-boot version is 2.3.2.RELEASE

Spring Session

First we introduce the Spring Session dependency. Here we use Redis to store Session information centrally, so we need the following dependencies.

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
Copy the code

If it is not a Spring Boot project, the main dependencies need to be introduced:

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-redis</artifactId>
  <version>2.3.0. RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-core</artifactId>
  <version>2.3.0. RELEASE</version>
</dependency>
Copy the code

After introducing dependencies, we first need to add session-related configuration to application.properties:

Session storage mode
spring.session.store-type=redis
## Session expiration time (default unit: s
server.servlet.session.timeout=600
## Session stored in the Redis key prefix
spring.session.redis.namespace=test:spring:session
## Redis configuration
spring.redis.host=127.0.0.1
spring.redis.password=* * * *
spring.redis.port=6379
Copy the code

After the configuration is complete, Spring Session starts to manage Session information. Let’s test it:

@ResponseBody
@GetMapping("/hello")
public String hello(a) {
    return "Hello World";
}
Copy the code

After accessing the above address, access Redis and see the stored Session information.

Another Redis DeskTop Manager (Another Redis DeskTop Manager, Another Redis DeskTop Manager, Another Redis DeskTop Manager)

Github.com/qishibo/ano…

Internet speed is not good students, public number inside reply: “redis”, obtain installation package.

By default, sessions use the HttpSession serialization method by default, which may not seem intuitive. We can modify this to json serialization and store it in Redis.

@Configuration
public class HttpSessionConfig implements BeanClassLoaderAware {


    private ClassLoader loader;

    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer(a) {
        return new GenericJackson2JsonRedisSerializer(objectMapper());
    }

    /**
     * Customized {@link ObjectMapper} to add mix-in for class that doesn't have default
     * constructors
     *
     * @return the {@link ObjectMapper} to use
     */
    private ObjectMapper objectMapper(a) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModules(SecurityJackson2Modules.getModules(this.loader));
        return mapper;
    }


    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.loader = classLoader; }}Copy the code

The Redis key values after modification are as follows:

Ps: here Redis key meaning, the next analysis of the source code, and then do the analysis.

Spring Session also has an @enableredisHttpSession annotation on which you can configure the Spring Session-related configuration.

@EnableRedisHttpSession(redisNamespace = "test:session")
Copy the code

Note that using this annotation will invalidate the application.properties Session configuration, meaning that Spring Session will use the annotation configuration directly.

Here the small black compares recommends everybody to use the configuration file way.

Ok, so Spring Session is done here.

Spring security

Above we integrate Spring Session, complete Session unified Redis storage. The next step is to implement the requested login authentication.

In this step, we use Spring Security to implement unified login authentication services, the same framework as Shiro, where we use the Spring family bucket.

First we need the corresponding dependency of the dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
Copy the code

With the above dependencies in place, a random password will be generated when the application starts, and all requests will be redirected to a Spring Security page.

Here we need to implement the login page for our business, so we need to customize the login verification logic.

In Spring Security we just need to implement the UserDetailsService interface and rewrite the loadUserByUsername method logic.

@Service
public class UserServiceImpl implements UserDetailsService {

    @Autowired
    PasswordEncoder passwordEncoder;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // For simplicity, check directly inside
        String uname = "admin";
        String passwd = "1234qwer";

        // If it is a formal project, we need data from the database, and then verify, the form is as follows:
        // User user = userDAO.query(username);

        if(! username.equals(uname)) {throw new UsernameNotFoundException(username);
        }
        // Encapsulate the User object defined by Spring Security
        return User.builder()
                .username(username)
                .passwordEncoder(s -> passwordEncoder.encode(passwd))
                .authorities(new SimpleGrantedAuthority("user")) .build(); }}Copy the code

The above code, here mainly in memory fixed user name and password, real environment, we need to change to query user information from the database.

Next we need to configure UserServiceImpl into Spring Security.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    UserServiceImpl userService;

    @Bean
    public PasswordEncoder passwordEncoder(a) {
        return new BCryptPasswordEncoder();
    }

    /** * Verify login information with custom user service **@param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // The user login information is verified using the custom userService
        // Also note that password encryption and authentication need to be used in the same wayauth.userDetailsService(userService).passwordEncoder(passwordEncoder()); }}Copy the code

In the above configuration, the password part is encrypted with BCrypt algorithm. It should be noted that encryption and decryption need to use the same way.

Then we need to implement a custom login page, so instead of writing it ourselves, use the Spring-session-data-redis page.

<! DOCTYPEhtml>
<html xmlns:th="https://www.thymeleaf.org" xmlns:layout="https://github.com/ultraq/thymeleaf-layout-dialect"
      layout:decorate="~{layout}">
<head>
    <title>Login</title>
</head>
<body>
<div layout:fragment="content">
    <! -- Custom login request -->
    <form name="f" th:action="@{/auth/login}" method="post">
        <fieldset>
            <legend>Please Login -</legend>
            <div th:if="${param.error}" class="alert alert-error">Invalid username and password.</div>
            <div th:if="${param.logout}" class="alert alert-success">You have been logged out.</div>
            <label for="username">Username</label>
            <input type="text" id="username" name="username"/>
            <label for="password">Password</label>
            <input type="password" id="password" name="password"/>
            <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
            <label>remember me: </label>
            <input type="checkbox" name="remember-me"/>
            <div class="form-actions">
                <button type="submit" class="btn">Log in</button>
            </div>
        </fieldset>
    </form>
</div>
</body>
</html>
Copy the code

Note that the request address of the form is /auth/login, which needs to be changed in the configuration below. By default, the address of the login request needs to be /login.

We then add the corresponding configuration method to the SecurityConfig class above:

/** * Custom processing Login processing **@param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {

    http.authorizeRequests((authorize) -> authorize
            .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // Static resources such as CSS and JS do not require login authentication
            .anyRequest().permitAll() // Other pages require login authentication
    ).formLogin((formLogin) -> formLogin  // Customize the login page
            .loginPage("/login") / / login page
            .loginProcessingUrl("/auth/login") // Customize the login request address
            .permitAll()// The login page does not need authentication.
    ).logout(LogoutConfigurer::permitAll // Exit the page
    ).rememberMe(rememberMe -> rememberMe
            .rememberMeCookieName("test-remember") // Custom remember my cookie name
            .key("test") / / salt value
            .tokenValiditySeconds(3600 * 12)) // Remember me, locally generated cookies contain user information

    ;
}
Copy the code

This method may be a long one, but it’s important to explain:

  • authorizeRequestsMethods need to specify which pages need to be authenticated. Here we specify that static resources do not need login authentication
  • formLoginMethod to modify the default login page address and the login request address.
  • logoutHere you can configure the relevant configuration for logout.
  • rememberMeAfter this function is enabled, when the internal Session expires, users can also implement the login exemption function based on the Cookie information in the browser.

Finally, we need to configure the jump address of some pages:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        / / home page
        registry.addViewController("/").setViewName("home");
        // Jump to the home page after login
        registry.addViewController("/login").setViewName("login"); }}Copy the code

Ps: complete source code, please reply 20200807 to obtain the address.

conclusion

So far, we have integrated Spring-Session and Spring-Security to complete the login authentication function of the website. As you can see from this example, with the introduction of these two frameworks, we only need to develop according to the Spring specification, and we don’t need to implement the other complicated implementation principles ourselves, which is really convenient.

The above is just a simple small example, xiaohei is just throwing a diversion jade, real development may need to modify the configuration of more, here need to use friends in in-depth research.

reference

  1. Creaink. Making. IO/post/Backen…
  2. Github.com/spring-proj…

Welcome to pay attention to my public account: procedures to get daily dry goods push. If you are interested in my topics, you can also follow my blog: studyidea.cn