基于Spring Security實現(xiàn)對密碼進行加密和校驗
一、密碼加密和校驗
我們在入門案例中,其實已經(jīng)是一個非常簡單的認證,但是用戶名是寫死的,密碼也需要從控制臺查看,很顯然實際中并不能這么做。下面的學習中,我們來實現(xiàn)基于內(nèi)存模型的認證以及用戶的自定義認證,密碼加密等內(nèi)容。
1.基于內(nèi)存模型實現(xiàn)認證
基于內(nèi)存模型的意思就是,我們可以現(xiàn)在內(nèi)存來構建用戶數(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 模式的對象構建方式。
測試
可以輸入在內(nèi)存構建的用戶進行登錄,比如:user/123456 admin/112233
二、BCrypt密碼加密
明文密碼肯定不安全,所以我們需要實現(xiàn)一個更安全的加密方式BCrypt。
BCrypt就是一款加密工具,可以比較方便地實現(xiàn)數(shù)據(jù)的加密工作。也可以簡單理解為它內(nèi)部自己實現(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內(nèi)部使用了BCrypt來進行加密和校驗,這種加密方式相對于MD5來說更加的安全。
三、基于數(shù)據(jù)庫實現(xiàn)認證
我們剛才的實現(xiàn)都是基于內(nèi)存來構建用戶的,在實際開發(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ù)庫中獲取)
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: 100
3.表結構及實體類和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,在自己定義的類中,就可以擴展自己想要的內(nèi)容,如下代碼:
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)對密碼進行加密和校驗的詳細內(nèi)容,更多關于Spring Security密碼加密和校驗的資料請關注腳本之家其它相關文章!
相關文章
Java文件字符輸入流FileReader讀取txt文件亂碼的解決
這篇文章主要介紹了Java文件字符輸入流FileReader讀取txt文件亂碼的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09使用java代碼實現(xiàn)一個月內(nèi)不再提醒,通用到期的問題
這篇文章主要介紹了使用java代碼實現(xiàn)一個月內(nèi)不再提醒,通用到期的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01