1. 场景描述
- 功能实现:基于SpringSecurity实现自定义登录页面进行用户认证
- 步骤一:配置自定义用户认证逻辑
- 步骤二:配置自定义密码加密解密规则并实现校验逻辑
- 步骤三:配置自定义登录页面配置
2. 基础项目构建
- 创建SpringBoot项目
- pom.xml,依赖引入
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.drama.securitygroupId>
<artifactId>drama-security-baseartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.5.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
<scope>providedscope>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.1.2version>
dependency>
dependencies>
project>
- application.properties,配置文件
#数据库配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/drama_batis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=0330033
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#服务端口
server.port=8888
- OrderController.java,编写一个接口,作为测试
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/list/{id}")
@ResponseBody
public Order getOrder(@PathVariable("id")String id){
Order order = new Order(IdUtil.randomUUID(), "订单一", RandomUtil.randomDay(1, 7));
return order;
}
}
3.步骤一:配置自定义用户认证逻辑
3.1 编写安全配置类
- 使用代码+注解的方式编写配置文件,配置类继承SpringSecurity提供的
WebSecurityConfigurerAdapter
,并重写configure(HttpSecurity http)
方法
- 对
httpSecurity
进行配置
- 设置为表单登录
- 拦截所有请求,并且需要进行认证才能通过
- WebSecurityConfiguration.java
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
3.2 编写用户认证核心类
- 用户认证的过程由两个部分组成**(UserDetails、UserDetailsService)**:
- UserDetails接口,实现该接口的实体可以作为用户认证过程的媒介,接口提供四个核心方法
isAccountNonExpired()
,在该方法中可以编写判断账号是否过期的业务逻辑,返回布尔值;
isAccountNonLocked()
,在该方法中可以编写判断账号是否锁定的业务逻辑,返回布尔值;
isCredentialsNonExpired()
,在该方法中可以编写判断账号的密码是否过期的业务逻辑,返回布尔值
isEnabled()
,在该方法中可以编写判断账号是否启用的业务逻辑,返回布尔值
- 如果实际业务没有过期、锁定等业务逻辑的话可以直接写死,让方法返回固定的结果,避免springSecurity拦截链进行校验,导致抛出异常;
- UserDetailsService接口,实现该接口的service需要重写
loadUserByUsername
方法,在该方法中可以编写根据用户名从数据库获取用户信息的逻辑,并封装到上面提到的UserDetail
子类型中,如果没有自定义实现UserDetails
接口的实体也可以使用Spring默认提供的User
- DramaUserDetail.java,自定义实现用户认证对象,实现
UserDetails
接口
@Data
@AllArgsConstructor
public class DramaUserDetail implements UserDetails {
private static final long serialVersionUID = 6568115551708367212L;
private String id;
private String username;
private String password;
private Boolean disabled;
private Boolean valid;
private Boolean locked;
private Boolean smsTimeout;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return valid;
}
@Override
public boolean isAccountNonLocked() {
return locked;
}
@Override
public boolean isCredentialsNonExpired() {
return smsTimeout;
}
@Override
public boolean isEnabled() {
return disabled;
}
}
- DramaUserDetailsService.java,实现
UserDetailsService
接口,实现用户身份认证
@Component
public class DramaUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DramaUserDetail userDetail = new DramaUserDetail("1", "admin", "123456", true, true, false, true);
return userDetail;
}
}
4.步骤二:配置密码加密解密并实现校验逻辑
4.1 PasswordEncoder密码解析器
- SpringSecurity实现密码加密解密及密码校验的过程主要通过实现
PasswordEncoder
接口来完成,该接口提供了三个方法:
- encode(),把参数按照特定的解析规则进行解析
- matches(),验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
- upgradeEncoding(),如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false
- Spring默认提供了许多内置密码解析器,详细可以查看
PasswordEncoder
的具体实现类,了解不同的密码解析器的加解密规则
- 这里使用
BCryptPasswordEncoder
作为密码解析器,是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器,对 bcrypt 强散列方法的具体实现, 是基于 Hash 算法实现的单向加密。可以通过 strength 控制加密强度,默认 10.
4.2 代码实现密码加解密与校验
- WebSecurityConfiguration.java,在配置文件中创建一个密码解析器Bean;
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
- DramaUserDetailsService.java,在新增一个用户或修改用户信息的时候,注入密码解析器,通过
encoder()
方法对密码进行加密;
- 这里直接在DramaUserDetailsService中模拟新增用户,对密码进行加密的过程
- 然后从数据库根据用户名查出用户信息,进行返回
@Component
public class DramaUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password=passwordEncoder.encode("123456");
DramaUserDetail userDetail = new DramaUserDetail("1", password, password, true, true, false, true);
return userDetail;
}
}
5. 配置自定义登录页面配置
- WebSecurityConfiguration.java,在配置类中的
configure
方法中进行指定登录页面、登录接口等信息
- 需要注意的内容点:
- **自定义页面存放的位置:**必须在
src/main/resources/resources
下面
- **放行登录页面路径:**指定了登录页面之后,按原先的配置会对所有请求进行拦截,需要放行访问登录页面的路径请求
- **指定登录接口路径:**需要指定登录校验接口路径,配置中的路径必须与页面
form
表单的action
属性保持一致
- **关闭跨域拦截:**为了测试方便,这里不叙述关于跨域的问题,直接将其关闭
- WebSecurityConfiguration.java
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/loginPage.html")
.loginProcessingUrl("/authentication/login")
.and()
.authorizeRequests()
.antMatchers("/loginPage.html").permitAll()
.anyRequest()
.authenticated();
}
}
<html lang="en">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<head>
<meta charset="UTF-8">
<title>登陆页面title>
head>
<body>
<div class="container">
<div class="row">
<form action="/authentication/login" method="post">
<div class="jumbotron">
<h1 class="display-4">Drama Security Loginh1>
<p class="lead">This is a simple spring security authenticate Demo.p>
<hr class="my-4">
<div class="input-group input-group-sm mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-sm">用户名span>
div>
<input type="text" name="username" class="form-control" aria-label="Sizing example input"
aria-describedby="inputGroup-sizing-sm">
div>
<div class="input-group input-group-sm mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-sm-password">密码span>
div>
<input type="password" name="password" class="form-control" aria-label="Sizing example input"
aria-describedby="inputGroup-sizing-sm">
div>
<button class="btn btn-primary btn-lg" type="submit" role="button">Loginbutton>
div>
form>
div>
div>
body>
html>
6.测试
- 启动springBoot项目
- 打开浏览器,输入
localhost:8081/order/list/1
,页面将自动跳转到自定义登录页面
- 如果页面出现404、频繁重新向、csrf的问题请参照第五点的注意事项进行相关配置
- 输入在
DramaUserDetailsService
类中,配置的账号和加密前的密码
- 登录成功,成功访问接口看到返回的json数据