Skip to content

8 Spring Security & Session-based Authentication

What is Spring Security?

Spring Security provides security services for Java EE-based enterprise software applications. There is a particular emphasis on Spring based applications.

Authentication

  • The process of checking credentials and making sure the current logged user is who they claim to be.

Authorization

  • The process of deciding whether a current logged user is allowed to perform an action within your application.

Why do we need authentication/authorization?

  • Access control: user can only access data that is authorized to that user.
  • Logging: record user-specific activity for book keeping, statistics, etc.

Screenshot 2023-12-14 at 00.56.16

  • Once a user is authenticated, the server uses a session to maintain his/her status. The session object is stored on the server-side, on the server-side, only session ID is returned to the client-side. Users need to provide a session ID to access resources that require authentication.
  • When a user logs out, the session is destroyed. Next time a user comes, he/she has to authenticate again to get a new session.

https://sherryhsu.medium.com/session-vs-token-based-authentication-11a6c5ac45e4

Enable Spring Security

Screenshot 2023-12-14 at 01.34.59

Add below dependencies to the pom.xml file

        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>5.5.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.5.2</version>
        </dependency>

The first thing you need to do is add the following filter declaration to your web.xml file

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sum.com/dtd/web-app_2_3.dtd">
<web-app>

    <display-name>OnlineOrder Website</display-name>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <!--filter-mapping 是 filter 和 URL 之间的映射关系。 -->

    <!--filter-class 是 filter 的类名,它必须和 filter 中的 <filter-class> 标签的值一致。 -->
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--filter-name 是 filter 的名字,它必须和 filter 中的 <filter-name> 标签的值一致。 -->
    <!--url-pattern 是 URL 的匹配模式,它必须和 filter 中的 <url-pattern> 标签的值一致。 -->

    <servlet>
        <servlet-name>onlineOrder</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>0</load-on-startup> <!-- 0 means load on first request -->
        <!-- 1 means load on startup -->
    </servlet>

    <servlet-mapping>
        <servlet-name>onlineOrder</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

Add SecurityConfig.java under onlineOrder package

package com.eve.onlineOrder;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

import javax.sql.DataSource;

// @EnableWebSecurity 用于启用 Spring Security。
// Spring Security 是一个安全框架,用于实现认证和授权。
// 认证用于验证用户的身份,例如,用户登录时,需要验证用户的身份。
// 授权用于控制用户对资源的访问权限,例如,当用户访问 http://localhost:8080/checkout 时,需要验证用户是否具有访问权限。
// 因此,需要在 SecurityConfig 类中实现认证和授权的业务逻辑。

//https://docs.spring.io/spring-security/site/docs/5.5.5/reference/html5/

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // WebSecurityConfigurerAdapter 是一个抽象类,用于实现认证和授权的业务逻辑。
    // 因此,需要继承 WebSecurityConfigurerAdapter 类,并实现 configure 方法。
    @Autowired // @Autowired 用于自动装配,将 DataSource 注入到 SecurityConfig 中。
    private DataSource dataSource;

    @Override // @Override 用于表示重写父类的方法。
    protected void configure(HttpSecurity http) throws Exception{
        // configure 方法用于实现认证和授权的业务逻辑。
        // 例如,当用户访问 http://localhost:8080/checkout 时,需要验证用户是否具有访问权限。
        // 因此,需要在 configure 方法中实现认证和授权的业务逻辑。
        http
                .csrf().disable() // csrf 用于防止跨站请求伪造。 disable() 用于禁用 csrf。
                .formLogin() // formLogin 用于实现基于表单的认证。
                .failureForwardUrl("/login?error=true"); // failureForwardUrl 用于指定认证失败时的跳转 URL。
        http
                .authorizeRequests() // authorizeRequests 用于实现基于 URL 的授权。
                .antMatchers("/order/*", "/cart","/checktou").hasAnyAuthority("ROLE_USER")
                .anyRequest().permitAll(); // authorizeRequests 用于实现基于 URL 的授权。
        // antMatchers 用于指定 URL,hasAnyAuthority 用于指定访问该 URL 需要的权限。
        // 例如,antMatchers("/order/*", "/cart").hasAnyAuthority("ROLE_USER")
        // 表示访问 /order/* 和 /cart 时,需要具有 ROLE_USER 权限。
        // anyRequest 用于指定所有的请求,permitAll 用于指定所有的请求都允许访问。
        // 例如,anyRequest().permitAll() 表示所有的请求都允许访问。
    }

    @Override // @Override 用于表示重写父类的方法。
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        // protected 是一个访问修饰符,用于指定该方法的访问权限。
        // 它与public、private、default等访问修饰符不同,它可以修饰类的成员变量、成员方法和构造方法。
        // protected 修饰的成员变量、成员方法和构造方法可以被同一个包中的其他类访问,也可以被不同包中的子类访问。
        // 例如,protected void configure(AuthenticationManagerBuilder auth) throws Exception
        // 表示该方法可以被同一个包中的其他类访问,也可以被不同包中的子类访问。
        // configure 方法用于实现认证的业务逻辑。
        auth
                .jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery("SELECT email, password, enabled FROM customers WHERE email=?")
                .authoritiesByUsernameQuery("SELECT email, authority FROM authorities WHERE email=?");
        // jdbcAuthentication 用于实现基于 JDBC 的认证。
        // usersByUsernameQuery 用于指定查询用户的 SQL 语句。
        // authoritiesByUsernameQuery 用于指定查询用户权限的 SQL 语句。
        // 例如,usersByUsernameQuery("SELECT email, password, enabled FROM customers WHERE email=?")
        // 表示查询 email、password 和 enabled 字段,其中 email 字段用于查询用户,password 字段用于查询密码,enabled 字段用于查询用户是否可用。
        // authoritiesByUsernameQuery("SELECT email, authority FROM authorities WHERE email=?")
    }

    @SuppressWarnings("deprecation") // @SuppressWarnings 用于抑制编译器警告。
    @Bean // @Bean 用于声明当前方法的返回值是一个 Bean。
    // Bean 是 Spring 的核心,它是由 Spring IoC 容器管理的对象。
    // Spring IoC 容器是一个管理 Bean 的容器,它负责创建 Bean 对象、装配 Bean 之间的依赖关系、配置 Bean 的属性等。
    public static NoOpPasswordEncoder passwordEncoder() {
        // NoOpPasswordEncoder 用于实现密码的编码。
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
        // getInstance() 用于获取 NoOpPasswordEncoder 的实例。
        // NoOpPasswordEncoder 是一个密码编码器,它不对密码进行任何编码。
        // 例如,当用户注册时,用户输入的密码为 123456,NoOpPasswordEncoder 不对密码进行任何编码,直接将密码保存到数据库中。
        // 因此,当用户登录时,用户输入的密码为 123456,NoOpPasswordEncoder 不对密码进行任何编码,直接将密码与数据库中的密码进行比较。
        // 如果两者相同,则认证成功;否则,认证失败。
    }
}

