基础环境:依赖 parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.2.1.RELEASE/version relativePath/ !-- lookup parent from repository --/parent dependency groupIdorg.apache.shiro/g
基础环境:依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.4.0</version> </dependency>
如果不是前后端分离,要实现页面级的权限控制,则加入以下依赖就可以使用shiro的权限标签了(记得在html头部加上相应约束:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="Thymeleaf" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" lang="en"> ): <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
Realm:认证鉴权器
package com.rz.monomer.modules.shiro; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.rz.monomer.modules.user.entity.SysUserInfo; import com.rz.monomer.modules.user.entity.SysUserRole; import com.rz.monomer.modules.user.service.SysButtonInfoService; import com.rz.monomer.modules.user.service.SysUserInfoService; import com.rz.monomer.modules.user.service.SysUserRoleService; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.Set; import java.util.stream.Collectors; /** * 认证、鉴权类(必须) * * @author sunziwen * @version 1.0 * @date 2019/11/14 14:06 **/ @Slf4j public class ShiroRealm extends AuthorizingRealm { //以下三个服务是普通Dao查询,从数据库查询用户及其角色权限信息(这个类没有自动注入,需要在下个文件中手动注入) private SysUserInfoService userInfoService; private SysButtonInfoService buttonInfoService; private SysUserRoleService userRoleService; public ShiroRealm(SysUserInfoService userInfoService, SysButtonInfoService buttonInfoService, SysUserRoleService userRoleService) { this.userInfoService = userInfoService; this.buttonInfoService = buttonInfoService; this.userRoleService = userRoleService; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { log.info("check authorization info"); SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo(); // 获取当前用户 SysUserInfo userInfo = (SysUserInfo) principals.getPrimaryPrincipal(); // 查询角色信息 Set<Long> userRoles = userRoleService.list(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getUserId, userInfo.getId())) .stream() .map(SysUserRole::getRoleId) .collect(Collectors.toSet()); //角色所有权限 Set<String> perms = buttonInfoService.getPermsByRoles(userRoles); authInfo.addStringPermissions(perms); return authInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { log.info("check authentication info"); String username = (String) token.getPrincipal(); // 获取用户信息 SysUserInfo user = userInfoService.getOne(new LambdaQueryWrapper<SysUserInfo>().eq(SysUserInfo::getUsername, username)); if (user == null) { return null; } /*SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(654321), getName());*/ SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName()); return authenticationInfo; } }
WebSecurityManager:安全管理器
package com.rz.monomer.modules.shiro; import com.rz.monomer.modules.user.service.SysButtonInfoService; import com.rz.monomer.modules.user.service.SysUserInfoService; import com.rz.monomer.modules.user.service.SysUserRoleService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import org.apache.shiro.web.filter.authc.LogoutFilter; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; /** * Shiro配置类(必须) * * @author sunziwen * @version 1.0 * @date 2019/11/14 14:08 **/ @Configuration @Slf4j @AllArgsConstructor public class WebSecurityManager { private SysUserInfoService userInfoService; private SysButtonInfoService buttonInfoService; private SysUserRoleService userRoleService; /** * 安全管理器 */ @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm()); return securityManager; } /** * 认证鉴权器(安全管理器的依赖) */ @Bean public ShiroRealm realm() { return new ShiroRealm(userInfoService, buttonInfoService, userRoleService); } /** * 配置拦截规则 */ @Bean public ShiroFilterFactoryBean filter(org.apache.shiro.mgt.SecurityManager securityManager) { log.info("config shiro filter"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 定义URL拦截链 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 允许匿名用户访问首页 filterChainDefinitionMap.put("/shiro/index", "anon"); // 定义注销路径 filterChainDefinitionMap.put("/shiro/logout", "logout"); // 所有用户界面都需要身份验证,否则会跳转到loginurl,由FormAuthenticationFilter处理 filterChainDefinitionMap.put("/shiro/user/**", "authc"); // 为login路径定义拦截,由FormAuthenticationFilter处理 filterChainDefinitionMap.put("/shiro/login", "authc"); // 所有vip路径要求具备vip角色权限 filterChainDefinitionMap.put("/shiro/vip/**", "roles[vip]"); // 指定loginurl 路径 shiroFilterFactoryBean.setLoginUrl("/shiro/login"); // 登录成功后跳转路径 shiroFilterFactoryBean.setSuccessUrl("/shiro/user/"); // for un authenticated shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/unauth"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // 自定义filters,可覆盖默认的Filter列表,参考 DefaultFilter Map<String, Filter> filters = new LinkedHashMap<String, Filter>(); // 定制logout 过滤,指定注销后跳转到登录页(默认为根路径) LogoutFilter logoutFilter = new LogoutFilter(); logoutFilter.setRedirectUrl("/shiro/login"); filters.put("logout", logoutFilter); // 定制authc 过滤,指定登录表单参数 FormAuthenticationFilter authFilter = new FormAuthenticationFilter(); authFilter.setUsernameParam("username"); authFilter.setPasswordParam("password"); filters.put("authc", authFilter); shiroFilterFactoryBean.setFilters(filters); return shiroFilterFactoryBean; } }
Test:登录测试
package com.rz.monomer.modules.user.controller; import com.rz.monomer.commons.utils.Md5Encrypt; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @RestController @Slf4j public class LoginController { @PostMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password) { Subject subject = SecurityUtils.getSubject(); AuthenticationToken token = new UsernamePasswordToken(username, Md5Encrypt.md5(password)); try { // 执行登录 subject.login(token); } catch (UnknownAccountException e) { // 未知用户 log.warn("the account {} is not found", username); return "account not found"; } catch (IncorrectCredentialsException e) { // 用户或密码不正确 log.warn("the account or password is not correct"); return "account or password not correct"; } return "login success"; } }
补充:SpringBoot配置Shiro时踩坑
在SpringBoot2.0整合shiro时使用@EnableAutoConfiguration的时候需要对config文件进行扫描,即使用@ComponentScan对配置进行扫描。
或者直接使用@SpringBootApplication,但是这种方法会将主方法目录下的所有package都进行扫描影响项目效率。
Authentication failed for token submission [org.apache.shiro.authc.UsernamePasswordToken - zxc, rememberMe=false]. Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException
当出现此异常时,一般情况是用户名密码不匹配,或者是在配置对应的Realm时出现空值导致匹配失败。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持易盾网络。如有错误或未考虑完全的地方,望不吝赐教。