基于Spring Security實現(xiàn)對密碼進行加密和校驗
一、密碼加密和校驗
我們在入門案例中,其實已經(jīng)是一個非常簡單的認證,但是用戶名是寫死的,密碼也需要從控制臺查看,很顯然實際中并不能這么做。下面的學習中,我們來實現(xiàn)基于內存模型的認證以及用戶的自定義認證,密碼加密等內容。
1.基于內存模型實現(xiàn)認證
基于內存模型的意思就是,我們可以現(xiàn)在內存來構建用戶數(shù)據(jù)完成認證的操作
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 的實現(xiàn)類 User,用于用戶信息的實例表示。另外,User 提供 Builder 模式的對象構建方式。
測試
可以輸入在內存構建的用戶進行登錄,比如:user/123456 admin/112233
二、BCrypt密碼加密
明文密碼肯定不安全,所以我們需要實現(xiàn)一個更安全的加密方式BCrypt。
BCrypt就是一款加密工具,可以比較方便地實現(xiàn)數(shù)據(jù)的加密工作。也可以簡單理解為它內部自己實現(xiàn)了隨機加鹽處理。例如,使用MD5加密,每次加密后的密文其實都是一樣的,這樣就方便了MD5通過大數(shù)據(jù)的方式進行破解。
BCrypt生成的密文長度是60,而MD5的長度是32。
我們現(xiàn)在隨便找個類,寫個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,表示密碼匹配失敗
接下來,我們看代碼如何實現(xiàn)
1.修改配置類SecurityConfig 的passwordEncoder實現(xiàn)類為BCryptPasswordEncoder
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}PasswordEncoder的實現(xiàn)類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,如果登錄成功,則表示認證成功(密碼校驗也成功)
結論
到現(xiàn)在為止呢,我們就清楚了Spring Security內部使用了BCrypt來進行加密和校驗,這種加密方式相對于MD5來說更加的安全。
三、基于數(shù)據(jù)庫實現(xiàn)認證
我們剛才的實現(xiàn)都是基于內存來構建用戶的,在實際開發(fā)中,用戶肯定會保存到數(shù)據(jù)庫中,在Spring Security框架中提供了一個UserDetailsService 接口
它的主要作用是提供用戶詳細信息。具體來說,當用戶嘗試進行身份驗證時,UserDetailsService 會被調用,以獲取與用戶相關的詳細信息。這些詳細信息包括用戶的用戶名、密碼、角色等
1、執(zhí)行流程

新創(chuàng)建一個UserDetailsServiceImpl,讓它實現(xiàn)UserDetailsService ,代碼如下
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查詢數(shù)據(jù)庫中的用戶,并且返回框架要求的UserDetails
return null;
}
}當前對象需要讓spring容器管理,所以在類上添加注解@Component
大家注意一下loadUserByUsername方法的返回值,叫做UserDetails,這也是框架給提供了保存用戶的類,并且也是一個接口,如果我們有自定義的用戶信息存儲,可以實現(xiàn)這個接口,我們后邊會詳細講解
既然以上能使用這個類來查詢用戶信息,那么我們之前在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 、數(shù)據(jù)庫查詢用戶
我們下面就來實現(xiàn)數(shù)據(jù)庫去查詢用戶,我們可以直接使用我們項目中的用戶表,實現(xiàn)的步驟如下:
導入相關依賴(數(shù)據(jù)庫、mybaits、lombok等)
添加配置:連接數(shù)據(jù)庫、mybatis配置等(application.yml)
編寫實體類和mapper
改造UserDetailsServiceImpl(用戶從數(shù)據(jù)庫中獲?。?/p>
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添加數(shù)據(jù)庫相關配置
#服務配置
server:
#端口
port: 8080
spring:
application:
name: springsecurity-demo
#數(shù)據(jù)源配置
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:
# 這個配置會將執(zhí)行的sql打印出來,在開發(fā)或測試的時候可以用
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
新創(chuàng)建一個數(shù)據(jù)庫,名字為: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,我們只需要定義一個根據(jù)用戶名查詢的方法即可
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都是框架提供的類,我們在項目開發(fā)的過程中,很多需求都是我們自定義的屬性,我們需要擴展該怎么辦?
其實,我們可以自定義一個類,來實現(xiàn)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;
}
}然后,我們可以繼續(xù)改造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();
}
}測試
重啟項目之后,可以根據(jù)數(shù)據(jù)庫中有的用戶進行登錄,如果登錄成功則表示整合成功
以上就是基于Spring Security實現(xiàn)對密碼進行加密和校驗的詳細內容,更多關于Spring Security密碼加密和校驗的資料請關注腳本之家其它相關文章!
相關文章
Java文件字符輸入流FileReader讀取txt文件亂碼的解決
這篇文章主要介紹了Java文件字符輸入流FileReader讀取txt文件亂碼的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
使用java代碼實現(xiàn)一個月內不再提醒,通用到期的問題
這篇文章主要介紹了使用java代碼實現(xiàn)一個月內不再提醒,通用到期的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01

