本文简述:密码加盐,比较策略修改.关于密码加盐详解,见此文章
shiro 完整流程以及集成:
- 用户访问注册接口
/regester
, 用户输入登录账号和密码,将密码加盐后存入数据库 - 用户访问登录接口
/login
, 用户输入登录账号和密码被封装成UsernamePasswordToken
对象,然后调用subject.login()
方法 - 在 shiroConfig 设置
filterChainDefinitionMap.put("/login", "anon");
, 也就意味着将/login
登录请求交给shiro提供的 anno 过滤器
处理。 - 因此,登录服务中调用
subject.login()
方法,shiro 立即进入用户认证过程,此认证过程就是检验用户账号和密码是否正确,交给了自定义Realm
来处理。 - 在
自定义Realm
中,方法doGetAuthenticationInfo(AuthenticationToken authenticationToken)
接收一个参数authenticationToken
。
此时注意第 2 步,登录账号和密码被封装成了UsernamePasswordToken
对象,而UsernamePasswordToken
是AuthenticationToken
接口的实现类,因此这里的authenticationToken
内部就储存这用户输入的账号和密码.
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
流程例子
shiro 运行流程,使用此小例子简单说明。(由于此处做演示,为了代码可以运行,因此没有 subjet.login() 这一行代码)
@SpringBootTest
class UserControllerTest {
@Test
public void testShiroEncryptPassword(){
// 第一步 (登录封装)
String username = "ifredom";
String password = "123456";
String salt = RandomStringUtils.randomAlphanumeric(20);
UserEntity userEntity = new UserEntity();
userEntity.setUsername(username);
userEntity.setPassword(password);
AuthenticationToken token = new UsernamePasswordToken(username, password);
// 第二步 (认证流程)
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userEntity, //用户名
password, //密码
ByteSource.Util.bytes(salt),// salt
"anyRealmName" //realm name
);
// 第三步 (返回,shiro内部比较)
// 比较
SimpleCredentialsMatcher matcher = new SimpleCredentialsMatcher();
boolean match = matcher.doCredentialsMatch(token, authenticationInfo);
System.out.println(match);
if (match) {
System.out.println("密码相同");
}else{
System.out.println("密码不同");
}
}
}
controller 注册与登录
注册用户时,为了保护用户的密码,使用 MD5 算法对其进行一层加密,并将加密后的密码存入数据库。
public class UserVo extends UserEntity {
}
将密码进行加盐(密)处理:
- 使用 shiro 提供的类 SimpleHash ,对密码执行 MD5 hash 算法,加密后存入数据库
- 使用第三方工具包
commons-lang3
提供的方法生成一个随机字符串,取名为salt
盐,并存入数据库
@RestController
public class UserController {
@Autowired
private UserService userService;
/**
* 注册新用户
*
*/
@PostMapping("/register")
public AjaxResult registerUser(@RequestBody UserVo userVo) {
UserEntity user = new UserEntity();
String salt = RandomStringUtils.randomAlphanumeric(20);
//加密
SimpleHash simpleHash = new SimpleHash("md5",userVo.getPassword(),salt,1);
String hashPassword = simpleHash.toString();
user.setUsername(userVo.getUsername());
user.setPassword(hashPassword);
user.setSalt(salt);
userService.save(user);
return AjaxResult.success();
}
/**
* 登录
*
*/
@PostMapping("/login")
public String loginUser(@Validated @RequestBody UserVo userVo, BindingResult bindingResult) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userVo.getUsername(), userVo.getPassword());
usernamePasswordToken.setRememberMe(true);
try {
subject.login(usernamePasswordToken);
return "登录成功";
} catch (AuthenticationException ae) {
return "登录失败: " + ae.getMessage();
}
return "登录成功";
}
}
@Configuration
public class ShiroConfig {
/**
* `SecurityManager`:安全管理器
*
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* `shiroFilter`:过滤器
*
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 定义过滤链
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 对静态资源设置匿名访问
filterChainDefinitionMap.put("/index.html", "anon");
filterChainDefinitionMap.put("/favicon.ico**", "anon");
filterChainDefinitionMap.put("/static/**","anon");
// 登录,不需要拦截的访问
filterChainDefinitionMap.put("/login", "anon");
// 错误页面无需认证
filterChainDefinitionMap.put("/error","anon");
// 其余资源都需要认证
filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* `Realm`:realm 认证流程
*
*/
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
/**
* 设置密码匹配策略
* 因为在注册时,密码使用MD5算法执行了 1 次hash化
*
*/
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return userRealm;
}
}
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 {
UsernamePasswordToken accessToken = (UsernamePasswordToken) authenticationToken;
// 查询用户
UserEntity user = Optional.ofNullable(userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername, accessToken.getUsername())))
.orElseThrow(() -> new UnknownAccountException("账号或密码不正确"));
// 验证密码
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user,
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),
getName()
);
return authenticationInfo;
}
}
到此,注册用户并对密码加盐就完成了,并且可以随时替换算法,只需要
- 在注册时,修改对密码执行的算法
- 在 shiroConfig 的
HashedCredentialsMatcher
中,匹配策略保持与注册时的算法一致即可