一、密码加密和校验
我们在入门案例中,其实已经是一个非常简单的认证,但是用户名是写死的,密码也需要从控制台查看,很显然实际中并不能这么做。下面的学习中,我们来实现基于内存模型的认证以及用户的自定义认证,密码加密等内容。
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: 1003.表结构及实体类和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密码加密和校验的资料请关注代码网其它相关文章!
发表评论