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

1. 场景描述

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

2. 基础项目构建

  1. 创建SpringBoot项目

在这里插入图片描述

  1. 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.drama.security</groupId>
    <artifactId>drama-security-base</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
    </parent>

    <dependencies>
<!--        SpringBoot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
<!--        Spring Web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--      jdbc   -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
<!--        Spring Security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
<!--        lombok 用于简化开发 如不需编写实体getter/setter-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>
<!--        常用工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.2</version>
        </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;
    /**
     * <pre>
     *     描述:可以对应数据库用户表里的字段
     * </pre>
     */
    private String id;
    private String username;
    private String password;
    private Boolean disabled;//账号是否启用
    private Boolean valid; //账号是否过期
    private Boolean locked;//账号是否锁定
    private Boolean smsTimeout;//短信密码是否到期

    /**
     * <pre>
     *     描述:用来为用户分配权限,配置类会根据权限来限制访问,产生不同结果
     * </pre>
     */
    @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
<!DOCTYPE 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 Login</h1>
                <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>
<!--                输入框name属性指定为username    -->
                    <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>
<!--                    输入框name属性指定为password-->
                    <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">Login</button>
            </div>
        </form>
    </div>
</div>
</body>
</html>

6.测试

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

[

  • 请求成功,返回结果

在这里插入图片描述

发布了5 篇原创文章 · 获赞 1 · 访问量 214

猜你喜欢

转载自blog.csdn.net/drama_CJL/article/details/104221891