一、创建一个springboot项目
创建完成后目录如下:
补齐目录结构
二、添加依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> </parent> <groupId>org.example</groupId> <artifactId>shiro-springboot</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <spring.shiro.version>1.4.0</spring.shiro.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--页面模板依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--热部署配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${spring.shiro.version}</version> </dependency> </dependencies>
三、创建实体类
User
@Data @AllArgsConstructor public class User { private String id; private String userName; private String password; /** * 用户对应的角色集合 */ private Set<Role> roles; }
Role
@Data @AllArgsConstructor public class Role { private String id; private String roleName; /** * 角色对应权限集合 */ private Set<Permissions> permissions; }
Permissions
@Data @AllArgsConstructor public class Permissions { private String id; private String permissionsName; }
四、定义Service接口和实现类
service接口:根据username获取用户信息和角色(包含权限)信息
public interface LoginService { User getUserByName(String getMapByName); }
service实现类
import com.zwh.entity.Permissions; import com.zwh.entity.Role; import com.zwh.entity.User; import com.zwh.service.LoginService; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @Service public class LoginServiceImpl implements LoginService { @Override public User getUserByName(String getMapByName) { return getMapByName(getMapByName); } /** * 模拟数据库查询 * * @param userName 用户名 * @return User */ private User getMapByName(String userName) { Permissions permissions1 = new Permissions("1", "query"); Permissions permissions2 = new Permissions("2", "add"); Set<Permissions> permissionsSet = new HashSet<>(); permissionsSet.add(permissions1); permissionsSet.add(permissions2); Role role = new Role("1", "admin", permissionsSet); Set<Role> roleSet = new HashSet<>(); roleSet.add(role); User user = new User("1", "wsl", "123456", roleSet); Map<String, User> map = new HashMap<>(); map.put(user.getUserName(), user); Set<Permissions> permissionsSet1 = new HashSet<>(); permissionsSet1.add(permissions1); Role role1 = new Role("2", "user", permissionsSet1); Set<Role> roleSet1 = new HashSet<>(); roleSet1.add(role1); User user1 = new User("2", "zhangsan", "123456", roleSet1); map.put(user1.getUserName(), user1); return map.get(userName); } }
五、自定义Realm
import com.zwh.entity.Permissions; import com.zwh.entity.Role; import com.zwh.entity.User; import com.zwh.service.LoginService; 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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; /** * @author zhouwenhao * @date 2021/12/1 * @dec 描述 */ public class CustomRealm extends AuthorizingRealm { @Autowired private LoginService loginService; /** * @MethodName doGetAuthorizationInfo * @Description 权限配置类 * @Param [principalCollection] * @Return AuthorizationInfo * @Author WangShiLin */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取登录用户名 String name = (String) principalCollection.getPrimaryPrincipal(); //查询用户名称 User user = loginService.getUserByName(name); //添加角色和权限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for (Role role : user.getRoles()) { //添加角色 simpleAuthorizationInfo.addRole(role.getRoleName()); //添加权限 for (Permissions permissions : role.getPermissions()) { simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName()); } } return simpleAuthorizationInfo; } /** * @MethodName doGetAuthenticationInfo * @Description 认证配置类 * @Param [authenticationToken] * @Return AuthenticationInfo * @Author WangShiLin */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { if (StringUtils.isEmpty(authenticationToken.getPrincipal())) { return null; } //获取用户信息 String name = authenticationToken.getPrincipal().toString(); User user = loginService.getUserByName(name); if (user == null) { //这里返回后会报出对应异常 return null; } else { // 如果成功,向shiro存入安全数据 //这里验证authenticationToken和simpleAuthenticationInfo的信息 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName()); return simpleAuthenticationInfo; } } }
六、编写ShiroConfig
把CustomRealm和SecurityManager等注入到spring容器中:
import com.zwh.shiro.CustomRealm; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * @author zhouwenhao * @date 2021/12/1 * @dec 描述 */ @Configuration public class ShiroConfig { @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; } //将自己的验证方式加入容器 @Bean public CustomRealm myShiroRealm() { CustomRealm customRealm = new CustomRealm(); return customRealm; } //权限管理,配置主要是Realm的管理认证 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } //Filter工厂,设置对应的过滤条件和跳转条件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> map = new HashMap<>(); //登出 map.put("/logout", "logout"); //对所有用户认证 map.put("/**", "authc"); //登录 shiroFilterFactoryBean.setLoginUrl("/login"); //首页 shiroFilterFactoryBean.setSuccessUrl("/index"); //错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
anon: 无需认证就可以访问,authc:必须认证了才能访问,user: 必须拥有 记住我 功能才让使用,perms: 拥有对某个资源的权限才能访问,role: 拥有某个角色权限才能访问
七、编写LoginController
@RestController @Slf4j public class LoginController { @GetMapping("/login") public String login(User user) { if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) { return "请输入用户名和密码!"; } //用户认证信息 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken( user.getUserName(), user.getPassword() ); try { //进行验证,这里可以捕获异常,然后返回对应信息 subject.login(usernamePasswordToken); // subject.checkRole("admin"); // subject.checkPermissions("query", "add"); } catch (UnknownAccountException e) { log.error("用户名不存在!", e); return "用户名不存在!"; } catch (AuthenticationException e) { log.error("账号或密码错误!", e); return "账号或密码错误!"; } catch (AuthorizationException e) { log.error("没有权限!", e); return "没有权限"; } return "login success"; } @RequiresRoles("admin") @GetMapping("/admin") public String admin() { return "admin success!"; } @RequiresPermissions("query") @GetMapping("/index") public String index() { return "index success!"; } @RequiresPermissions("add") @GetMapping("/add") public String add() { return "add success!"; } }
八、编写异常捕获类
注解验证角色和权限的话无法捕捉异常,从而无法正确的返回给前端错误信息,所以我加了一个类用于拦截异常,具体代码如下
import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authz.AuthorizationException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * @author zhouwenhao * @date 2021/12/1 * @dec 描述 */ @ControllerAdvice @Slf4j public class MyExceptionHandler { @ExceptionHandler @ResponseBody public String ErrorHandler(AuthorizationException e) { log.error("没有通过权限验证!", e); return "没有通过权限验证!"; } }
最终的目录结构
九、测试
1、测试zwh账号
浏览器访问:http://localhost:8086/login?userName=zwh&password=123456,结果如下:
然后输入index地址:http://localhost:8086/index
再访问add:http://localhost:8086/add
2、测试zhangsan账号
换zhangsan账号登录
再访问index:http://localhost:8086/index
再访问add:http://localhost:8086/add
十、常见的shiro异常
1. AuthenticationException 认证异常
Shiro在登录认证过程中,认证失败需要抛出的异常。 AuthenticationException包含以下子类:
1.1. CredentitalsException 凭证异常
IncorrectCredentialsException 不正确的凭证
ExpiredCredentialsException 凭证过期
1.2. AccountException 账号异常
ConcurrentAccessException: 并发访问异常(多个用户同时登录时抛出)
UnknownAccountException: 未知的账号
ExcessiveAttemptsException: 认证次数超过限制
DisabledAccountException: 禁用的账号
LockedAccountException: 账号被锁定
UnsupportedTokenException: 使用了不支持的Token
2. AuthorizationException: 授权异常
Shiro在登录认证过程中,授权失败需要抛出的异常。 AuthorizationException包含以下子类:
2.1. UnauthorizedException:
抛出以指示请求的操作或对请求的资源的访问是不允许的。
2.2. UnanthenticatedException:
当尚未完成成功认证时,尝试执行授权操作时引发异常。