springboot集成security(认证)

Posted 364.99°

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot集成security(认证)相关的知识,希望对你有一定的参考价值。

目录

1. 依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

加入 spring-boot-starter-security 依赖之后,不需要任何配置,启动项目,就会出现一个登录界面:

  • 任何访问请求都会被拦截到 /login,并且要求认证后才能访问,请求方式是 Get

  • 默认 usernameuser,每次启动都会在控制台输出一个 128Byte 的 password

  • 可通过配置文件修改默认的 usernamepassword (静态用户,适用于内部网络认证)

    spring:
      # default login url: /login
      security:
        user:
          name: dev
          # default size: 128 Byte
          password: admin
    

2. 自定义登录逻辑


1. 数据库查询

表、实体类:

@Data
public class User 
    private Integer id;
    private String name;
    private String password;


配置文件:

# application name
spring:
  # mysql
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spbt?serverTimezone=Asia/Shanghai
    username: root
    password: admin
# web service port
server:
  port: 8088
mybatis-plus:
  type-aliases-package: com.chenjy.security_demo.dto
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

mapper:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.chenjy.security_demo.mapper.UserMapper">
    <select id="getUserByName" parameterType="string" resultType="user">
        select
               id,
               name,
               password
        from
             user
        <where>
            <if test="name != null and name != ''">
                name = #name
            </if>
        </where>
    </select>
</mapper>
@Mapper
public interface UserMapper 
    User getUserByName(String name);
    List<User> getUserByName();

启动类加上 @MapperScan 注解


service:

public interface UserService 
    User getUserByName(String name);
    List<User> getUserByName();

@Service
public class UserServiceImpl implements UserService 
    @Resource
    private UserMapper userMapper;

    @Override
    public User getUserByName(String name) 
        return userMapper.getUserByName(name);
    

    @Override
    public List<User> getUserByName() 
        return userMapper.getUserByName();
    


2. security认证

要自定义 security 认证逻辑,就需要定义一个服务对象来实现 UserDetailsService 接口,并重写 loadUserByUsername 方法。


1. loadUserByUsername

loadUserByUsername

  • security 唯一的认证方法
  • 查询用户失败,会抛出 UsernameNotFoundException
@Component
public class UserDetailsServiceImpl implements UserDetailsService 
    @Resource
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        com.chenjy.security_demo.dto.User user = userService.getUserByName(username);
        if (user == null) 
            throw  new UsernameNotFoundException("用户名错误");
        
        // 匹配用户密码
        // org.springframework.security.core.userdetails.User
         User res = new User(username, user.getPassword(), AuthorityUtils.createAuthorityList());
        return res;
    

  • org.springframework.security.core.userdetails.User

  • loadUserByUsername 需要返回一个 UserDetails 的实现类,可以直接使用 User,其有两个构造器:

    public User(String username, String password, Collection<? extends GrantedAuthority> authorities)
    public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) 
    
    • username 用户名
    • password 用户密码
    • authorities 权限集合
  • security 内部会自动进行密码匹配

这时候直接启动项目,会报错:

*java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"*

这是因为 security 5.X 需要提供一个PasswordEncorder的实例


2. PasswordEncorder(不加密)

security 内部进行密码匹配的时候,一定要进行加密和解密处理。要求 IOC 容器中,必须要存在一个 PasswordEncorder 对象,提供加密和解密逻辑。

  • 可以直接客户端明文,数据库解密来匹配验证
  • 也可以客户端加密,数据库直接密文来匹配验证

因为我的数据库密码并未进行加密,所以这里,我们就直接返回字符串进行明文比对就行了。

@Component
public class MyPasswordEncoder implements PasswordEncoder 
    /**
     * @Description 收集页面的密码
     * @param rawPassword
     * @return String
    */
    @Override
    public String encode(CharSequence rawPassword) 
        return rawPassword.toString();
    

    /**
     * @Description 匹配逻辑
     * @param rawPassword 明文,页面收集的密码
     * @param encodedPassword 密文,存储在数据源中的密码
     * @return boolean
    */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) 
        return rawPassword.equals(encodedPassword);
    

encode 对页面收集到的密码进行加密,然后将加密好的密文交给 matches,与数据库密码进行比对。


3. MD5加密数据库密码

自定义加密工具类

