原创声明:本人所发内容及涉及源码,均为亲手所撸,如总结内容有误,欢迎指出
一、组件说明
来源于百度图片,三个部分,主题Subject、安全管理器SecurityManager、Realm
1.2 Shiro配置的几个过滤器
anon(AnonymousFilter.class), authc(FormAuthenticationFilter.class), authcBasic(BasicHttpAuthenticationFilter.class), authcBearer(BearerHttpAuthenticationFilter.class), logout(LogoutFilter.class), noSessionCreation(NoSessionCreationFilter.class), perms(PermissionsAuthorizationFilter.class), port(PortFilter.class), rest(HttpMethodPermissionFilter.class), roles(RolesAuthorizationFilter.class), ssl(SslFilter.class), user(UserFilter.class), invalidRequest(InvalidRequestFilter.class);
二、整合步骤
2.1 导入依赖
<dependencies> <!-- web启动依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- thymeleaf依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- mp--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.0</version> </dependency> <!-- shiro依赖--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.8.0</version> </dependency> </dependencies>
2.2 配置文件
server: port: 2022 spring: # thymeleaf配置 thymeleaf: prefix: classpath:/templates/ suffix: .html encoding: UTF-8 cache: false # 数据源配置 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql:///shiro-spring-boot-demo?characterEncoding=utf-8&useSSL=false username: root password: root # mp的配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl type-aliases-package: com.cxs.model mapper-locations: classpath:mapper/*.xml global-config: banner: false
2.3 编写认证Realm
AuthorizingRealm有两个方法需要我们重写
-
doGetAuthenticationInfo:自定义认证
根据用户输入的用户名查数据库,存在将其封装为一个AuthenticationInfo对象返回,不存在自己处理,这里抛出UnknownAccountException异常,这个异常会在异常处理器处理(后面提)
AuthenticationInfo对象中会有一个载荷,实现类SimpleAuthenticationInfo构造方法中的第一个参数,这个载荷用于认证成功后Shiro存储的实体,可以自定义,我这直接将用户信息存里面了,但是存什么,在 subject.getPrincipal()中取出来的就是什么,建议将用户id和用户名存一下
-
doGetAuthorizationInfo:自定义授权
根据认证成功后Shiro存的载荷查询用户应有的角色权限,封装到一个AuthorizationInfo对象中,由Shiro的authc过滤器去判断是否有权限,看下其继承结构
/* * @Project:spring-boot-shiro-demo * @Author:cxs * @Motto:放下杂念,只为迎接明天更好的自己 * */ public class AuthRealm extends AuthorizingRealm { @Autowired private UserService userService; public AuthRealm(){ // 注入密码加密 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashIterations(1024); matcher.setHashAlgorithmName("MD5"); this.setCredentialsMatcher(matcher); } public static void main(String[] args) { Md5Hash md5Hash = new Md5Hash("user","user",1024); System.out.println(md5Hash.toString()); } /** * 负责认证 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { if (token == null || token.getPrincipal() == null) { throw new UnknownAccountException("用户不存在!"); } // 从 AuthenticationToken 中获取当前用户 String username = (String) token.getPrincipal(); // 查询数据库获取用户信息,此处使用 Map 来模拟数据库 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUserName,username); User user = userService.getOne(wrapper); // 用户不存在 if (user == null) { throw new UnknownAccountException("用户不存在!"); } /** * 将获取到的用户数据封装成 AuthenticationInfo 对象返回,此处封装为 SimpleAuthenticationInfo 对象。 * 参数1. 认证的实体信息,可以是从数据库中获取到的用户实体类对象或者用户名 * 参数2. 查询获取到的登录密码 * 参数3. 盐值 * 参数4. 当前 Realm 对象的名称,直接调用父类的 getName() 方法即可 */ SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName()); // 设置盐 info.setCredentialsSalt(ByteSource.Util.bytes(user.getUserName())); return info; } /** * 负责权限 * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Object principal = principals.getPrimaryPrincipal(); User token = (User) principal; // 这个步骤在认证之后,principal必定不为空 // 根据用户获取对应权限,这里就不获取了,因为权限和用户在一张表里 Set<String> roles = new HashSet<>(); roles.add(token.getRole()); AuthorizationInfo info = new SimpleAuthorizationInfo(roles); return info; } }
2.4 编写Shiro核心配置类
以下两步配置后不会发生无限重定向,去他的不说了,注释都写了
-
shiroFilterFactoryBean.setLoginUrl("/login"); 配置登录页面的地址,
-
放行/login路径
/* * @Project:spring-boot-shiro-demo * @Author:cxs * @Motto:放下杂念,只为迎接明天更好的自己 * */ @Configuration public class ShiroConfig { /** * 注入realm * @return */ @Bean public AuthRealm authRealm(){ return new AuthRealm(); } /** * 配置 SecurityManager */ @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //设置Realm securityManager.setRealm(authRealm()); return securityManager; } /** * 配置访问资源需要的权限 */ @Bean ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 指定登录的地址,请勿指定xxx.html,说过了templates下的文件不能通过浏览器直接访问 shiroFilterFactoryBean.setLoginUrl("/login"); // 自定义过滤器 LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/error", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/auth/login", "anon"); // anno可匿名访问 filterChainDefinitionMap.put("/**", "authc"); // 其他所有资源均需登录才能访问 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } //开启对shior注解的支持 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager()); return advisor; } }
2.5 编写接口
登陆接口,
User user = (User) subject.getPrincipal();
这里取出来的值是realm中决定的,不一致或发生类型转换异常(ClassCastException)
/** * 用户认证接口 * @param request * @param model * @param username * @param password * @return */ @PostMapping("/auth/login") public String authLogin(HttpServletRequest request, Model model, String username, String password){ if (!StringUtils.hasLength(username)) { model.addAttribute("msg", "用户名不能为空"); return "login"; } if (!StringUtils.hasLength(password)) { model.addAttribute("msg", "密码不能为空"); return "login"; } // 获取当前用户主体 Subject subject = SecurityUtils.getSubject(); // 将用户名和密码封装成 UsernamePasswordToken 对象 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username.trim(), password.trim()); // 执行认证流程,走我们的自定义realm subject.login(usernamePasswordToken); // 登录成功,将用户信息存于session HttpSession session = request.getSession(); // 这里获取的载荷是realm中决定的 User user = (User) subject.getPrincipal(); session.setAttribute("user", user); return "index"; }
一个列表接口,这个接口只有admin角色才能访问,有两个用户
admin拥有admin角色,user拥有user角色,数据库sql文件中内置了
@GetMapping("/list") @RequiresRoles("admin") public String list(Model model){ model.addAttribute("list", userService.list()); return "list"; }
2.6 统一的异常处理器
为了错误稍微的好看些,配置个异常处理器,主要处理两个异常,一个认证、一个授权,有需要可以自己加
新建了一个error页面,来显示错误
/* * @Project:spring-boot-shiro-demo * @Author:cxs * @Motto:放下杂念,只为迎接明天更好的自己 * */ @ControllerAdvice public class ShiroExceptionHandler { /** * 处理用户认证时所抛出的异常 * @param model * @param e * @return */ @ExceptionHandler(value = AuthenticationException.class) public String authExceptionHandle(Model model, AuthenticationException e){ if (e instanceof UnknownAccountException) { // 用户名不存在会抛出这个异常 model.addAttribute("msg", "用户名不存在"); return "error"; } else if (e instanceof CredentialsException) { // 密码验证失败会抛出这个异常 model.addAttribute("msg", "密码验证失败"); return "error"; } else { model.addAttribute("msg", "用户名或密码错误"); return "error"; } } /** * 处理授权时抛出的异常 * @param model * @param e * @return * AuthorizationException 用户无权限访问资源会抛出这个异常 */ @ExceptionHandler(value = AuthorizationException.class) public String forbiddenExceptionHandle(Model model, AuthorizationException e){ model.addAttribute("msg", "用户权限不足,拒绝访问"); return "error"; } }
三、成果展示
3.1 admin/admin登录
3.2 admin查看用户列表
3.3 user/user登录
3.4 user查看用户列表
3.5 用户名或密码错误
四、源代码地址
码云:https://gitee.com/whole-stack-of-white/shiro-spring-boot-demo
