文章目录
前言
由于将用户的账号密码明文存储在数据库中具有不安全性,比价容易泄露,用户觉得不靠谱等,所以我们需要将用户的密码加密存储在数据库中。
- Hash 加密
hash算法(散列算法、摘要算法)即把任意长度的输入映射为固定长度的输出,比如密码Evanniubi变成五位的输出kchpl,这种算法不可逆,且存在信息损失,虽然随着时间推移,出现了字典法、彩虹表法等优化手段,但本质上想要破解还是靠穷举与瞎蒙,而且对于复杂密码来说,破解成本极高。
- 加盐加密
加盐,是提高hash算法的安全性的一个常用手段。其意义在于利用你的账户生成‘盐’,通过这个盐生成密码并用hash加密。
为什么更安全? - 黑客就算拿到数据库中的密码也无法使用。
- 即使用户使用相同的密码,由于各个用户的盐不同,所以也无法破解用户的密码。
代码部分
我们首先要在user表中添加salt字段,存储每个用户生成的“盐”。
在SpringBoot的pojo文件夹下的User同样增加salt字段以及get,set方法。
-
引入service 层
大型的项目中,一般不由控制层直接调用mapper层,所以需要提供一个service层,让它担当controller层与mapper层的媒介,接收controller层的请求,处理mapper层的数据。所以我们在项目文件下创建service文件夹,创建UserService.java文件,注解@Service,代表Service 层,并在其中对UserMapper进行@Autowired。 -
用户注册
前面说过,每个用户生成唯一的盐,所以我们的保证用户是唯一的,这样就需要我们用户注册的时候验证数据库中是否存在该账号。
UserMapper.java
User isExist(String username);
UserMapper.xml
<select id="isExist" resultType="com.jkd.springbootproject.pojo.User">
select * from user where username = #{username}
</select>
// 用户注册
@PostMapping("register")
public String register(@RequestBody User user){
System.out.println(user.getUsername());
if (userService.isExist(user.getUsername())){
return "用户名已存在";
}
// 根据用户名生成盐
String salt = new SecureRandomNumberGenerator().nextBytes().toString();
// 根据生成的盐和用户输入的密码迭代2次生成新的密码
String newPasswoed = new SimpleHash("md5",user.getPassword(),salt,2).toString();
// 存储用户信息,包括salt与hash后的密码
user.setSalt(salt);
user.setPassword(newPasswoed);
userService.add(user);
return "注册成功";
}
- Shiro的Jar包引入
<!--shiro的jar包引入-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>
- 验证
当输入已存在用户名:
当输入不同用户名:
可以看到,数据库中存储了用户的密码和盐值都是加密方式的,大大提高了用户信息的安全性。
以后用户登录的过程:- 前端明文提交账号密码
- 后端根据用户名取出数据库中对应的盐值
- 根据取出的盐值进行同样hash加密两次
- 比如加密后的结果与数据库中的是否一致
Shiro框架的引入
Shiro提供了一系列安全的加密验证,用户授权,用户会话等方法,我们的一切验证授权手段都是在调用Shiro的API,与之相类似的有Spring Security。
- List item
Shiro的三个概念
Subject: 当前与Shiro交互的东西,比如前端发送登录请求到后端,那么Subject就是提交的用户类。
SecurityManager: 管理全部的Subject。
Realms: 将用户信息进行一系列处理 传递给SecurityManage。 - Realm的配置
我们说过,Realm是输入的用户信息与SecurityManage之间的桥梁,那么我们需要在Realm中做什么呢?比如对用户输入的用户名变成盐,对输入的密码进行加密等,操作完成后提交给SecurityManage。
我们创建Realm文件夹下创建UserRealm.java文件,在其中对用户信息进行处理。
/**
* @projectName: springbootproject
* @package: com.jkd.springbootproject.Realm
* @className: UserRealm
* @author: JKD
* @description: 用户信息处理
* 所有自定义的Realm必须继承AuthorizingRealm
* @version: 1.0
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 对登录用户的授权,现在我们先不写
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 此部分处理登录
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// token由控制层传入,获取token中存储的用户名
String username = authenticationToken.getPrincipal().toString();
// 根据用户名去数据库查询是否存在该用户
User user = userService.getUserByName(username);
if (user == null){
// 用户不存在抛出不存在异常交给控制层处理
throw new UnknownAccountException();
}
String password = user.getPassword();
String salt = user.getSalt();
// 再次把salt转成byte将整个认证交给SecurityManage
return new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), getName());
}
}
- Shiro配置
在Config文件夹下创建ShiroConfig.java文件进行Shiro配置
package com.jkd.springbootproject.config;
import com.jkd.springbootproject.Realm.UserRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;
/**
* @projectName: springbootproject
* @package: com.jkd.springbootproject.config
* @className: ShiroConfig
* @author: JKD
* @description: Shiro配置
*/
@Configuration
public class ShiroConfig {
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(getUserRealm());
return securityManager;
}
@Bean
public UserRealm getUserRealm() {
UserRealm realm = new UserRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
@Bean
public CredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
- 登录控制层重写
@PostMapping("/login")
public String Login(@RequestBody User user){
Subject subject = SecurityUtils.getSubject();
// Shiro帮我们写好了usernamePasswordToken,只要提交账号密码,后面的交给Realm,Realm交给SecurityManage
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
// 只要一行代码就能实现登录
try {
subject.login(token);
return "登录成功";
}catch (UnknownAccountException e){ // 处理我们在Realm中抛出的异常
return "用户不存在";
} catch (AuthenticationException e) { // 当Shiro发现用户的账号密码不匹配时自动抛出这个异常
return "账号或密码错误";
}
}
- 登录验证一下
发现登录成功,说明我们的Shiro配置完成,已实现基本的登录。
下章预告
- 对登录的用户给予授权凭证
- 登出功能
结语
参考文章:https://blog.csdn.net/qq_42241722/article/details/104269094
版权声明:本文为WuKongHeBaJie原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。