Add SignInController.java under controller package

package com.eve.onlineOrder.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

import static com.fasterxml.jackson.databind.type.LogicalType.Map;

// Controller 层用于接收请求,调用 Service 层的方法,将数据返回给前端页面。
@Controller // @Controller 用于标注控制层组件,即 Controller 组件。
public class SignInController {

    // ObjectMapper 用于将 Java 对象转换为 JSON 字符串。
    private final ObjectMapper objectMapper = new ObjectMapper();

    // we only process the failed login request here
    // if login successfully, it will automatically redirect to the home page

    // @RequestMapping 用于映射请求,即指定请求的 URL。
    @RequestMapping("/login") // value 属性用于指定请求的 URL。例如,/login
    // 表示请求的 URL 为 http://localhost:8080/login。
    public void login(@RequestParam(value = "error") String error, HttpServletResponse response) throws Exception {
        // @RequestParam 用于获取请求参数。
        // value 属性用于指定请求参数的名称。例如,@RequestParam(value = "error")
        // 表示获取名为 error 的请求参数。
        response.setStatus(HttpStatus.UNAUTHORIZED.value()); 
        // HttpStatus 是一个枚举类,包含了所有的响应状态码。
        // HttpStatus.UNAUTHORIZED 表示响应状态码为 401。
        // response.setStatus(HttpStatus.UNAUTHORIZED.value()) 表示响应状态码为 401。
        Map<String, Object> data = new HashMap<>(); // 创建一个 Map 对象。
        data.put("message", "bad credentials");
        // put 方法用于向 Map 对象中添加数据。
        // 例如,data.put("message", "bad credentials") 表示向 Map 对象中添加名为 message,值为 bad credentials 的数据。
        // bad credentials 表示用户名或密码错误。
        response.getOutputStream()
                .println(objectMapper.writeValueAsString(data));
        // getOutputStream 方法用于获取输出流。
        // objectMapper.writeValueAsString(data) 用于将 Map 对象转换为 JSON 字符串。
        // 例如,当用户名或密码错误时,需要将错误信息返回给前端页面。
    }
}

注意配置 版本 tomcat 需要降级到9

Screenshot 2023-12-14 at 16.01.25

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>


    <groupId>com.eve</groupId>
    <artifactId>onlineOrder</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>onlineOrder</name>
    <packaging>war</packaging>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <junit.version>5.9.1</junit.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20200518</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.7</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.11.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.11.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.9</version>
        </dependency>


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.9</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.25.Final</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>5.7.7</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.7.3</version>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.4.0</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.3.1</version>
            </plugin>

        </plugins>
    </build>
</project>

web.xml:

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >


<web-app>


    <display-name>OnlineOrder WebSite</display-name>


    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>


    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>




    <servlet>
        <servlet-name>onlineOrder</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>0</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>onlineOrder</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


</web-app>

My java verison is 21

Using

Screenshot 2023-12-14 at 16.12.06

Screenshot 2023-12-14 at 16.12.53