当前位置: 代码网 > it编程>编程语言>Java > 基于Spring Security实现对密码进行加密和校验

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

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

一、密码加密和校验

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

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);
}
  • passwordencoder()方法来说明用户认证密码的校验方式,目前的设置为使用明文进行密码校验
  • users方法:spring security 提供了一个 userdetails 的实现类 user,用于用户信息的实例表示。另外,user 提供 builder 模式的对象构建方式。

测试

可以输入在内存构建的用户进行登录,比如: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
  • md5对于同一个字符串加密多次都是相同的

  • bcrypt对于同一个字符串加密多次是不同的,主要是因为添加了随机盐(随机字符串),更加安全

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

boolean checkpw = bcrypt.checkpw("123456", "$2a$10$rkb/70cz5uvse7f5zsbh8o2eydogus3/anvregp5ctpsglxm8iyg6");
  • 返回值为true,表示密码匹配成功

  • 返回值为false,表示密码匹配失败

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

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;
    }
}
  • 当前对象需要让spring容器管理,所以在类上添加注解@component

  • 大家注意一下loaduserbyusername方法的返回值,叫做userdetails,这也是框架给提供了保存用户的类,并且也是一个接口,如果我们有自定义的用户信息存储,可以实现这个接口,我们后边会详细讲解

既然以上能使用这个类来查询用户信息,那么我们之前在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 、数据库查询用户

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

  • 导入相关依赖(数据库、mybaits、lombok等)

  • 添加配置:连接数据库、mybatis配置等(application.yml)

  • 编写实体类和mapper

  • 改造userdetailsserviceimpl(用户从数据库中获取)

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);
}
  • 改造userdetailsserviceimpl
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

上述代码中,返回的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密码加密和校验的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com