java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Security密码加密和校验

基于Spring Security实现对密码进行加密和校验

作者:还不如ctrC+V

我们在入门案例中,其实已经是一个非常简单的认证,但是用户名是写死的,密码也需要从控制台查看,很显然实际中并不能这么做,下面的学习中,我们来实现基于内存模型的认证以及用户的自定义认证,密码加密等内容,需要的朋友可以参考下

一、密码加密和校验

我们在入门案例中,其实已经是一个非常简单的认证,但是用户名是写死的,密码也需要从控制台查看,很显然实际中并不能这么做。下面的学习中,我们来实现基于内存模型的认证以及用户的自定义认证,密码加密等内容。

1.基于内存模型实现认证

基于内存模型的意思就是,我们可以现在内存来构建用户数据完成认证的操作

2.修改配置类 SecurityConfig,添加两个bean的配置

@Bean
PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}
 
@Bean
public UserDetailsService users() {
    UserDetails user = User.builder()
            .username("user")
            .password("123456")
            .roles("USER")
            .build();
    UserDetails admin = User.builder()
            .username("admin")
            .password("112233")
            .roles("USER", "ADMIN")
            .build();
    return new InMemoryUserDetailsManager(user, admin);
}

测试

可以输入在内存构建的用户进行登录,比如:user/123456 admin/112233

二、BCrypt密码加密

明文密码肯定不安全,所以我们需要实现一个更安全的加密方式BCrypt。

BCrypt就是一款加密工具,可以比较方便地实现数据的加密工作。也可以简单理解为它内部自己实现了随机加盐处理。例如,使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。

BCrypt生成的密文长度是60,而MD5的长度是32。

我们现在随便找个类,写个main方法测试一下

package com.zzyl.vo;
 
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.util.DigestUtils;
 
public class PswdTest {
 
    public static void main(String[] args) {
 
        //md5加密
        String md5Pswd1 = DigestUtils.md5DigestAsHex("123456".getBytes());
        String md5Pswd2 = DigestUtils.md5DigestAsHex("123456".getBytes());
        System.out.println(md5Pswd1);
        System.out.println(md5Pswd2);
        System.out.println(md5Pswd1.equals(md5Pswd2));
 
        System.out.println("-------------------------------------");
 
        String password1 = BCrypt.hashpw("123456", BCrypt.gensalt());
        String password2 = BCrypt.hashpw("123456", BCrypt.gensalt());
        System.out.println(password1);
        System.out.println(password2);
        System.out.println(password1.equals(password2));
    }
}

输出的结果如下:

e10adc3949ba59abbe56e057f20f883e
e10adc3949ba59abbe56e057f20f883e
true
-------------------------------------
$2a$10$rkB/70Cz5UvsE7F5zsBh8O2EYDoGus3/AnVrEgP5cTpsGLxM8iyG6
$2a$10$QIefMa.FmFIb2k2S9/jO7e1S3b0aeXCMtGS/ArKUyt6q28deYQrfy
false

其中BCrypt提供了一个方法,用于验证密码是否正确

boolean checkpw = BCrypt.checkpw("123456", "$2a$10$rkB/70Cz5UvsE7F5zsBh8O2EYDoGus3/AnVrEgP5cTpsGLxM8iyG6");

接下来,我们看代码如何实现

1.修改配置类SecurityConfig 的passwordEncoder实现类为BCryptPasswordEncoder

@Bean
PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

PasswordEncoder的实现类BCryptPasswordEncoder,用于BCrypt密码的解析。

2.修改配置类SecurityConfig 的users方法中的密码,为加密后的密码

@Bean
public UserDetailsService users() {
    UserDetails user = User.builder()
            .username("user")
            .password("$2a$10$2VCyByZ5oeiXCEN73wvBB.xpmJgPBbZVS/Aallmdyij2G7hmAKQTG")
            .roles("USER")
            .build();
    UserDetails admin = User.builder()
            .username("admin")
            .password("$2a$10$cRH8iMMh6XO0T.ssZ/8qVOo8ThWs/qfntIH3a7yfpbPd05h9ZGx8y")
            .roles("USER", "ADMIN")
            .build();
    return new InMemoryUserDetailsManager(user, admin);
}

大家可以使用BCrypt对想要的字符串进行加密后填充到上面的password中

3.再次测试

输入user,密码为加密前的密码,比如123456,如果登录成功,则表示认证成功(密码校验也成功)

结论

到现在为止呢,我们就清楚了Spring Security内部使用了BCrypt来进行加密和校验,这种加密方式相对于MD5来说更加的安全。

三、基于数据库实现认证

我们刚才的实现都是基于内存来构建用户的,在实际开发中,用户肯定会保存到数据库中,在Spring Security框架中提供了一个UserDetailsService 接口

它的主要作用是提供用户详细信息。具体来说,当用户尝试进行身份验证时,UserDetailsService 会被调用,以获取与用户相关的详细信息。这些详细信息包括用户的用户名、密码、角色等

1、执行流程

新创建一个UserDetailsServiceImpl,让它实现UserDetailsService ,代码如下

@Component
public class UserDetailsServiceImpl  implements UserDetailsService {
 
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
        //查询数据库中的用户,并且返回框架要求的UserDetails 
        return null;
    }
}

既然以上能使用这个类来查询用户信息,那么我们之前在SecurityConfig中定义的用户信息,可以注释掉了,如下:

    /*
    @Bean
    public UserDetailsService users() {
        UserDetails user = User.builder()
                .username("user")
                .password("$2a$10$2VCyByZ5oeiXCEN73wvBB.xpmJgPBbZVS/Aallmdyij2G7hmAKQTG")
                .roles("USER")
                .build();
        UserDetails admin = User.builder()
                .username("admin")
                .password("$2a$10$cRH8iMMh6XO0T.ssZ/8qVOo8ThWs/qfntIH3a7yfpbPd05h9ZGx8y")
                .roles("USER", "ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }*/

