步骤 2 : 数据 步骤 3 : 先运行,看到效果,再学习 步骤 4 : 模仿和排错 步骤 5 : 运用前面的文件 步骤 6 : 创建项目 步骤 7 : 输入项目参数 步骤 8 : pom.xml 步骤 9 : application.properties 步骤 10 : Application.java 步骤 11 : src 下的 java 文件们 步骤 12 : jsp们 步骤 13 : ShiroConfiguration 步骤 14 : SpringContextUtils.java 步骤 15 : URLPathMatchingFilter.java 步骤 16 : 重启后测试
springboot 作为一个框架集成,把ssm的配置都简化了。
如果没有springboot基础,请事先学习 springboot 入门
通过前面的学习,数据估计又被修改了,执行下面这部分sql语句,还原数据,以便观察演示
DROP DATABASE IF EXISTS shiro;
CREATE DATABASE shiro DEFAULT CHARACTER SET utf8;
USE shiro;
drop table if exists user;
drop table if exists role;
drop table if exists permission;
drop table if exists user_role;
drop table if exists role_permission;
create table user (
id bigint auto_increment,
name varchar(100),
password varchar(100),
salt varchar(100),
constraint pk_users primary key(id)
) charset=utf8 ENGINE=InnoDB;
create table role (
id bigint auto_increment,
name varchar(100),
desc_ varchar(100),
constraint pk_roles primary key(id)
) charset=utf8 ENGINE=InnoDB;
create table permission (
id bigint auto_increment,
name varchar(100),
desc_ varchar(100),
url varchar(100),
constraint pk_permissions primary key(id)
) charset=utf8 ENGINE=InnoDB;
create table user_role (
id bigint auto_increment,
uid bigint,
rid bigint,
constraint pk_users_roles primary key(id)
) charset=utf8 ENGINE=InnoDB;
create table role_permission (
id bigint auto_increment,
rid bigint,
pid bigint,
constraint pk_roles_permissions primary key(id)
) charset=utf8 ENGINE=InnoDB;
INSERT INTO `permission` VALUES (1,'addProduct','增加产品','/addProduct');
INSERT INTO `permission` VALUES (2,'deleteProduct','删除产品','/deleteProduct');
INSERT INTO `permission` VALUES (3,'editeProduct','编辑产品','/editeProduct');
INSERT INTO `permission` VALUES (4,'updateProduct','修改产品','/updateProduct');
INSERT INTO `permission` VALUES (5,'listProduct','查看产品','/listProduct');
INSERT INTO `permission` VALUES (6,'addOrder','增加订单','/addOrder');
INSERT INTO `permission` VALUES (7,'deleteOrder','删除订单','/deleteOrder');
INSERT INTO `permission` VALUES (8,'editeOrder','编辑订单','/editeOrder');
INSERT INTO `permission` VALUES (9,'updateOrder','修改订单','/updateOrder');
INSERT INTO `permission` VALUES (10,'listOrder','查看订单','/listOrder');
INSERT INTO `role` VALUES (1,'admin','超级管理员');
INSERT INTO `role` VALUES (2,'productManager','产品管理员');
INSERT INTO `role` VALUES (3,'orderManager','订单管理员');
INSERT INTO `role_permission` VALUES (1,1,1);
INSERT INTO `role_permission` VALUES (2,1,2);
INSERT INTO `role_permission` VALUES (3,1,3);
INSERT INTO `role_permission` VALUES (4,1,4);
INSERT INTO `role_permission` VALUES (5,1,5);
INSERT INTO `role_permission` VALUES (6,1,6);
INSERT INTO `role_permission` VALUES (7,1,7);
INSERT INTO `role_permission` VALUES (8,1,8);
INSERT INTO `role_permission` VALUES (9,1,9);
INSERT INTO `role_permission` VALUES (10,1,10);
INSERT INTO `role_permission` VALUES (11,2,1);
INSERT INTO `role_permission` VALUES (12,2,2);
INSERT INTO `role_permission` VALUES (13,2,3);
INSERT INTO `role_permission` VALUES (14,2,4);
INSERT INTO `role_permission` VALUES (15,2,5);
INSERT INTO `role_permission` VALUES (50,3,10);
INSERT INTO `role_permission` VALUES (51,3,9);
INSERT INTO `role_permission` VALUES (52,3,8);
INSERT INTO `role_permission` VALUES (53,3,7);
INSERT INTO `role_permission` VALUES (54,3,6);
INSERT INTO `role_permission` VALUES (55,3,1);
INSERT INTO `role_permission` VALUES (56,5,11);
INSERT INTO `user` VALUES (1,'zhang3','a7d59dfc5332749cb801f86a24f5f590','e5ykFiNwShfCXvBRPr3wXg==');
INSERT INTO `user` VALUES (2,'li4','43e28304197b9216e45ab1ce8dac831b','jPz19y7arvYIGhuUjsb6sQ==');
INSERT INTO `user_role` VALUES (43,2,2);
INSERT INTO `user_role` VALUES (45,1,1);
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
启动Application类,然后访问业务地址进行测试: http://127.0.0.1:8080/shiro/index 权限配置地址,进行权限设置: http://127.0.0.1:8080/shiro/config/listUser
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。 采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。 推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。 这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来 这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
springboot,说到底,也就是配置方式变化了,该要的类,都要有,该要的jsp,也都要有。 所以本知识点会运用基于URL配置权限 中的源代码进行改进,一些要调整的地方会重点说一说
首先新建个 maven 项目
菜单 -> File -> New -> Other -> Maven -> Maven -> Maven Project -> New Maven Project 勾上这个 Create a simple project (skip archetype selection), 看吧,Springboot就是个简单的maven 项目 注: 这里用的是eclipse的,idea的做法请看 springboot-idea入门
修改pom.xml, 修改之后,记得刷新一下maven. 刷新办法: 项目右键->Maven->Update Project。
这个pom.xml里就提供了本项目需要的各种jar包 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.how2java</groupId>
<artifactId>shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro</name>
<description>shiro</description>
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- servlet依赖. -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- tomcat的支持.-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>
<!-- mybatis 逆向工程 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- shiro-web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在resources目录下新建文件application.properties
这里就提供了数据库链接参数, jsp重定位位置, mybatis配置以及项目访问的时候需要加上一个/shiro, 这样才和前面的教程保持一致。 spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
mybatis.mapper-locations=classpath:com/how2java/mapper/*.xml
mybatis.type-aliases-package=com.how2java.pojo
server.context-path=/shiro
spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=admin spring.datasource.driver-class-name=com.mysql.jdbc.Driver mybatis.mapper-locations=classpath:com/how2java/mapper/*.xml mybatis.type-aliases-package=com.how2java.pojo server.context-path=/shiro
这个是启动类。 需要注意几点:
1. 包一定要放在com.how2java 下,因为其他包,比如mapper,service什么的都是基于这个包名,SpringBoot会根据这个启动类所处与的包名去搜索其他类。 如果放其他包名下面,会折腾死个人的。 2. 还要通过这种方式指定mapper的位置,不然就找不到。 妈蛋~ 虽然application.properties 里指定了扫描mapper的位置,但是他是不听啊~ 这个也折腾了我很久~ @MapperScan(basePackages = "com.how2java.mapper") package com.how2java;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.how2java.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
package com.how2java; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan(basePackages = "com.how2java.mapper") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
然后就是上一个知识点基于URL配置权限 下载的项目里的src下的源代码。 仅仅是java源代码, 什么.xml, .properties等文件就不要了。
下载后,解压,然后把里面的src下的java源代码都复制过来。 复制过来后,记得刷新一下项目才看得到。 这些文件用用途在前面的知识点已经讲过了,这里就不再赘述,否则本页起码要有40个步骤,太过眼花缭乱。 所以,没有学习前面知识点的同学,还是回过头去学习一边,里面每个类的讲解也更加清晰详细
然后就是jsp也复制过来。需要注意几点
1. WEB-INF目录要自己建 2. web.xml 文件不用复制 3. lib 目录不用复制 4. static 目录要复制过来
接着就是Shiro配置了。
按照Springboot的尿性,配置文件不要了, 用类来做。。。其实不是一样的么,还没有配置文件可读性强呢。。。 哎,咱们也这样做吧,以后看别人的springboot项目看得懂。 ShiroConfiguration 这个类就是shiro配置类,类名怎么命名都可以,就是要用@Configuration 注解表示它是一个配置类 @Bean public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } 这种写法就和 applicationContext-shiro.xml 中的 <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> 是同一个作用,只要用@Bean 注解了,就表示是被spring管理起来的对象了。 这个类里面就声明了SecurityManager,DatabaseRealm, HashedCredentialsMatcher,ShiroFilterFactoryBean 等等东西,只是写法变化了,其实和配置文件里是一样的。 需要注意一点,URLPathMatchingFilter 并没有用@Bean管理起来。 原因是Shiro的bug, 这个也是过滤器,ShiroFilterFactoryBean 也是过滤器,当他们都出现的时候,默认的什么anno,authc,logout过滤器就失效了。所以不能把他声明为@Bean。 public URLPathMatchingFilter getURLPathMatchingFilter() { return new URLPathMatchingFilter(); } package com.how2java.shiro;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.how2java.filter.URLPathMatchingFilter;
import com.how2java.realm.DatabaseRealm;
@Configuration
public class ShiroConfiguration {
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
Filter Chain定义说明
1、一个URL可以配置多个Filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
//自定义拦截器
Map<String, Filter> customisedFilter = new HashMap<>();
customisedFilter.put("url", getURLPathMatchingFilter());
//配置映射关系
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/index", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/config/**", "anon");
filterChainDefinitionMap.put("/doLogout", "logout");;
filterChainDefinitionMap.put("/**", "url");
shiroFilterFactoryBean.setFilters(customisedFilter);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
public URLPathMatchingFilter getURLPathMatchingFilter() {
return new URLPathMatchingFilter();
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(getDatabaseRealm());
return securityManager;
}
@Bean
public DatabaseRealm getDatabaseRealm(){
DatabaseRealm myShiroRealm = new DatabaseRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
继续上面的知识点,因为URLPathMatchingFilter 没有被声明为@Bean, 那么换句话说 URLPathMatchingFilter 就没有被Spring管理起来,那么也就无法在里面注入 PermissionService类了。
但是在业务上URLPathMatchingFilter 里面又必须使用PermissionService类,怎么办呢? 就借助SpringContextUtils 这个工具类,来获取PermissionService的实例。 这里提供工具类,下个步骤讲解如何使用这个工具类。 package com.how2java.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext context)
throws BeansException {
SpringContextUtils.context = context;
}
public static ApplicationContext getContext(){
return context;
}
}
package com.how2java.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringContextUtils implements ApplicationContextAware { private static ApplicationContext context; public void setApplicationContext(ApplicationContext context) throws BeansException { SpringContextUtils.context = context; } public static ApplicationContext getContext(){ return context; } }
因为前面两个步骤的原因,既不能把 URLPathMatchingFilter.java 作为@Bean管理起来,又需要在里面使用 PermissionService,所以就用上一步的工具类 SpringContextUtils.java,通过如下方式获取 PermissionService了:
permissionService = SpringContextUtils.getContext().getBean(PermissionService.class); package com.how2java.filter;
import java.util.Arrays;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.how2java.service.PermissionService;
import com.how2java.service.impl.PermissionServiceImpl;
import com.how2java.util.SpringContextUtils;
public class URLPathMatchingFilter extends PathMatchingFilter {
@Autowired
PermissionService permissionService;
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
if(null==permissionService)
permissionService = SpringContextUtils.getContext().getBean(PermissionService.class);
String requestURI = getPathWithinApplication(request);
System.out.println("requestURI:" + requestURI);
Subject subject = SecurityUtils.getSubject();
// 如果没有登录,就跳转到登录页面
if (!subject.isAuthenticated()) {
WebUtils.issueRedirect(request, response, "/login");
return false;
}
// 看看这个路径权限里有没有维护,如果没有维护,一律放行(也可以改为一律不放行)
System.out.println("permissionService:"+permissionService);
boolean needInterceptor = permissionService.needInterceptor(requestURI);
if (!needInterceptor) {
return true;
} else {
boolean hasPermission = false;
String userName = subject.getPrincipal().toString();
Set<String> permissionUrls = permissionService.listPermissionURLs(userName);
for (String url : permissionUrls) {
// 这就表示当前用户有这个权限
if (url.equals(requestURI)) {
hasPermission = true;
break;
}
}
if (hasPermission)
return true;
else {
UnauthorizedException ex = new UnauthorizedException("当前用户没有访问路径 " + requestURI + " 的权限");
subject.getSession().setAttribute("ex", ex);
WebUtils.issueRedirect(request, response, "/unauthorized");
return false;
}
}
}
}
重新启动Application类,然后访问业务地址进行测试:
http://127.0.0.1:8080/shiro/index 权限配置地址,进行权限设置: http://127.0.0.1:8080/shiro/config/listUser
HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。
问答区域
2021-12-26
学完了,都能走得通,但站长怎么没把shiro的会话管理写出来?难道这块不重要吗?可以写的全面一点吗?
回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
2021-09-24
按照Springboot的尿性,配置文件不要了, 用类来做。。。其实不是一样的么,还没有配置文件可读性强呢。。。 哎,咱们也这样做吧,以后看别人的springboot项目看得懂。??
2021-04-28
个人认为这个逆向工程有点臭了
2020-11-22
正确的用户名和密码无法登陆
2020-09-10
关于shiro设置anno还是被拦截的解决方案
提问太多,页面渲染太慢,为了加快渲染速度,本页最多只显示几条提问。还有 14 条以前的提问,请 点击查看
提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
|