public class PwdEncode 

    /**
     * @Description 收集
     * @param pwd
     * @return String
    */
    public static String encode(String pwd) 
        MessageDigest md5 = null;
        try 
            md5 = MessageDigest.getInstance("MD5");
         catch (Exception e) 
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        
        char[] charArray = pwd.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++) 
            byteArray[i] = (byte) charArray[i];
        
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) 
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16)
                hexValue.append("0");
            
            hexValue.append(Integer.toHexString(val));
        
        return hexValue.toString();
    

    /**
     * @Description 解密
     * 一次加密之后,需要两次解密
     * @param pwd
     * @return String
    */
    public static String decrypt(String pwd) 
        char[] a = pwd.toCharArray();
        for (int i = 0; i < a.length; i++) 
            a[i] = (char) (a[i] ^ 't');
        
        String s = new String(a);
        return s;
    

    public static void main(String[] args) 
        String s = "123456";
        System.out.println("原始:" + s);
        System.out.println("MD5后:" + encode(s));
        System.out.println("加密的:" + decrypt(s));
        System.out.println("解密的:" + decrypt(decrypt(s)));
    

然后修改数据库密码。


4. PasswordEncorder(加密)

@Component
public class MyPasswordEncoder implements PasswordEncoder 
    /**
     * @Description 收集页面的密码
     * @param rawPassword
     * @return String
    */
    @Override
    public String encode(CharSequence rawPassword) 
        return rawPassword.toString();
    

    /**
     * @Description 匹配逻辑
     * @param rawPassword 明文,页面收集的密码
     * @param encodedPassword 密文,存储在数据源中的密码
     * @return boolean
    */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) 
        return PwdEncode.encode(rawPassword.toString()).equals(encodedPassword);
    

注意: MD5解密很简单——https://www.cmd5.com/,所以不推荐使用MD5加密。


5. BCryptPasswordEncoder

鉴于MD5加密的不安全性,所以建议使用 security 自带的加密工具类 —— BCryptPasswordEncoder

  • 同一明文,加密两次,输出不同

        public static void main(String[] args) 
            String s = "123456";
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();;
            System.out.println("原始: " + s);
            System.out.println("加密1: " + encoder.encode(s));
            System.out.println("加密1: " + encoder.encode(s));
        
    
  • 可使用 matches 验证明文与密文是否相同:

    System.out.println(encoder.matches(s, encoder.encode(s)));
    

BCryptPasswordEncoder使用bcrypt算法对密码进行加密,同时会为密码加上“盐”,开发者不需要自己加“盐”,即使相同的明文字段生成的加密字符串也不同。匹配时,从密文中取出“盐”,用该盐值加密明文和最终密文作对比。


BCryptPasswordEncoder的默认强度为10,开发者可以根据自己服务器的速度进行调整,以确保密码验证的时间约为1秒(官方建议)

BCryptPasswordEncoder encoder_pro = new BCryptPasswordEncoder(15);
System.out.println("加密2: " + encoder_pro.encode(s));

注册 BCryptPasswordEncoder

  • 取消MyPasswordEncoder的 @Component 注解

  • 新建一个配置类,注册 BCryptPasswordEncoder

    @Configuration
    public class SecurityConf 
        @Bean
        public BCryptPasswordEncoder passwordEncoder() 
            return new BCryptPasswordEncoder();
        
    
    

6. 认证流程(图)


3. 自定义登录界面


1. 界面

依赖:

        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

resources/templates/login.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>登录</title>
</head>
<body>
<div class="main">
  <div class="title">
    <span>密码登录</span>
  </div>

  <div class="title-msg">
    <span>请输入登录账户和密码</span>
  </div>

  <form class="login-form" method="post" novalidate th:action="@/myLogin">
    <!--输入框-->
    <div class="input-content">
      <!--autoFocus-->
      <div>
        <input type="text"
               autocomplete="off"
               placeholder="用户名"
               name="name"
               required/>
      </div>

      <div style="margin-top: 16px">
        <input type="password"
               autocomplete="off"
               placeholder="登录密码"
               name="password"
               required
               maxlength="32"/>
      </div>
    </div>

    <!--登入按钮-->
    <div style="text-align: center">
      <button type="submit" class="enter-btn" >登录</button>
    </div>
  </form>

</div>
</body>
<style>
  body
    background: #426258;
  
  *
    padding: 0;
    margin: 0;
  

  .main 
    padding-left: 25px以上是关于springboot集成security(认证)的主要内容,如果未能解决你的问题,请参考以下文章

springboot集成security(认证)

springboot集成spring security实现restful风格的登录认证 附代码

Security安全认证 | Spring Boot如何集成Security实现安全认证

Spring Boot如何集成Security实现安全认证

springboot集成security(鉴权)

Spring Security应用详解(集成SpringBoot)