SpringSecurity通过自定义页面实现用户密码认证

1. 场景描述

  1. 功能实现:基于SpringSecurity实现自定义登录页面进行用户认证
  2. 步骤一:配置自定义用户认证逻辑
  3. 步骤二:配置自定义密码加密解密规则并实现校验逻辑
  4. 步骤三:配置自定义登录页面配置

2. 基础项目构建

  1. 创建SpringBoot项目

SpringSecurity通过自定义页面实现用户密码认证_第1张图片

  1. 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>
  1. 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
  1. 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 编写安全配置类

  1. 使用代码+注解的方式编写配置文件,配置类继承SpringSecurity提供的WebSecurityConfigurerAdapter,并重写configure(HttpSecurity http)方法
  2. httpSecurity进行配置
    1. 设置为表单登录
    2. 拦截所有请求,并且需要进行认证才能通过
  • WebSecurityConfiguration.java
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() //表单登录
             .and()
                .authorizeRequests()
                .anyRequest() //拦截所有请求
                .authenticated();//都需要进行认证
    }
}

3.2 编写用户认证核心类

  1. 用户认证的过程由两个部分组成**(UserDetails、UserDetailsService)**:
    1. UserDetails接口,实现该接口的实体可以作为用户认证过程的媒介,接口提供四个核心方法
      1. isAccountNonExpired(),在该方法中可以编写判断账号是否过期的业务逻辑,返回布尔值;
      2. isAccountNonLocked(),在该方法中可以编写判断账号是否锁定的业务逻辑,返回布尔值;
      3. isCredentialsNonExpired(),在该方法中可以编写判断账号的密码是否过期的业务逻辑,返回布尔值
      4. isEnabled(),在该方法中可以编写判断账号是否启用的业务逻辑,返回布尔值
      5. 如果实际业务没有过期、锁定等业务逻辑的话可以直接写死,让方法返回固定的结果,避免springSecurity拦截链进行校验,导致抛出异常;
    2. 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 {
        //编写逻辑,根据入参username从数据库中获取对应的用户信息
        //如 userService.getByUsername(username);
        //假设已经从数据库获取到的用户信息封装到userDetail中,并返回
        DramaUserDetail userDetail = new DramaUserDetail("1", "admin", "123456", true, true, false, true);
        return userDetail;
    }
}

4.步骤二:配置密码加密解密并实现校验逻辑

4.1 PasswordEncoder密码解析器

  1. SpringSecurity实现密码加密解密及密码校验的过程主要通过实现PasswordEncoder接口来完成,该接口提供了三个方法:
    • encode(),把参数按照特定的解析规则进行解析
    • matches(),验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
    • upgradeEncoding(),如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false
  2. Spring默认提供了许多内置密码解析器,详细可以查看PasswordEncoder的具体实现类,了解不同的密码解析器的加解密规则
  3. 这里使用BCryptPasswordEncoder作为密码解析器,是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器,对 bcrypt 强散列方法的具体实现, 是基于 Hash 算法实现的单向加密。可以通过 strength 控制加密强度,默认 10.

4.2 代码实现密码加解密与校验

  1. WebSecurityConfiguration.java,在配置文件中创建一个密码解析器Bean;
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
  1. DramaUserDetailsService.java,在新增一个用户或修改用户信息的时候,注入密码解析器,通过encoder()方法对密码进行加密;
    1. 这里直接在DramaUserDetailsService中模拟新增用户,对密码进行加密的过程
    2. 然后从数据库根据用户名查出用户信息,进行返回
@Component
public class DramaUserDetailsService implements UserDetailsService {
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1. 创建一个用户信息用来模拟保存一个新用户信息
        //对用户保存的密码进行加密后再保存到数据库
        String password=passwordEncoder.encode("123456");//加密过程
        DramaUserDetail userDetail = new DramaUserDetail("1", password, password, true, true, false, true);
        //2. 保存用户信息后,再从数据库获取用户信息出来,并返回
        return userDetail;
    }
}

5. 配置自定义登录页面配置

  1. WebSecurityConfiguration.java,在配置类中的configure方法中进行指定登录页面、登录接口等信息
  2. 需要注意的内容点:
    1. **自定义页面存放的位置:**必须在src/main/resources/resources下面
    2. **放行登录页面路径:**指定了登录页面之后,按原先的配置会对所有请求进行拦截,需要放行访问登录页面的路径请求
    3. **指定登录接口路径:**需要指定登录校验接口路径,配置中的路径必须与页面form表单的action属性保持一致
    4. **关闭跨域拦截:**为了测试方便,这里不叙述关于跨域的问题,直接将其关闭
  • WebSecurityConfiguration.java
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() //表单登录
                //指定自定义登录页面
                .loginPage("/loginPage.html")
                //配置登录接口路径,需要与页面form表单的action保持一致
                .loginProcessingUrl("/authentication/login")
             .and()
                .authorizeRequests()
                //指定路径,放行对登录页面路径的拦截
                .antMatchers("/loginPage.html").permitAll()
                .anyRequest() //拦截所有请求
                .authenticated();//都需要进行认证
    }
}
  • loginPage.html

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

  1. 启动springBoot项目
  2. 打开浏览器,输入 localhost:8081/order/list/1,页面将自动跳转到自定义登录页面
    1. 如果页面出现404、频繁重新向、csrf的问题请参照第五点的注意事项进行相关配置
  3. 输入在DramaUserDetailsService类中,配置的账号和加密前的密码
  4. 登录成功,成功访问接口看到返回的json数据
  • 登陆页面

SpringSecurity通过自定义页面实现用户密码认证_第2张图片

  • 请求成功,返回结果

SpringSecurity通过自定义页面实现用户密码认证_第3张图片

你可能感兴趣的:(SpringSecurity)