前言
Spring Boot整合Shiro进行身份认证等必须的前置性工作各位可以网上搜索一番,本篇主要是对用户登录验证自定义加密验证以及Shiro存储登录用户便于后续使用的内容。
1.1 用户邮箱登录
注:用户在配置Shiro登录地址时,实际执行登录验证逻辑请求URL要设置为不进行验证(新手)
ShiroConfig.java
/**
* Shiro Filter
*
* @param securityManager securityManager
* @return shiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
log.info("======== Shiro config ==========");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// jwt过滤器
Map<String, Filter> filterMap = shiroFilterFactoryBean.getFilters();
filterMap.put("jwt", new JWTFilter());
shiroFilterFactoryBean.setFilters(filterMap);
shiroFilterFactoryBean.setUnauthorizedUrl("/401");
// 设置登录路径
shiroFilterFactoryBean.setLoginUrl("/login");
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 实际登录地址,不能为/login
filterChainDefinitionMap.put("/doLogin", "anon");
filterChainDefinitionMap.put("/login", "anon");
......
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
复制代码
用户邮箱登录:邮箱作为账号;
LoginController.java
/**
* login
*
* @param email 登录邮箱
* @param password 登录密码
* @return login
*/
@PostMapping("/doLogin")
public String login(String email, String password) {
// 创建Subject实例
Subject subject = SecurityUtils.getSubject();
// 封装用户数据
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(email, password);
// 登录判断
try {
subject.login(usernamePasswordToken);
if (subject.isAuthenticated()) {
return "redirect:/customers";
}
} catch (UnknownAccountException e) {
log.info("---> {}登录失败", email);
}
return "login";
}
复制代码
1.2 用户密码加盐验证
注:Shiro本身支持MD5加密验证,使用HashedCredentialsMatcher配置加密规则进行加密
/**
* 这里需要设置成与PasswordEncrypter类相同的加密规则
*
* 在doGetAuthenticationInfo认证登陆返回SimpleAuthenticationInfo时会使用hashedCredentialsMatcher
* 把用户填入密码加密后生成散列码与数据库对应的散列码进行对比
*
* HashedCredentialsMatcher会自动根据AuthenticationInfo的类型是否是SaltedAuthenticationInfo来获取credentialsSalt盐
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");// 散列算法, 与注册时使用的散列算法相同
hashedCredentialsMatcher.setHashIterations(2);// 散列次数, 与注册时使用的散列册数相同
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);// 生成16进制, 与注册时的生成格式相同
return hashedCredentialsMatcher;
}
复制代码
然而并没有使用本身自带的加密方式进行用户加盐加密存储,所以需要在Realm中重写setCredentialsMatcher方法,保证自己加密和验证的统一(自定义),我选择重写方法,而非自定义加密验证类。(如下验证Realm类代码)
AuthRealmForWeb.java
/**
* 校验用户身份
*
* @param auth
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
String email = (String) auth.getPrincipal();
Optional<User> userOptional = userService.findByEmail(email);
if (!userOptional.isPresent()) {
throw new UnknownAccountException("未查询到该用户信息");
}
User user = userOptional.get();
// 此处第一个参数传递user,则将登录user信息存储备用
// 如果使用加盐验证,则第三个参数必须使用ByteSource.Util.bytes(xxx)
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(user, user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), getName());
return authenticationInfo;
}
/**
* Authorizaton 授权
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = (User) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 赋予登录用户权限
Optional<List<Role>> optionalRoleList = userRoleService.findRolesByUserId(user.getId());
if (optionalRoleList.isPresent()) {
// 授权
for (Role role : optionalRoleList.get()) {
authorizationInfo.addRole(role.getName());
Optional<List<Permission>> optionalPermissions = rolePermissionService.findPermissionsByRoleIds(
Arrays.asList(role.getId()));
if (optionalPermissions.isPresent()) {
authorizationInfo.addStringPermissions(optionalPermissions.get()
.stream().map(Permission::getPval).collect(Collectors.toList()));
}
}
}
return authorizationInfo;
}
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
credentialsMatcher = (token, info) -> {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
// 验证时传递的加密盐
String salt = new String(((SimpleAuthenticationInfo) info).getCredentialsSalt().getBytes());
// 登录录入的密码
String password = new String(usernamePasswordToken.getPassword());
// 自定义的加盐加密方式
String realPassword = Hashing.sha512().hashString(password + salt,
Charsets.UTF_8).toString().substring(0, 17);
return realPassword.equalsIgnoreCase(info.getCredentials().toString());
};
super.setCredentialsMatcher(credentialsMatcher);
}
复制代码
1.3 存储用户信息
存储用户信息如1.2上节所示,只需将第一个参数设置为当前用户即可:
// 此处第一个参数传递user,则将登录user信息存储备用
// 如果使用加盐验证,则第三个参数必须使用ByteSource.Util.bytes(xxx)
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(user, user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), getName());
复制代码
存储用户信息后,如何取出存储的用户信息?很简单,就存储在Subject中如下:
User currentUser = (User) SecurityUtils.getSubject().getPrincipal();
复制代码
总结
Shrio听说过但我本身未使用,只是项目中使用突击一下,网上一篇篇的发现并不适合自己需要,没有相关的代码;此篇结合自己项目的实际需要,记录一下,后续有机会更新Shiro相关的内容,加深认知;
文中如有疑问或不解,欢迎指正!