2 、数据库查询用户

我们下面就来实现数据库去查询用户,我们可以直接使用我们项目中的用户表,实现的步骤如下:

1.pom文件添加依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.1</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
<!--MySQL支持-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
</dependency>

2.application.yml添加数据库相关配置

#服务配置
server:
  #端口
  port: 8080
spring:
  application:
    name: springsecurity-demo
  #数据源配置
  datasource:
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.200.146:3306/security_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
      username: root
      password: heima123
# MyBatis配置
mybatis:
  #mapper配置文件
  mapper-locations: classpath*:mapper*/*Mapper.xml
  type-aliases-package: com.itheima.project.entity
  configuration:
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 驼峰下划线转换
    map-underscore-to-camel-case: true
    use-generated-keys: true
    default-statement-timeout: 60
    default-fetch-size: 100

3.表结构及实体类和mapper

新创建一个数据库,名字为:security_db

导入当天资料的sql脚本

用户实体类

package com.itheima.project.entity;
 
import lombok.Data;
 
import java.time.LocalDateTime;
 
@Data
public class User {
 
    public Long id;
 
    /**
     * 用户账号
     */
    private String username;
 
    /**
     * 密码
     */
    private String password;
 
    /**
     * 用户昵称
     */
    private String nickName;
 
}

用户mapper,我们只需要定义一个根据用户名查询的方法即可

package com.itheima.project.mapper;
 
import com.itheima.project.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
 
/**
 * @author sjqn
 */
@Mapper
public interface UserMapper {
 
    @Select("select * from sys_user where username = #{username}")
    public User findByUsername(String username);
}
package com.zzyl.security.service;
 
import com.zzyl.security.entity.User;
import com.zzyl.security.entity.UserAuth;
import com.zzyl.security.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Component;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.SimpleTimeZone;
 
/**
 * @author sjqn
 */
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
 
 
    @Autowired
    private UserMapper userMapper;
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
        //查询用户
        User user = userMapper.findByUsername(username);
        if(user == null){
            throw new RuntimeException("用户不存在或已被禁用");
        }
        SimpleGrantedAuthority user_role = new SimpleGrantedAuthority("user");
        SimpleGrantedAuthority admin_role = new SimpleGrantedAuthority("admin");
        List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
 
        list.add(user_role);
        list.add(admin_role);
 
        return new org.springframework.security.core.userdetails.User(user.getUsername()
                ,user.getPassword()
                , list);
    }
}

上述代码中,返回的UserDetails或者是User都是框架提供的类,我们在项目开发的过程中,很多需求都是我们自定义的属性,我们需要扩展该怎么办?

其实,我们可以自定义一个类,来实现UserDetails,在自己定义的类中,就可以扩展自己想要的内容,如下代码:

package com.zzyl.security.entity;
 
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
 
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
 
/**
 * @author sjqn
 * @date 2023/9/1
 */
@Data
public class UserAuth implements UserDetails {
 
    private String username; //固定不可更改
    private String password;//固定不可更改
    private String nickName;  //扩展属性  昵称
    private List<String> roles; //角色列表
 
 
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(roles==null) return null;
        //把角色类型转换并放入对应的集合
        return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_"+role)).collect(Collectors.toList());
    }
 
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
 
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
 
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
 
    @Override
    public boolean isEnabled() {
        return true;
    }
}

然后,我们可以继续改造UserDetailsServiceImpl中检验用户的逻辑,代码如下:

package com.itheima.project.service;
 
import com.itheima.project.entity.User;
import com.itheima.project.mapper.UserMapper;
import com.itheima.project.vo.UserAuth;
import org.springframework.beans.factory.annotation.Autowired;
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.Component;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * @author sjqn
 */
@Component
public class UserDetailServiceImpl implements UserDetailsService {
 
    @Autowired
    private UserMapper userMapper;
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户
        User user = userMapper.findByUsername(username);
        if(user == null){
            throw new RuntimeException("用户不存在或已被禁用");
        }
        UserAuth userAuth = new UserAuth();
        userAuth.setUsername(user.getUsername());
        userAuth.setPassword(user.getPassword());
        userAuth.setNickName(user.getNickName());
 
        //添加角色
        List<String> roles=new ArrayList<>();
        if("user@qq.com".equals(username)){
            roles.add("USER");
            userAuth.setRoles(roles);
        }
        if("admin@qq.com".equals(username)){
            roles.add("USER");
            roles.add("ADMIN");
            userAuth.setRoles(roles);
        }
        return userAuth;
    }
}

修改HelloController,使用getPrincipal()方法读取认证主体对象。

/**
 * @ClassName
 * @Description
 */
@RestController
public class HelloController {
 
    @RequestMapping("/hello")
    public String hello(){
        //获取当前登录用户名称
        String name = SecurityContextHolder.getContext().getAuthentication().getName();
        UserAuth userAuth = (UserAuth)SecurityContextHolder.getContext().getAuthentication().getPrincipal();//取出认证主体对象
 
        return "hello :"+name+"  昵称:"+userAuth.getNickName();
    }
 
}

测试

重启项目之后,可以根据数据库中有的用户进行登录,如果登录成功则表示整合成功

以上就是基于Spring Security实现对密码进行加密和校验的详细内容,更多关于Spring Security密码加密和校验的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文