目录
- Shiro
- Shiro核心组件
- SpringBoot整合Shiro
- 1.创建新SpringBoot项目和导包
- 2.自定义Shiro过滤器Realm
- 3.配置类ShiroConfig
- 编写认证和授权规则案例:
- 案例思路
- 改造ShiroConfig
- AccountController
- 配置视图解析器
- templates下新建3个页面
- 启动测试
- 登录认证
- 设置自定义登录页面
- 写登录接口
- 测试效果
- 授权
- 设置未授权异常响应
- 登录后显示欢迎信息
- 退出登录
- 动态菜单
- tips:
Shiro
什么是 Shiro
官网:http://shiro.apache.org/
是一款主流的 Java 安全框架,不依赖任何容器,可以运行在 Java SE 和 Java EE 项目中,它的主要作用是对访问系统的用户进行身份认证、授权、会话管理、加密等操作。
Shiro 就是用来解决安全管理的系统化框架。
Shiro核心组件
1.UsernamePasswordToken:
(封装了用户名和密码的token)
Shiro 用来封装用户登录信息,使用用户的登录信息来创建令牌 Token。
(把用户名和密码传入token,交给shiro,让shiro去登录认证和权限校验)
2、Suject:Shiro 的一个抽象概念,包含了用户信息。
(相当于包含了用户信息的一个载体,系统可通过Suject取用户信息)
3、SecurityManager:Shiro 的核心部分,负责安全认证和授权。
(上面只是封装,具体操作由它完成)
4、AuthenticationInfo:用户的角色信息集合,认证时使用。
(用户、角色、权限,会给角色赋予权限,给用户赋予角色)
5、AuthorizationInfo:角色的权限信息集合,授权时使用。
6、Realm:开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中。
7、DefaultWebSecurityManager:安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。
8、ShiroFilterFactoryBean:过滤器工厂,Shiro 的基本运行机制是开发者定制规则(怎么拦截请求,怎么认证,怎么授权,告诉shiro),Shiro 去执行,具体的执行操作就是由 ShiroFilterFactoryBean 创建的一个个 Filter 对象来完成。
Shiro 的运行机制如下图所示。
SpringBoot整合Shiro
1.创建新SpringBoot项目和导包
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.5.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.3.1.tmp</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency>
</dependencies>
2.自定义Shiro过滤器Realm
public class AccoutRealm extends AuthorizingRealm {@Autowiredprivate AccountService accountService;/*** 登录认证:用户名和密码校验* @param authenticationToken* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;Account account = accountService.findByUsername(token.getUsername());if(account != null){return new SimpleAuthenticationInfo(account,account.getPassword(),getName());}return null;}/*** 授权* @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}
}
3.配置类ShiroConfig
@Configuration
public class ShiroConfig {@Bean // 创建对象并放入容器public AccoutRealm accoutRealm(){return new AccoutRealm();}@Bean // @Qualifier("accoutRealm"):根据方法名从ioc容器中取beanpublic DefaultWebSecurityManager securityManager(@Qualifier("accoutRealm") AccoutRealm accoutRealm){DefaultWebSecurityManager manager = new DefaultWebSecurityManager();manager.setRealm(accoutRealm);return manager;}@Bean // @Qualifier("securityManager"):根据方法名从ioc容器中取beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();factoryBean.setSecurityManager(securityManager);return factoryBean;}
}
编写认证和授权规则案例:
认证过滤器选项:
anon:无需认证。
authc:必须认证。
authcBasic:需要通过 HTTPBasic 认证。
user:不一定通过认证,只要曾经被 Shiro 记录即可,比如:记住我。
授权过滤器选项:
perms:必须拥有某个权限才能访问。
role:必须拥有某个角色才能访问。
port:请求的端口必须是指定值才可以。
rest:请求必须基于 RESTful,POST、PUT、GET、DELETE。
ssl:必须是安全的 URL 请求,协议 HTTPS。
案例思路
创建 3 个页面:main.html、manage.html、administrator.html,访问权限如下:
1、必须登录才能访问 main.html
2、当前用户必须拥有 manage 授权才能访问 manage.html
3、当前用户必须拥有 administrator 角色才能访问 administrator.html
改造ShiroConfig
@Bean // @Qualifier("securityManager"):根据方法名从ioc容器中取beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();factoryBean.setSecurityManager(securityManager);//权限设置Map<String,String> map = new Hashtable<>();map.put("/main","authc"); //请求main.html时必须为登录状态map.put("/manage","perms[manage]"); //请求manage.html时必须有manage权限map.put("/administrator","roles[administrator]"); //请求administrator.html时必须有administrator角色factoryBean.setFilterChainDefinitionMap(map); //根据map创建对应的过滤器,去拦截和校验return factoryBean;}
AccountController
@Controller
public class AccountController {@GetMapping("/{url}")public String redirect(@PathVariable("url") String url){return url;}
}
配置视图解析器
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: rooturl: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTCthymeleaf: #配置视图解析器prefix: classpath:/templates/suffix: .html
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
templates下新建3个页面
main.html、manage.html、administrator.html
启动测试
浏览器访问http://localhost:8080/main会报500并跳到http://localhost:8080/login.jsp
tips:页面加入以下代码可消除不影响运行的报错
<link rel="shortcut icon" href="#"/>
登录认证
设置自定义登录页面
ShiroConfig
@Bean // @Qualifier("securityManager"):根据方法名从ioc容器中取beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();factoryBean.setSecurityManager(securityManager);//权限设置Map<String,String> map = new Hashtable<>();map.put("/main","authc"); //请求main.html时必须为登录状态map.put("/manage","perms[manage]"); //请求manage.html时必须有manage权限map.put("/administrator","roles[administrator]"); //请求administrator.html时必须有administrator角色factoryBean.setFilterChainDefinitionMap(map); //根据map创建对应的过滤器,去拦截和校验//设置去登录页面的后端get请求 不走默认的login.jsp,发后端请求经视图解析器走到login.htmlfactoryBean.setLoginUrl("/login");
// //设置未授权页面
// factoryBean.setUnauthorizedUrl("/unauth");return factoryBean;}
login.html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title><link rel="shortcut icon" href="#"/>
</head>
<body><form action="/login" method="post"><table><span th:text="${msg}" style="color: red"></span><tr><td>用户名:</td><td><input type="text" name="username"/></td></tr><tr><td>密码:</td><td><input type="password" name="password"/></td></tr><tr><td><input type="submit" value="登录"/></td></tr></table></form>
</body>
</html>
写登录接口
AccountController
@PostMapping("/login")public String login(String username, String password, Model model){Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken(username,password);try {subject.login(token); //会走去realm里认证
// Account account = (Account) subject.getPrincipal();
// subject.getSession().setAttribute("account",account);return "index";} catch (UnknownAccountException e) {e.printStackTrace();model.addAttribute("msg","用户名错误!");return "login";} catch (IncorrectCredentialsException e){model.addAttribute("msg","密码错误!");e.printStackTrace();return "login";}}
测试效果
1.浏览器输入http://localhost:8080/index进入index页面
2.index页面点击main进入登录页面
3.登录进入首页,然后可以访问main了
授权
从数据库取出角色和权限信息设置到框架中
注意:案例里用户的角色和权限都是单个的
AccoutRealm
/*** 授权* @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//获取当前登录的用户信息Subject subject = SecurityUtils.getSubject();Account account = (Account) subject.getPrincipal();//设置角色Set<String> roles = new HashSet<>();roles.add(account.getRole());SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);//设置权限info.addStringPermission(account.getPerms());return info;}
重新启动测试:分别用不同用户访问不同页面
设置未授权异常响应
ShiroConfig添加代码
@Bean // @Qualifier("securityManager"):根据方法名从ioc容器中取beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();factoryBean.setSecurityManager(securityManager);//权限设置Map<String,String> map = new Hashtable<>();map.put("/main","authc"); //请求main.html时必须为登录状态map.put("/manage","perms[manage]"); //请求manage.html时必须有manage权限map.put("/administrator","roles[administrator]"); //请求administrator.html时必须有administrator角色factoryBean.setFilterChainDefinitionMap(map); //根据map创建对应的过滤器,去拦截和校验//设置去登录页面的后端get请求 不走默认的login.jsp,发后端请求经视图解析器走到login.htmlfactoryBean.setLoginUrl("/login");//设置处理未授权异常的get接口factoryBean.setUnauthorizedUrl("/unauth");return factoryBean;}
AccountController添加代码
@GetMapping("/unauth")@ResponseBodypublic String unauth(){return "未授权,无法访问!";}
登录后显示欢迎信息
后台登录后把用户信息存进session
index页面取出用户名显示
退出登录
index.html添加代码
后端AccountController添加代码
@GetMapping("/logout")public String logout(){Subject subject = SecurityUtils.getSubject();subject.logout();return "login";}
动态菜单
1、pom.xml 引入依赖
<dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version>
</dependency>
2、配置类 ShiroConfig 添加 ShiroDialect 方言
@Bean
public ShiroDialect shiroDialect(){return new ShiroDialect();
}
3、index.html
tips:
idea不识别已导入依赖?
1.maven clean install,2.pom 剪切 粘贴,3.重启idea
Mapper注入报红?
因为是用的动态代理对象,运行后ioc容器里面才会有,不影响运行,也可以用@Repository解决