文章目录
-
- 前言
- 代码部分
- Shiro框架的引入
- 下章预告
- 结语
前言
由于将用户的账号密码明文存储在数据库中具有不安全性,比如容易泄露,用户觉得不靠谱等,所以我们需要将用户的密码加密存储在数据库中。
- 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 。
@Service public class UserService { @Autowired private UserMapper userMapper; public boolean checkUser(User user){ User u = userMapper.selectUser(user); if(u == null){ return false;//如果账号密码输入不正确,返回false } return true; } public boolean isExist(String username){ User user = userMapper.isExist(username); if(user != null){ return true;//从数据库可以查找出用户 } return false; } }
-
用户注册
前面说过,每个用户生成唯一的盐,所以我们得保证用户是唯一的,这样就需要用户注册的时候验证数据库中是否存在该账号。
<select id="isExist" resultType="com.demo.demo.Pojo.User"> select * from user where username = #{username} </select>
@PostMapping("/register") public String register(@RequestBody User user){ if(userService.isExist(user.getUsername())){ return "用户名已存在"; } // 根据用户名生成盐 String salt = new SecureRandomNumberGenerator().nextBytes().toString(); // 根据生成的盐和用户输入的密码迭代2次生成新的密码 String newPassword = new SimpleHash("md5", user.getPassword(), salt, 2).toString(); // 存储用户信息,包括 salt 与 hash 后的密码 user.setSalt(salt); user.setPassword(newPassword); userService.add(user); return "注册成功"; }
- 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.4.0</version> </dependency>
- 前端提供一个注册的按钮写注册函数
register(){ var params = { username:this.username, password:this.password } axios.post('http://localhost:9527/user/register',params).then(res => { consoel.log(res) }) }
-
验证
当输入已存在用户名:
当输入不同用户名: 可以看到,数据库中存储了用户的密码和盐值都是加密方式的,大大提高了用户信息的安全性。 以后用户登录的过程:- 前端明文提交账号密码
- 后端根据用户名取出数据库中对应的盐值
- 根据取出的盐值进行同样hash加密两次
- 比如加密后的结果与数据库中的是否一致
Shiro框架的引入
Shiro提供了一系列安全的加密验证,用户认证,用户授权,用户会话等方法,我们的一切验证授权手段都是在调用Shiro的API,与之相类似的有Spring Security。
-
Shiro的三个概念
Subject:当前与Shiro交互的东西,比如前端发送登录请求到后端,那么Subject就是提交的用户类。
SecurityManager :管理全部的Subject。
Realms:将用户信息进行一系列处理传递给SecurityManage。
-
Realm的配置
我们说过,Realm是输入的用户信息与SecurityManaga之间的桥梁,那么我们需要在Realm中做什么呢?比如对用户输入的用户名变成盐,对输入的密码进行加密等,操作完后才提交给SecurityManage。
我们创建Realm文件夹下创建UserRealm.java文件,在其中对用户信息进行处理。
public class UserRealm extends AuthorizingRealm {//所有自定义的Realm必须继承AuthorizingRealm @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null;//对登录用户的授权,现在我们先不写 } //此部分处理登录 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = token.getPrincipal().toString();//token由控制层传入,获取token中存储的用户名 User user = userService.getUserByName(username);//根据用户名去数据库查询是否存在该用户 if(user == null){ throw new UnknownAccountException();//用户不存在抛出不存在异常交给控制层处理 } String password = user.getPassword(); String salt = user.getSalt(); //再次把salt转成byte将整个认证交给SecurityManage SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes(salt),getName()); return authenticationInfo; } }
-
Shiro配置
在Config文件夹下创建ShiroConfig.java文件进行Shiro配置
package com.demo.demo.Confg; import com.demo.demo.Realm.UserRealm; import org.apache.shiro.authc.AbstractAuthenticator; 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; @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配置完成,已实现基本的登录。
下章预告
- 对登录的用户给予授权凭证
- 登出功能
结语
有问题的可以在评论区留言哦,也可以提出意见或者建议。