SpringSecurity实战(二)自定义登录页面和

开篇

上一篇(在项目里面引入spring-security会发生什么)文章中, 我们在搭建的spring-boot 工程中引入了 Spring-seccurity 依赖,并且没有做多余的配置.

当项目启动成功后,我们通过浏览器访问测试接口时,页面跳转到了 security 内置的一个默认登陆页,当我们输入 security 提供的用户名和密码,并且登陆成功后,就获取到了接口的返回值。

那么问题来了! 我们如何自定义自己的登录页面,以及如何设置自己的用户名和密码呢?

正文

创建工程

为了不影响之前的工程,我们这里新建一个模块 spring-security-02

之前的工程名是 spring-security-01.

引入依赖

本文的依赖跟前面一样

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo-02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-02</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

复制代码

添加 SpringSecurity 配置类

既然我们需要自定义一些内容,必定要通过一些额外配置来实现。所以这里我们引入了spring-security 的配置类


package com.example.demo.config;

import com.example.demo.config.service.UserDetailServiceImpl;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable();// 必须有, 不然会 403 forbidden

        http.formLogin()
                .loginPage("/loginPage.html")// 自定义登录页
                .loginProcessingUrl("/form/login");// 自定义登录 action, 名字随便起
                // passwordParameter("password") 配置 form 表单 密码的 name 属性值
                // usernameParameter("username") 配置 form 表单 用户名的 name 属性值

        // 访问 "/form/login", "/loginPage.html"   放行
        http.authorizeRequests().antMatchers("/form/login", "/loginPage.html").permitAll()
                .anyRequest().authenticated();
    }

    /**
     * 配置 用户登录处理类
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /* 将用户信息存储到内存中
           实际上不会这样做,了解下即可
        auth.inMemoryAuthentication()
                .withUser("zhangsan")
                .password(passwordEncoder().encode("123456"))
                .authorities("admin");*/

        auth.userDetailsService(userDetailsService());
    }

    /**
     * 自定义登录校验处理
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailServiceImpl();
    }


    /**
     * 加密工具
     * 2.x 版本的 spring-security-starter 必须加上
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


}


复制代码

UserDetailService 接口

通过重写 UserDetailService 接口的 loadUserByUsername 方法来实现登录校验逻辑,在 loadUserByUsername 方法里我们可以读取数据库,来校验我们的用户是否存在。最后将我们实现的类配置到上面的 configure 方法中。

package com.example.demo.config.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 自定义登录处理逻辑
 */
public class UserDetailServiceImpl implements UserDetailsService {

/*    @Autowired
    private PasswordEncoder passwordEncoder;*/

    /**
     * @param username 登录页面输入的用户名
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // TODO 根据 username 去用户表查询出来用户的信息,然后进行验证

        // 验证成功后,返回Spring-Security 提供的 User 对象
        // 对应三个构造参数依次是: 1.用户名 2.密码(经过 passwordEncoder 加密后的密码) 3.权限列表
        return new User(username, "$2a$10$g1gzj4KvMNY1kMZT1xDx9ufLuaDvCFDpX.PdETx85zQwXI/Mn4ttC", AuthorityUtils.createAuthorityList("admin"));
    }

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));// $2a$10$g1gzj4KvMNY1kMZT1xDx9ufLuaDvCFDpX.PdETx85zQwXI/Mn4ttC
    }
}


复制代码

其他需要注意的点

  • configure(HttpSecurity http) 方法中需要加上 http.csrf().disable(); 目前先不要问为什么,加上就完事了。

  • 当我们重写了 config 方法后,spring-security 就不会拦截我们要访问的资源了,所以需要重新配置下

http.authorizeRequests().antMatchers("/form/login","/loginPage.html").permitAll().anyRequest().authenticated();

  • 当我们定义了 UserDetailService,应用启动时控制台就不会打印默认密码了。
  • 高版本的 security 必须要配置密码加密工具类。不然会报错

passwordEncoder is null

总结

spring-security 提供的功能特别多,并且基本都是以配置为主, 我们需要根据我们的需求, 选择性地去使用它提供的配置选项.

在不了解的情况下, 不要盲目地把别人的配置拷贝到自己的项目中,一个成型的案例,里面的配置往往都是一大堆,根本无从看起.