**
6 密码处理
6.1 为什么要加密?
csdn 密码泄露事件
泄露事件经过:https://www.williamlong.info/archives/2933.html
泄露数据分析:https://blog.csdn.net/crazyhacking/article/details/10443849
6.2加密方案
密码加密一般使用散列函数,又称散列算法,哈希函数,这些函数都是单向函数(从明文到密文,反之不行)
常用的散列算法有MD5和SHA
Spring Security提供多种密码加密方案,基本上都实现了PasswordEncoder接口,官方推荐使用BCryptPasswordEncoder
6.3 BCryptPasswordEncoder类初体验
拷贝springsecurity-04-inmemory工程,重命名为springsecurity-05-password-encode
test/java 下新建包com.powernode.password,在该包下新建测试类PasswordEncoderTest,如下
@Slf4j
public class PasswordEncoderTest {
@Test
@DisplayName("测试加密类BCryptPasswordEncoder")
void testPassword(){
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
//加密(明文到密文)
String encode1 = bCryptPasswordEncoder.encode("123456");
_log_.info("encode1:"+encode1);
String encode2 = bCryptPasswordEncoder.encode("123456");
_log_.info("encode2:"+encode2);
String encode3 = bCryptPasswordEncoder.encode("123456");
_log_.info("encode3:"+encode3);
//匹配方法,判断明文经过加密后是否和密文一样
boolean result1 = bCryptPasswordEncoder.matches("123456", encode1);
boolean result2 = bCryptPasswordEncoder.matches("123456", encode1);
boolean result3 = bCryptPasswordEncoder.matches("123456", encode1);
_log_.info(result1+":"+result2+":"+result3);
_assertTrue_(result1);
_assertTrue_(result2);
_assertTrue_(result3);
}
}
查看控制台发现特点是:**相同的字符串加密之后的结果都不一样,但是比较的时候是一样的,因为加了盐(**salt)了。
上面简单看下即可
小提示:
Ø 开发代码时不允许使用main方法测试,而是使用单元测试来测试
Ø 代码中一般不允许使用System.out.println 直接输出,而是使用日志输出
Ø 单元测试尽量使用断言,而不是使用System.out.println输出
6.4 使用加密器并且加密
修改MySecurityUserConfig类中的加密器bean
@Bean
public PasswordEncoder passwordEncoder(){
//使用加密算法对密码进行加密
return new BCryptPasswordEncoder();
}
启动程序测试,发现不能正常登录
原因是输入的密码是进行加密了,但是系统中定义的用户密码没有加密
将系统定义的用户密码修改成密文,如下
@Configuration
public class MySecurityUserConfig {
@Bean
public UserDetailsService userDetailService() {
// 使用org.springframework.security.core.userdetails.User类来定义用户
//定义两个用户
UserDetails user1 = User._builder_()
.username("eric")
.password(passwordEncoder().encode("123456"))
.roles("student")
.build();
UserDetails user2 = User._builder_()
.username("thomas")
.password(passwordEncoder().encode("123456"))
.roles("teacher")
.build();
//创建两个用户
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(user1);
userDetailsManager.createUser(user2);
return userDetailsManager;
}
/*
* 从 Spring5 开始,强制要求密码要加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
//使用加密算法对密码进行加密
return new BCryptPasswordEncoder();
}
}
重启程序,再次测试即可,发现登录和访问没问题了
7 查看当前登录用户信息及配置用户权限
复制springsecurity-05-password-encode,复制后为springsecurity-06-loginuser-info
7.1 获取当前登录用户信息
新建一个controller
@RestController
public class CurrentLoginUserInfoController {
_/**
* 从当前请求对象中获取
*/
_@GetMapping("/getLoginUserInfo")
public Principal getLoginUserInfo(Principal principle){
return principle;
}
_/**
*从当前请求对象中获取
*/
_@GetMapping("/getLoginUserInfo1")
public Authentication getLoginUserInfo1(Authentication authentication){
return authentication;
}
_/**
* 从安全应用上下文(SecurityContextHolder)获取安全应用上下文(SecurityContext),从安全应用上下文中获取认证信息
* **@return
***/
_@GetMapping("/getLoginUserInfo2")
public Authentication getLoginUserInfo(){
Authentication authentication = SecurityContextHolder._getContext_().getAuthentication();
return authentication;
}
}
注意Authentication接口继承自 Principal
重启程序,访问
http://localhost:8080/getLoginUserInfo
http://localhost:8080/getLoginUserInfo1
http://localhost:8080/getLoginUserInfo2
运行结果
{
“authorities”: [{
“authority”: “ROLE_teacher”
}],
“details”: {
“remoteAddress”: “0:0:0:0:0:0:0:1”,
“sessionId”: “34E452050095348E6306CF95B2025CD9”
},
“authenticated”: true,
“principal”: {
“password”: null,
“username”: “thomas”,
“authorities”: [{
“authority”: “ROLE_teacher”
}],
“accountNonExpired”: true,
“accountNonLocked”: true,
“credentialsNonExpired”: true,
“enabled”: true
},
“credentials”: null,
“name”: “thomas” }
Principal 定义认证的而用户,如果用户使用用户名和密码方式登录,principal通常就是一个UserDetails(后面再说)
Credentials:登录凭证,一般就是指密码。当用户登录成功之后,登录凭证会被自动擦除,以防泄露。
authorities:用户被授予的权限信息。
7.2 配置用户权限
配置用户权限有两种方式:
配置roles
配置authorities
注意事项:
如果给一个用户同时配置roles和authorities,哪个写在后面哪个起作用
配置roles时,权限名会加上ROLE_。
修改WebSecurityConfig代码中的
// 注意 1 哪个写在后面哪个起作用 2 角色变成权限后会加一个ROLE_前缀,比如ROLE_teacher
// UserDetails user2 = User.builder()
// .username("thomas")
// .password(passwordEncoder().encode("123456"))
// .authorities("teacher:add","teacher:update")
// .roles("teacher")
// .build();
UserDetails user2 = User._builder_()
.username("thomas") .password(passwordEncoder().encode("123456"))
.roles("teacher")
.authorities("teacher:add","teacher:update")
.build();
重启程序使用thomas登录,然后查看用户认证信息
http://localhost:8080/getLoginUserInfo |
---|
可以看到authorities的情况。
从设计层面讲,角色和权限是两个完全不同的东西
从代码层面来讲,角色和权限并没有太大区别,特别是在Spring Security中
8 授权(对URL进行授权)
上面讲的实现了认证功能,但是受保护的资源是默认的,默认所有认证(登录)用户均可以访问所有资源,不能根据实际情况进行角色管理,要实现授权功能,需重写WebSecurityConfigureAdapter 中的一个configure方法
复制springsecurity-06-loginuser-info 工程,然后改名为springsecurity-07-url
新建WebSecurityConfig类,重写configure(HttpSecurity http)方法
WebSecurityConfig 完整代码如下:
@Configuration
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//角色student或者teacher都可以访问/student/** 这样的url
.mvcMatchers("/student/*").hasAnyRole("student", "teacher")
// 角色teacher 可以访问teacher/**
.mvcMatchers("/teacher/**").hasRole("teacher")
//权限admin:query 可以访问/admin**
// .mvcMatchers("/admin/**").hasAuthority("admin:query")
//角色teacher 或者权限admin:query 觉可以访问admin/**
.mvcMatchers("/admin/**").access("hasRole('teacher') or hasAuthority('admin:query')")
//任何请求均需要认证
.anyRequest().authenticated();
//使用表单登录
http.formLogin();
}
}
使用admin登录,访问
http://localhost:8080/teacher/query
http://localhost:8080/student/query
http://localhost:8080/admin/query
分别查看效果,实现权限控制
上面是对URL资源进行控制,就是哪些权限可以访问哪些URL。
9 授权(方法级别的权限控制)
上面学习的认证与授权都是基于URL的,我们也可以通过注解灵活的配置方法安全,我们先通过@EnableGlobalMethodSecurity开启基于注解的安全配置。
9.1 新建模块springsecurity-08-method
9.2 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
9.3 新建启动类并复制 CurrentLoginUserInfoController类
新建启动类Application,学员自行创建
9.4 新建service及其实现
com.powernode.service 新建教师接口
public interface TeacherService {
String add();
String update();
String delete();
String query();
}
com.powernode.service.impl 实现接口
@Service
@Slf4j
public class TeacherServiceImpl implements TeacherService {
@Override
public String add() {
_log_.info("添加教师成功");
return "添加教师成功";
}
@Override
public String update() {
_log_.info("修改教师成功");
return "修改教师成功";
}
@Override
public String delete() {
_log_.info("删除教师成功");
return "删除教师成功";
}
@Override
public String query() {
_log_.info("查询教师成功");
return "查询教师成功";
}
}
9.5 修建TeacherController
@RestController
@RequestMapping("/teacher")
public class TeacherController {
&#