美文网首页
spring-boot-security 一个简单的demo

spring-boot-security 一个简单的demo

作者: 东南枝下 | 来源:发表于2022-07-31 09:08 被阅读0次
    1. 引入依赖:使用的依赖版本如下

    父依赖

    <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.5.12</version>
        </parent>
    

    依赖版本随父依赖指定,重点是spring-boot-starter-security

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <!-- security -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.16.18</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    

    其实这个时候就可以写一个接口来测试了
    先在配置文件application.yml中加上这一段,在spring.security中指定一个账号密码,当然实际情况这样做不太好,测完了把它删掉

    spring:
      application:
        name: salt-security
      security:
        user:
          name: admin
          password: 123456
    

    然后写个测试接口用的

    @RestController
    @RequestMapping("/test")
    public class TestController {
    
        @GetMapping()
        public String security(){
            return "hello spring security";
        }
    
    }
    

    在浏览器中调用这个接口,会跳转到一个登录页面,输入刚才指定的账号密码后,成功调通这个接口。

    测试完成后我们再进一步。

    1. 实现UserDetailsService,重写loadUserByUsername
      这个是为了自定义账户的获取,用户将用户名和密码传过来认证,我们这边要使用用户传过来的用户名去数据源(数据库 等)中找到对应的账户信息,才能和用户传递过来的信息做对比
      这里就意思意思直接指定密码了
    package com.jenson.oauth.security.custom;
    
    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.stereotype.Service;
    
    import java.util.Collections;
    
    @Service
    public class CustomUserDetailsService implements UserDetailsService {
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 这里应该是从数据源中根据用户名查找用户信息,如果用户信息查询不到则抛出用户不存在的异常
            String password = "123456";
            UserDetails userDetails = new User(username, password, Collections.emptyList());
            return userDetails;
        }
    }
    
    

    光实现这个查询出用户还不行,会报错

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

    需要实现一下PasswordEncoder
    matches方法就是用来判断密码是否匹配的,匹配就会返回true

    package com.jenson.oauth.security.custom;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Component;
    
    @Component
    @Slf4j
    public class CustomBCryptPasswordEncoder implements PasswordEncoder {
    
        @Override
        public String encode(CharSequence rawPassword) {
            return null;
        }
    
        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            return rawPassword.toString().equals(encodedPassword);
        }
    }
    
    

    这个时候再测试一下刚才的 /test ,还是一样的,可以认证成功。
    但是现在密码是明文的,要是数据库泄露就完蛋了,所以改一下CustomBCryptPasswordEncoder,对密码简单的加个密

    package com.jenson.oauth.security.custom;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.crypto.bcrypt.BCrypt;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Component;
    
    @Component
    @Slf4j
    public class CustomBCryptPasswordEncoder implements PasswordEncoder {
    
        @Override
        public String encode(CharSequence rawPassword) {
            // 简单加密,生成一个salt
            String salt = BCrypt.gensalt();
            return BCrypt.hashpw(rawPassword.toString(), salt);
        }
    
        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            if (rawPassword != null && encodedPassword != null && encodedPassword.length() != 0) {
                return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
            } else {
                log.warn("Empty encoded password");
                return false;
            }
        }
    }
    
    

    可以写个单元测试测试下这个类

    package com.jenson.oauth.security.custom;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    class CustomBCryptPasswordEncoderTest {
    
        @Autowired
        private CustomBCryptPasswordEncoder customBCryptPasswordEncoder;
    
        @Test
        void encode() {
            String password = customBCryptPasswordEncoder.encode("123456");
            System.out.println("password = " + password);
    
            boolean success1 = customBCryptPasswordEncoder.matches("123456", password);
    
            boolean success2 = customBCryptPasswordEncoder.matches("123456", "$2a$10$Z1OKl3clWu5FD2WMGNa.KOAiMn4QTk4CFfozKAC86s4Fw6aPmWbri");
    
            boolean success3 = customBCryptPasswordEncoder.matches("1234567", password);
    
            System.out.println("success1 = " + success1 + "\n"
                    + "success2 = " + success2 + "\n"
                    + "success3 = " + success3);
        }
    }
    

    运行结果如下

    password = $2a$10$o.DUpIgkmWS12DCee8.5z.YwFqA65pp/CzrI4Xj6eR/l2Rj5I8s9W
    success1 = true
    success2 = true
    success3 = false
    

    可以看出,BCrypt.checkpw可以用一段未加密的字符串和已加密的字符串做比较,如果与已加密字符串的原字符串能匹配上,就会返回true。
    这样的话数据库里就不用保存明文的密码了,安全性大大提高
    再修改下loadUserByUsername,密码换成加密后的"123456",再测试一下/test 接口,发现没有问题,依然能正常登录
    可以发现同样是"123456"这个字符串,BCrypt.hashpw("123456", salt)加密后的字符串并不相同,但是依然能和原字符串匹配成功

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 这里应该是从数据源中根据用户名查找用户信息,如果用户信息查询不到则抛出用户不存在的异常
            String password = "$2a$10$wETX0LQUnOJ8iJG9M.m4w.ofrD2RVkZ7udPqRXgonHILTKYgizg0e";
            UserDetails userDetails = new User(username, password, Collections.emptyList());
            return userDetails;
        }
    

    但是这样调用接口需要重定向到登录页面就很麻烦,特别是前后端分离的场景,如果是通过一个接口获取token,再使用此token去调用其他接口就好了

    相关文章

      网友评论

          本文标题:spring-boot-security 一个简单的demo

          本文链接:https://www.haomeiwen.com/subject/wpzhirtx.html