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.
- 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
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
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