# SpringSecruityNotes
**Repository Path**: Sunxz007/SpringSecruityNotes
## Basic Information
- **Project Name**: SpringSecruityNotes
- **Description**: SpringSecruity 存储笔记中的示例源码
- **Primary Language**: Java
- **License**: Artistic-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-06-09
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# SpringSecruityNotes
#### 介绍
SpringSecruity 存储笔记中的示例源码
#### 目录
- [环境搭建说明](#%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E8%AF%B4%E6%98%8E)
- [基本环境引入](#%E5%9F%BA%E6%9C%AC%E7%8E%AF%E5%A2%83%E5%BC%95%E5%85%A5)
- [引入Spring Secruity](#%E5%BC%95%E5%85%A5spring-secruity)
- [静态资源的配置](#%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E7%9A%84%E9%85%8D%E7%BD%AE)
- [SpringSecurity 的默认配置](#springsecurity-%E7%9A%84%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE)
- [放行首页和静态资源](#%E6%94%BE%E8%A1%8C%E9%A6%96%E9%A1%B5%E5%92%8C%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90)
- [未认证请求跳转到登录页](#%E6%9C%AA%E8%AE%A4%E8%AF%81%E8%AF%B7%E6%B1%82%E8%B7%B3%E8%BD%AC%E5%88%B0%E7%99%BB%E5%BD%95%E9%A1%B5)
- [loginPage(String loginPage) 方法的说明](#loginpagestring-loginpage-%E6%96%B9%E6%B3%95%E7%9A%84%E8%AF%B4%E6%98%8E)
- [loginProcessingUrl()方法指定登录地址](#loginprocessingurl%E6%96%B9%E6%B3%95%E6%8C%87%E5%AE%9A%E7%99%BB%E5%BD%95%E5%9C%B0%E5%9D%80)
- [设置登录系统的账号、密码并登录](#%E8%AE%BE%E7%BD%AE%E7%99%BB%E5%BD%95%E7%B3%BB%E7%BB%9F%E7%9A%84%E8%B4%A6%E5%8F%B7%E5%AF%86%E7%A0%81%E5%B9%B6%E7%99%BB%E5%BD%95)
- [设置登录系统的账号密码](#%E8%AE%BE%E7%BD%AE%E7%99%BB%E5%BD%95%E7%B3%BB%E7%BB%9F%E7%9A%84%E8%B4%A6%E5%8F%B7%E5%AF%86%E7%A0%81)
- [设置账号和密码的参数名,指定请求成功后的地址](#%E8%AE%BE%E7%BD%AE%E8%B4%A6%E5%8F%B7%E5%92%8C%E5%AF%86%E7%A0%81%E7%9A%84%E5%8F%82%E6%95%B0%E5%90%8D%E6%8C%87%E5%AE%9A%E8%AF%B7%E6%B1%82%E6%88%90%E5%8A%9F%E5%90%8E%E7%9A%84%E5%9C%B0%E5%9D%80)
- [**用户注销**](#%E7%94%A8%E6%88%B7%E6%B3%A8%E9%94%80)
- [基于角色或权限进行访问控制](#%E5%9F%BA%E4%BA%8E%E8%A7%92%E8%89%B2%E6%88%96%E6%9D%83%E9%99%90%E8%BF%9B%E8%A1%8C%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6)
- [配置角色权限](#%E9%85%8D%E7%BD%AE%E8%A7%92%E8%89%B2%E6%9D%83%E9%99%90)
- [自定义 403 错误页面](#%E8%87%AA%E5%AE%9A%E4%B9%89-403-%E9%94%99%E8%AF%AF%E9%A1%B5%E9%9D%A2)
- [记住我(不重要)](#%E8%AE%B0%E4%BD%8F%E6%88%91%E4%B8%8D%E9%87%8D%E8%A6%81)
- [内存版](#%E5%86%85%E5%AD%98%E7%89%88)
- [记住我原理简要分析](#%E8%AE%B0%E4%BD%8F%E6%88%91%E5%8E%9F%E7%90%86%E7%AE%80%E8%A6%81%E5%88%86%E6%9E%90)
- [数据库版本](#%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC)
- [**查询数据库完成认证**](#%E6%9F%A5%E8%AF%A2%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%8C%E6%88%90%E8%AE%A4%E8%AF%81)
- [SpringSecurity 默认实现](#springsecurity-%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0)
- [自定义数据库查询方式](#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%95%B0%E6%8D%AE%E5%BA%93%E6%9F%A5%E8%AF%A2%E6%96%B9%E5%BC%8F)
- [**应用自定义密码加密规则**](#%E5%BA%94%E7%94%A8%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AF%86%E7%A0%81%E5%8A%A0%E5%AF%86%E8%A7%84%E5%88%99)
- [盐值加密](#%E7%9B%90%E5%80%BC%E5%8A%A0%E5%AF%86)
## 环境搭建说明
### 基本环境引入
引入springmvc的环境和添加测试使用的资源,这个步骤可以直接引入tag0.0.1 就布置好了基本环境
此时可以填入登录名密码来随意访问演示项目中的任意资源

### 引入Spring Secruity
1. spring环境统一配置为 5.2.5.RELEASE,加入 SpringMVC 环境需要的依赖
```xml
org.springframework.security
spring-security-web
${spring.version}
org.springframework.security
spring-security-config
${spring.version}
org.springframework.security
spring-security-taglibs
${spring.version}
```
2. 在 web.xml 中加入 SpringSecurity 控制权限的 Filter
> SpringSecurity使用的是过滤器Filter而不是拦截器Interceptor,因此能够管理的不仅仅是 SpringMVC 中的 handler 请求,还包含 Web 应用中所有请求。比如: 项目中的静态资源也会被拦截,从而进行权限控制。
```xml
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
```
**注意**
`springSecurityFilterChain` 标签中必须是 springSecurityFilterChain。因为 springSecurityFilterChain 在 IOC 容器中对应真正执行权限控制的二十几个 Filter,只有叫这个名字才能够加载到这些 Filter。
3. 加入配置类 com.sun.security.config.WebAppSecurityConfig
```java
@Configuration
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter { }
```
使用springsecruity接管系统来测试
此时打开项目请求会转发到login界面中,静态资源也都被拦截,要求登录。

## 静态资源的配置
### SpringSecurity 的默认配置
在自定义配置类`WebAppSecurityConfig` 继承的父类的configure 方法中可以看到spring secruity 的默认配置,默认会把所有请求转发 到form表单请求页
```java
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
```
此时所有请求都会被重定向到Spring Security 默认的登录页

### 放行首页和静态资源
可以通过重写此方法来自定义放行配置
```java
@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**","/index.jsp") //使用 ANT 风格设置要授权的 URL 地 址
.permitAll() //允许上面使用 ANT 风格设置的全部请求
.anyRequest() //其他未设置的全部请求
.authenticated(); //需要认证
}
```
此时layui目录下的静态资源可以访问,并且项目首页也能正常显示

但是剩余的资源没有配置放行条件,一律被拦截

## 未认证请求跳转到登录页
继续修改configure 方法
```java
@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**", "/index.jsp") //使用 ANT 风格设置要授权的 URL 地 址
.permitAll() //允许上面使用 ANT 风格设置的全部请求
.anyRequest() //其他未设置的全部请求
.authenticated(); //需要认证
.and()
.formLogin() //设置未授权请求跳转到登录页面
.loginPage("/index.jsp") //指定登录页
.permitAll(); //为登录页设置所有人都可以访问
}
```
此时再请求未授权的资源(例如:`http://localhost:8080/SpringSecruityNotes_war/main.html`)就会重定向到`项目地址/index.jsp`界面

### loginPage(String loginPage) 方法的说明
更新loginPage的值会影响许多其他默认值,`登录表单地址`,`退出登录地址`,`登录失败地址`
例如
以下是设定formLogin()时的默认值
>/login GET - the login form
>/login POST - process the credentials and if valid authenticate the user
>/login?error GET - redirect here for failed authentication attempts
>/login?logout GET - redirect here after successfully logging out
如果将"/authenticate"传递给此方法,则将更新如下所示的缺省值
>/authenticate GET - 登录表单
>/authenticate POST - 登录后处理凭证,如果有效就验证用户
>/authenticate?error GET -身份验证尝试失败时,此处重定向错误
>/authenticate?logout GET - 成功登出后在这里重定向
于是登录的表单地址就会修改为为自定义的登录界面
### loginProcessingUrl()方法指定登录地址
loginProcessingUrl()方法指定了登录地址,就会覆盖 loginPage()方法中设置的默认值
## 设置登录系统的账号、密码并登录
验证的流程图

[commit:**放行静态资源和首页,无权限资源跳转自定义登录页面配置**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/e1e4a5a94a549f9adc98d4dd90a06b2e0febe5e6)
### 设置登录系统的账号密码
重写另外一个父类方法`configure(AuthenticationManagerBuilder builder) `
```java
/**
* 配置为无需加密,此方法在5.0以上已弃用,要求必须有默认的PasswordEncoder
* 这里只做演示需要暂用
*/
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
//super.configure(auth); 一定要禁用默认规则
builder.inMemoryAuthentication()
.withUser("tom")
.password("123123") //设置账号密码
.roles("ADMIN") //设置角色
.and()
.withUser("jerry")
.password("456456") //设置另一个账号密码
.authorities("SAVE", "EDIT"); //设置权限
}
```
Cannot pass a null GrantedAuthority collection 问题是由于没设置 roles() 或 authorities()方法导致的。
更改登录页面的表单请求
### 设置账号和密码的参数名,指定请求成功后的地址
默认账号、密码的请求参数名
>SpringSecurity 默认账号的请求参数名:username
>
>SpringSecurity 默认密码的请求参数名:password
自定义请求参数名可以修改页面上的表单项的 name 属性值,并在配置中指定
```html
//....
```
**注意:**input的name属性值必须符合SpringSecurity规则,除非专门进行了定制,否则用户名必须使用username,密码必须使用password
使用usernameParameter()和 passwordParameter()方法修改请求参数配置,同时指定登录成功后的跳转界面
```java
@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**", "/index.jsp") //使用 ANT 风格设置要授权的 URL 地 址
.permitAll() //允许上面使用 ANT 风格设置的全部请求
.anyRequest()
.authenticated()
.and()
.formLogin() //设置未授权请求跳转到登录页面
.loginPage("/index.jsp") //指定登录页
.loginProcessingUrl("/do/login.html")
.permitAll() //登录地址本身也需要放行
.usernameParameter("loginAcct") // 定制登录账号的请求参数名
.passwordParameter("userPswd") // 定制登录密码的请求参数名
.defaultSuccessUrl("/main.html"); //设置登录成功后默认前往的 URL 地址// 指定提交登录表单的地址
}
```
最后取消原来的登录请求,给界面设置form表单,指向登录请求地址
```html
${SPRING_SECURITY_LAST_EXCEPTION.message}
```
**_csrf** 为防止跨站请求伪造
> 发送登录请求时没有携带_csrf 值,则返回下面错误:
>
> 
>
> 从钓鱼网站的页面提交的请求无法携带正确、被承认的令牌
>
> 
此时登录能够实现

[commit:**设置登录系统的账号、密码并登录获取请求的资源**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/477b72ac9cf693c6b0b6e50661ff7423761f5c43)
## **用户注销**
通过调用 HttpSecurity 对象的一系列方法设置注销功能。
* logout()方法:开启注销功能
* logoutUrl()方法:自定义注销功能的 URL 地址
* logoutSuccessUrl()方法:退出成功后前往的 URL 地址
* addLogoutHandler()方法:添加退出处理器
* logoutSuccessHandler()方法:退出成功处理器
**注意:**如果 CSRF 功能没有禁用,那么退出请求必须是 POST 方式。如果禁用了 CSRF 功能则任何请求方式都可以。
```java
@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**", "/index.jsp") //使用 ANT 风格设置要授权的 URL 地 址
.permitAll() //允许上面使用 ANT 风格设置的全部请求
.anyRequest()
.authenticated()
.and()
.formLogin() //设置未授权请求跳转到登录页面
.loginPage("/index.jsp") //指定登录页
.loginProcessingUrl("/do/login.html")
.usernameParameter("loginAcct") // 定制登录账号的请求参数名
.passwordParameter("userPswd") // 定制登录密码的请求参数名
.defaultSuccessUrl("/main.html") //设置登录成功后默认前往的 URL 地址
.and()
//.csrf()
//.disable() //禁用csfr
.logout()
.logoutUrl("/do/logout.html")
.logoutSuccessUrl("/")
;
}
```
[commit:**用户注销**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/8210e5544dada06a746a8cc7a4947a04d3121638)
## 基于角色或权限进行访问控制
### 配置角色权限
通过 HttpSecurity 对象设置资源的角色要求
```java
@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**", "/index.jsp") //使用 ANT 风格设置要授权的 URL 地 址
.permitAll() //允许上面使用 ANT 风格设置的全部请求
.antMatchers("/level1/**") .hasRole("学徒")
.antMatchers("/level2/**") .hasRole("大师")
.antMatchers("/level3/**") .hasRole("宗师")
.anyRequest()
.authenticated()
....
```
通过 AuthenticationManagerBuilder 对象设置用户登录时具备的角色
```java
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
//super.configure(auth); 一定要禁用默认规则
builder.inMemoryAuthentication()
.withUser("tom")
.password("123123") //设置账号密码
.roles("ADMIN","学徒","宗师") //设置角色
.and()
.withUser("jerry")
.password("456456") //设置另一个账号密码
.authorities("SAVE", "EDIT"); //设置权限
}
```
成功登录有权限的页面

未分配角色的无权限登录

**注意:**
1. 调用顺序的范围从小到大,如果先调用了为设置请求需要验证,则权限匹配会被覆盖
```java
.antMatchers("/level1/**") //设置匹配/level1/**的地址
.hasRole("学徒") //要求具备“学徒”角色
.antMatchers("/level2/**") .hasRole("大师")
.antMatchers("/level3/**") .hasRole("宗师")
.anyRequest() //其它未设置的所有请求
.authenticated() //需要认证才可以访问
```
2. SpringSecurity 会在角色字符串前面加“ROLE\_”前缀。权限不会加,但是从数据库查询得到的用户信息、角色信息、权限信息需要我们自己手动组装。手动组装时需要我们自己给角色字符串前面加“ROLE\_”前缀。

3. 验证时会验证是否以Role_开头,所以角色名不能为 ROLE\_ 开头

[commit:**配置角色访问权限**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/140209a86a44fc5ad99d2ae255cb683347449e6e)
### 自定义 403 错误页面
* 可以使用SpringSecurity提供的默认处理器处理,转发到默认界面
在config中继续配置exceptionHandling
```java
.and()
.exceptionHandling() //指定异常处理器
.accessDeniedPage("/to/no/auth/page.html") //访问被拒绝时前往的页面
```
然后编写contrller方法处理请求转发返回的具体页面
```java
@RequestMapping("/to/no/auth/page.html")
public String toNoAuthPage() {
//请求返回的地址
return "no_auth";
}
```
* 也可以通过`accessDeniedHandler`方法,自定错误处理的方法,此方法需传入一个实现了 AccessDeniedHandler 接口的实现类
```java
.and()
.exceptionHandling() //指定异常处理器
//.accessDeniedPage("/to/no/auth/page.html") //访问被拒绝时前往的页面
.accessDeniedHandler(new AccessDeniedHandler() {
//自定义错误处理方法
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletRequest.setAttribute("message","抱歉没有访问资源的权限");
httpServletRequest.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(httpServletRequest,httpServletResponse);
}
})
```
此时如没有访问的权限会被默认转发到“no_auth.jsp”

[commit:**处理403 错误界面**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/884c2627858cdc67115f2cedbd1d6e609a96d005)
## 记住我(不重要)
### 内存版
原理 HttpSecurity 对象调用 rememberMe()方法。
```java
.and()
.rememberMe()
```
登录表单携带名为 remember-me 的请求参数。具体做法是将登录表单中的 checkbox 的 name 设置为 `remember-me `
```java
```
如果不能使用 “remember-me” 作为请求参数名称,可以使用 `rememberMeParameter()`方法定制。
[commit:**remember-me 使用cookie存储开启**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/bf1f9e06d5d8cdc2c7246c35a37f15af8c3e65e2)
### 记住我原理简要分析
通过开发者工具看到浏览器端存储了名为remember-me的Cookie。根据这个 Cookie 的 value 在服务器端找到以前登录的 User。而且这个 Cookie 被设置为存储 2 个星期的时间。


### 数据库版本
服务器重启后,cookie信息可能会清除,为了让服务器重启也不影响记住登录状态,将用户登录状态信息存入数据库。
**使用druid 演示**
引入ORM依赖
```xml
com.alibaba
druid
1.1.12
mysql
mysql-connector-java
8.0.20
org.springframework
spring-orm
${spring.version}
```
创建数据库
```sql
CREATE DATABASE `securityDemo` CHARACTER SET utf8;
```
配置数据源
```xml
```
在 WebAppSecurityConfig 类中注入数据源
```java
@Autowired
private DataSource dataSource;
```
启用令牌仓库功能
```java
JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
repository.setDataSource(dataSource);
//....
HttpSecurity 对象.tokenRepository(repository);
```
注意:需要进入 JdbcTokenRepositoryImpl 类中找到创建 persistent_logins 表的 SQL 语句创建
initDao() 是protect 的方法,因此调用必须继承JdbcTokenRepositoryImpl ,为简化代码,直接复制其 创建表的sql

创建 persistent_logins 表。
```sql
CREATE TABLE persistent_logins (
username VARCHAR ( 64 ) NOT NULL,
series VARCHAR ( 64 ) PRIMARY KEY,
token VARCHAR ( 64 ) NOT NULL,
last_used TIMESTAMP NOT NULL
);
```
登录后信息会记录到数据库中

[commit:**remember-me 使用数据库存储token信息**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/d53d6ca9796afff194538aec5a41ce3179e00021)
## **查询数据库完成认证**
### SpringSecurity 默认实现
builder.jdbcAuthentication().usersByUsernameQuery("tom");
在 `usersByUsernameQuery("tom")` 等方法中最终调用JdbcDaoImpl类的方法查询 数据库。
SpringSecurity 的默认实现已经将 SQL 语句硬编码在了 JdbcDaoImpl 类中。这种情况下,我们有下面三种选择:
* 按照 JdbcDaoImpl 类中 SQL 语句设计表结构。
* 修改 JdbcDaoImpl 类的源码。
* 不使用 jdbcAuthentication()。
### 自定义数据库查询方式
实现自定义的数据库查询方式需要自定义实现UserDetailsService接口的类并自动装配,然后使用`builder.userDetailsService(userDetailsService)`加载进springSecurity系统中
新建数据表
```SQL
CREATE TABLE `t_admin` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(100) NOT NULL,
`userpswd` varchar(100) NOT NULL,
`loginacct` varchar(100) NOT NULL,
`email` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `securityDemo`.`t_admin`(`username`, `userpswd`, `loginacct`, `email`)
VALUES ('汤姆', '123456', 'tom', '123@sun.com')
```
新建Admin bean
```java
package com.sun.springsecruity.bean;
public class Admin {
private Integer id;
private String loginacct;
private String username;
private String userpswd;
private String email;
// ...getter and setter
```
实现 UserDetailsService接口的类,并注入spring容器中
```java
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 根据表单提交的用户名查询User对象,并装配角色权限等信息
*
* @param username 表单提交的用户名
* @return userDetails
* @throws UsernameNotFoundException 查询错误
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 根据用户名在数据库中查询
String sql ="select id,loginacct,userpswd,username ,email from t_admin where loginacct= ?";
List list=jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Admin.class), username);
if (list.size()==0) {
throw new UsernameNotFoundException("无法查找到用户" + username);
}
Admin admin = list.get(0);
// 2. 给admin设置角色信息
List authorities = new ArrayList<>();
// 添加角色
authorities.add(new SimpleGrantedAuthority("Role_ADMIN"));
// 添加权限
authorities.add(new SimpleGrantedAuthority("UPDATE"));
// 3. 把admin对象和authorities封装到userDetials 中
return new User(username, admin.getUserpswd(), authorities);
}
}
```
**注意**
在自定义的 UserDetailsService 中 ,使用`org.springframework.security.core.authority.AuthorityUtils.createAuthorityList(String...) `工具方法获取创建 `SimpleGrantedAuthority` 对象添加角色时需要手动在角色名称前加“ROLE_”前缀。
**使用自定义** **UserDetailsService** **完成登录**
```java
// 注入UserDetailsServiceImpl
@Autowired
private UserDetailsServiceImpl detailsService;
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(detailsService);
}
```
[commit:**自定义从数据库查询用户完成认证**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/020f2d3511c0ba39a23287c85d91c89654213d6b)
## **应用自定义密码加密规则**
通常在数据库中的密码都会进行加密后存储,可以使用Spring Security 来实现自定义的加密规则。要实现自定义加密规则,可以通过实现 `org.springframework.security.crypto.password.PasswordEncoder`接口,设置自定义的加密规则。
实现该接口需实现两个方法
* encode()方法对明文进行加密。
* matches()方法对明文加密后和密文进行比较。
```java
@Component
public class PasswordEncoderServiceImpl implements PasswordEncoder {
public static void main(String[] args) {
System.out.println(new PasswordEncoderServiceImpl().privateEncode("123456"));
}
/**
* 密码加密
* @param rawPassword 原始密码
*/
@Override
public String encode(CharSequence rawPassword) {
return privateEncode(rawPassword);
}
/**
* 密码匹配
* @param rawPassword 原始密码
* @param encodedPassword 加密的密码,通常从数据库中取得
* @return 匹配结果
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
// 1.对明文密码加密
String formPassword = privateEncode(rawPassword);
// 2. 声明数据库密码
String databasePassword = encodedPassword;
// 3. 比较密码
return Objects.equals(formPassword, databasePassword);
}
/**
* 自定义加密规则
* @param rawPassword 原始密码
* @return 加密后的密码
*/
private String privateEncode(CharSequence rawPassword){
String algorithm = "MD5";
try {
// 1. 创建MessagDigest对象
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
// 2. 获取rawPassword的字节数组
byte[] input = ((String)rawPassword).getBytes();
// 3. 加密
byte[] output = messageDigest.digest(input);
// 4. 转为16进制的对应字符
return new BigInteger(1, output).toString(16).toUpperCase();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
}
```
在配置类的configure 方法中应用自定义加密规则
```java
@Autowired
private PasswordEncoderServiceImpl passwordEncoderService;
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(detailsService).passwordEncoder(passwordEncoderService);
}
```
[commit:**自定义加密规则**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/42199d13030bdfebd13c4cd73f58e67a80d824fc)
**问题**
Md5 加密是对明文的直接加密,密文和明文是一一对应的,因此一旦数据库泄露了,密码也会被反编译出来,因此需要对md5加密出的密文进行进一步的加密
### 盐值加密
我们可以可以使用自定义的盐值添加到明文中,进行多次的md5加密来解决md5加密撞库的问题
SpringSecurity 提供的 BCryptPasswordEncoder 加密规则。可以帮助进行更高级的加密算法
**使用方法**
BCryptPasswordEncoder 创建对象后代替自定义 passwordEncoder 对象即可 。 BCryptPasswordEncoder 在加密时通过加入随机**盐值**让每一次的加密结果都不同。能够 避免密码的明文被猜到。 而在对明文和密文进行比较时,BCryptPasswordEncoder 会在密文的固定位置取出盐值,重新进行加密。
在spring 中注入 BCryptPasswordEncoder 替换自定义的passwordEncoder
```java
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(detailsService).passwordEncoder(passwordEncoder);
}
```
[commit:**BCryptPasswordEncoder 替换自定义的passwordEncoder**](https://gitee.com/Sunxz007/SpringSecruityNotes/commit/54e55357785ed29d345e200146cacd269fdfc2bc)