上一节,我们介绍了 Spring Security 的基本认证组件,本节我们介绍最常见的认证方式「密码认证」的实现方法。
用户名、密码认证被广泛应用于 PC 端的 Web 应用和客户端应用,比如登陆网站,又比如 QQ 桌面客户端。
从认证数据源角度分类,也可以将认证分为:
本小节实例开发环境:
本小节所使用的实例代码是基于 Spring 官网中提供的最小化 HelloWorld 模板创建,请点此下载完整的 HelloWorld 模板压缩包。
<?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">
略...
<groupId>imooc.springsecurity</groupId>
<artifactId>UsernamePasswordSample</artifactId>
<version>0.0.1-SNAPSHOT</version>
略...
</project>
修改启动类,修改其包名为 imooc.springsecurity.usernamepassword
,修改类名为 UsernamePasswordSample
;
创建测试页面,返回登录用户信息
新建 src/main/java/imooc/springsecurity/usernamepassword/controller/UserController.java
。
package imooc.springsecurity.usernamepassword.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
@RequestMapping("user")
public class UserController {
@RequestMapping("me")
private String showMe(Principal principal) {
return String.format("当前登录用户为:「%s」", principal.getName());
}
}
mvn spring-boot:run
,如看到以下输出代表配置正确。Spring Security 支持从 HTML 的 Form 表单形式提交登录用户信息。
表单认证可分为以下步骤:
FilterSecurityInterceptor
对象,检测到当前用户认证未通过,应予以拒绝,并抛出 AccessDeniedException
;AccessDeniedException
被 ExceptionTranslationFilter
接收后,其认定需要发起认证流程,此时用户被要求登录,认证服务器将登录地址(默认由 LoginUrlAuthenticationEntryPoint
)返回给客户端;当用户提交登录信息,认证服务器端的 UsernamePasswordAuthenticationFilter
就会被执行。
此过程的具体执行过程如下:
UsernamePasswordAuthenticationFilter
产生 UsernamePasswordAuthenticationToken
,并存入从请求中获取的用户名、密码等信息;AuthenticationManager
用于认证;AbstractAuthenticationProcessingFilter
的执行过程一致。默认情况下,Spring Security 开启了表单认证功能。如果我们需要显式配置,可用如下方式实现。
创建 Security 配置文件: src/main/java/imooc/springsecurity/usernamepassword/config/WebSecurityConfig.java
,并在其中添加 http.formLogin(withDefaults())
的配置,完整代码如下:
package imooc.springsecurity.usernamepassword.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.formLogin(Customizer.withDefaults());
}
}
访问 http://localhost:8080/user/me ,网页会自动跳转到登录页面。
提交登录后,通过认证,我们将在浏览器看到当前登录的用户名。
当前登录用户为:「user」
默认情况下,表单登录的跳转地址是 /login
,登录参数中用户名变量名为 username
,密码变量名为 password
。如果我们需要修改这些配置信息,可以通过如下方式实现:
在 configure(HttpSecurity http)
方法中,为 http
的 formLogin
项修改配置。
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll() // 表单认证页面不需要权限
.anyRequest().authenticated(); // 其他页面需要登录用户才能访问
http.formLogin()
.loginPage("/login") // 自定义表单认证页面地址
.usernameParameter("user")
.passwordParameter("pass");
http.csrf().disable(); // 关闭 csrf 以通过认证,注意,这不是最好的做法,后续章节会有介绍。
}
当然这一步中配置 /login
页面需要我们自己去实现。这里有几个需要注意的地方:
/login
,提交方法仅支持 POST
;_csrf
参数;user
;pass
;error
参数;logout
参数。为了测试上述配置,我们创建一个测试登录页:
src/main/java/imooc/springsecurity/usernamepassword/controller/LoginController.java
。package imooc.springsecurity.usernamepassword.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
@RequestMapping("/login")
public String viewLogin2() {
return "/login.html";
}
}
<form method="post" action="/login">
<input type="text" name="user">
<input type="password" name="pass">
<input type="submit" value="登录">
</form>
访问测试:http://localhost:8080/user/me ,此时跳转到我们新建的登录页面。
基本认证也是常用的认证方式。基本认证分两种场景:
实现基本认证有两种方式:
Authorization: "Basic Base64(用户名+密码)"
;在 Spring Security 中,具体的认证过程如下:
FilterSecurityInterceptor
对象,检测到当前用户认证未通过,应予以拒绝,并抛出 AccessDeniedException
;(与表单认证相同)AccessDeniedException
被 ExceptionTranslationFilter
接收后,其认定需要发起认证流程,此时用户被要求登录,认证服务器将认证头 WWW-Authenticate
(默认由 BasicAuthenticationEntryPoint
提供)返回给客户端。当客户端收到 WWW-Authenticate
头后,客户端提供用户名和密码参数用于认证。
默认情况下,Spring Security 开启了基本认证功能。如果我们需要显式配置,可用如下方式实现。
protected void configure(HttpSecurity http) {
http
// ...
.httpBasic(withDefaults());
}
数字认证在新型应用中已不建议使用,因为这种方式下,用户的敏感信息,比如密码等都需要以明文形式存在,因此数字认证方式并不安全。
数字认证对应的认证过滤器为:DigestAuthenticationFilter
。
内存认证是将用户名密码信息存储在内存之中,通过 InMemoryUserDetailsManager
方式完成认证。
内存认证添加用户的方式如下,在 WebSecurityConfig.java 类(非必须)中添加以下 Bean 定义。
@Bean
public UserDetailsService users() {
// 用户1 user 用户
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
// 用户2 admin 用户
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
注意,其中的密码字段需要符合系统加密规则。比较简单的生成方式是通过 Spring Boot CLI 工具,在控制台将密码转换为密文。
然后我们可以在登录表单中,用这里配置的用户信息完成认证。
Spring Security 支持使用数据库作为认证数据源,并且提供了默认数据模型。
使用 JDBC 数据源最简单直接的方法就是使用 Spring Security 提供的默认数据模型「users.ddl」构建认证数据库。
users.ddl 的定义如下:
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(50) not null,
enabled boolean not null
);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);
使用此数据库描述文本,在我们的数据库中创建「用户表」和「权限表」,并在 Spring Security 项目中配置 JDBC 数据源。
@Autowired
private DataSource dataSource;
@Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
如此,我们便可以用数据库中存储的用户名和密码进行登录校验了。
本节我们介绍了用户名密码认证的实现方式,主要知识点如下:
Spring Security 用户名密码认证有多种实现方式,从认证方式角度看,可以分为表单认证、基本认证和数字证书认证,其中数字证书认证已经不适用现代的互联网技术。从认证数据源角度,也可以分为内存认证、JDBC 数据库认证和 LDAP 认证。
用户名密码认证是 Spring Security 框架中最基本的认证形式。下节开始我们将讨论一种在移动应用被广泛应用的认证方式:OAuth2.0 认证。
0/1000