Spring Boot整合Shiro进行身份认证等必须的前置性工作各位可以网上搜索一番,本篇主要是对用户登录验证自定义加密验证以及Shiro存储登录用户便于后续使用的内容。
注:用户在配置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"; } 复制代码
注: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.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(); 复制代码