认证逻辑
-
所有请求都会被shiro过滤器拦截,这是我们需要在过滤器中放行某些可以访问的公共资源,例如注册页面、登录页面;以及配置某些认证后才能访问的资源,例如只有登录后才能访问首页;自定义realm规则,将该规则设置进安全管理器
| |
| |
| |
| @Configuration |
| public class ShiroConfig { |
| |
| |
| @Bean(name = "shiroDialect") |
| public ShiroDialect shiroDialect(){ |
| return new ShiroDialect(); |
| } |
| |
| |
| @Bean |
| public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ |
| ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); |
| |
| |
| shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); |
| |
| Map<String,String> map = new HashMap<String,String>(); |
| |
| map.put("/login.html","anon"); |
| map.put("/register.html","anon"); |
| map.put("/user/register","anon"); |
| map.put("/user/login","anon"); |
| |
| map.put("/**","authc"); |
| |
| shiroFilterFactoryBean.setLoginUrl("/user/login"); |
| shiroFilterFactoryBean.setFilterChainDefinitionMap(map); |
| |
| return shiroFilterFactoryBean; |
| } |
| |
| |
| @Bean |
| public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){ |
| DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); |
| |
| defaultWebSecurityManager.setRealm(realm); |
| |
| return defaultWebSecurityManager; |
| } |
| |
| |
| @Bean |
| public Realm getRealm(){ |
| CustomerRealm customerRealm = new CustomerRealm(); |
| |
| |
| HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); |
| |
| credentialsMatcher.setHashAlgorithmName("MD5"); |
| |
| credentialsMatcher.setHashIterations(1024); |
| customerRealm.setCredentialsMatcher(credentialsMatcher); |
| |
| return customerRealm; |
| } |
| |
| } |
| |
-
首先我们注册用户,该请求不会被拦截到达控制层,之后在业务层对明文密码进行md5加密加盐散列操作,同时将身份信息、加密后的凭证信息、随机盐保存到数据库
| @Override |
| public void register(User user) { |
| |
| String salt = SaltUtils.getSalt(8); |
| |
| user.setSalt(salt); |
| |
| Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024); |
| user.setPassword(md5Hash.toHex()); |
| userDAO.save(user); |
| } |
| |
-
登录页面属于公共资源,之后再登录页面发送请求到达控制层,登录时的主身份信息和凭证信息会作为token到达安全管理器,凭证信息会使用注册时的加密逻辑进行加密
| |
| |
| |
| @RequestMapping("login") |
| @ResponseBody |
| public RespResult login(String username, String password,String code,HttpSession session,Model model) { |
| |
| String codes = (String) session.getAttribute("code"); |
| try { |
| if (codes.equalsIgnoreCase(code)){ |
| |
| Subject subject = SecurityUtils.getSubject(); |
| |
| subject.login(new UsernamePasswordToken(username, password)); |
| return RespResult.success(""); |
| }else{ |
| throw new RuntimeException("验证码错误!"); |
| } |
| } catch (UnknownAccountException e) { |
| e.printStackTrace(); |
| System.out.println("用户名错误!"); |
| } catch (IncorrectCredentialsException e) { |
| e.printStackTrace(); |
| System.out.println("密码错误!"); |
| }catch (Exception e){ |
| e.printStackTrace(); |
| System.out.println(e.getMessage()); |
| } |
| return RespResult.error("认证失败!"); |
| } |
| |
-
之后会进入自定义的Realm,我们会重写doGetAuthenticationInfo方法,在该方法中会查询数据库中该主体的数据,并与token中数据对比,相同则表示认证成功,跳转到首页;
当前登录的数据封装成一个令牌,由安全管理器中的凭证管理器(credentialsMatcher)进行处理,与数据库中查询到的数据进行比较,凭证管理器会自动对登录数据进行加盐、加密、散列操作;因为在第一步的shiroConfig中已经设置过了
| @Override |
| protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { |
| |
| String principal = (String) token.getPrincipal(); |
| |
| UserService userService = (UserService) ApplicationContextUtils.getBean("userService"); |
| |
| User user = userService.findByUserName(principal); |
| if(!ObjectUtils.isEmpty(user)){ |
| return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), |
| new MyByteSource(user.getSalt()), |
| this.getName()); |
| } |
| return null; |
| } |
| |

认证的具体实现
- 创建安全管理器
| DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); |
| |
- 注入realm
- 设置加密逻辑
| CustomerMd5Realm realm = new CustomerMd5Realm(); |
| |
| HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); |
| |
| credentialsMatcher.setHashAlgorithmName("md5"); |
| |
| credentialsMatcher.setHashIterations(1024); |
| realm.setCredentialsMatcher(credentialsMatcher); |
| defaultSecurityManager.setRealm(realm); |
- 将安全管理器注入安全工具类
| SecurityUtils.setSecurityManager(defaultSecurityManager); |
- 获取主体
- 将登录时的主身份信息和凭证信息作为token
| Subject subject = SecurityUtils.getSubject(); |
| UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123"); |
| subject.login(token); |
- 进入自定义realm中的doGetAuthenticationInfo方法,与查询到的用户名和密码进行比较
授权逻辑
- 每个主体拥有角色权限和资源权限,只有在认证成功后才能进行授权操作
- 在一个系统中前端资源或者后端路由上标注了具有某些角色权限和资源权限才能访问,当要访问这些资源时,都会进入自定义realm的doGetAuthorizationInfo方法中,根据登录时的主身份信息获取数据库中主体所拥有的角色权限和资源权限,并将权限赋给当前主体,之后与资源上指定的权限判断是否拥有所需的权限来决定是否可访问
授权的具体实现
- 在自定义realm的doGetAuthorizationInfo方法上打上断点,浏览器访问认证成功后才能访问的页面
- 在该方法中首先会获取主身份信息,根据主身份信息会去数据库查询该账号所拥有的角色权限和资源权限
- 将获取的权限添加到simpleAuthorizationInfo对象,那么该主体就可以访问对应的资源
| |
| |
| |
| @Override |
| protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { |
| |
| String primaryPrincipal = (String) principals.getPrimaryPrincipal(); |
| System.out.println("调用授权验证: "+primaryPrincipal); |
| |
| UserService userService = (UserService) ApplicationContextUtils |
| .getBean("userService"); |
| User user = userService.findRolesByUserName(primaryPrincipal); |
| |
| if(!CollectionUtils.isEmpty(user.getRoles())){ |
| SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); |
| user.getRoles().forEach(role->{ |
| simpleAuthorizationInfo.addRole(role.getName()); |
| |
| List<Perms> perms = userService.findPermsByRoleId(role.getId()); |
| if(!CollectionUtils.isEmpty(perms)){ |
| perms.forEach(perm->{ |
| simpleAuthorizationInfo.addStringPermission(perm.getName()); |
| }); |
| } |
| }); |
| return simpleAuthorizationInfo; |
| } |
| return null; |
| } |
| |
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术