shiro整合spring mvc,配置比较麻烦,先搭建spring mvc环境,然后再整合,本文主要是介绍shiro至于使用mybatis访问数据库,请参考之前文章
创建maven项目并修改pom.xml文件导入jar包
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--jsp-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
<!--jstl标签库-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--spring mvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--shiro整合spring以及shiro-core、shiro-web-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<!--java工具类封装了各种方法简化操作(主要用来访问数据库)-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.7</version>
</dependency>
<!--shiro日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.26</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
修改web.xml文件
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 配置Spring的监听器,tomcat启动时创建spring容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置加载类路径的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:ApplicationContext.xml</param-value>
</context-param>
<!-- SpringMVC的核心控制器 -->
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置Servlet的初始化参数,读取springmvc的配置文件,创建spring容器 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<!-- 配置servlet启动时加载对象 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置过滤器,解决中文乱码的问题 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 指定字符集 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--以上是spirng mvc环境搭建,以下是shiro环境搭建-->
<!--shiro过滤器-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
在resources目录下创建ApplicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解扫描,处理service和dao 而不处理controller-->
<context:component-scan base-package="zone.lxy">
<!--不处理@controller注解的类-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--以下开始为spring整合shiro-->
<!-- shiro过滤器bean,id要和web.xml中filter-name一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<!--配置要拦截的url-->
<property name="filterChainDefinitionMap">
<map>
<!--配置静态资源可以匿名访问-->
<entry key="/css/**" value="anon"/>
<entry key="/js/**" value="anon"/>
<entry key="/images/**" value="anon"/>
<entry key="/fonts/**" value="anon"/>
<!--登录页面不过滤-->
<entry key="/login" value="anon"/>
<!--退出页面-->
<entry key="/logout" value="logout"/>
<!--剩下的所有资源都被拦截-->
<entry key="/**" value="authc"/>
</map>
</property>
</bean>
<!--配置web安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--注入自定义的realm-->
<property name="realm" ref="myRealm"/>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--自定义realm-->
<bean id="myRealm" class="zone.lxy.realm.CustomerRealm">
<!--注入密码匹配器-->
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!--配置密码匹配器-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="1024"/>
</bean>
</beans>
shiro在运行是会创建一些默认的过滤器用于拦截url,常用过滤器如下:
anon
:可以匿名访问authc
:登录后才能访问logout
:实现退出功能roles
:需要指定角色才能访问perms
:需要指定权限才能访问user
:需要已登录或“记住我”的用户才能访问
在resources目录下创建SpringMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="zone.lxy.controller">
<!--只扫描controller注解-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 配置jsp视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 设置静态资源不过滤-->
<mvc:default-servlet-handler/>
<!--配置spring开启注解mvc的支持-->
<mvc:annotation-driven/>
<!--开启shiro注解,只能放在spring mvc配置文件中,放在spring配置文件不会生效 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
在resources目录下创建db.setting配置数据库信息(临时)
以后应该使用的都是mybatis,暂时使用简单的工具类操作数据库,减少学习成本
## db.setting文件
url = jdbc:mysql://localhost:3306/shiro?useSSL=false
user = root
pass = 123456
## 可选配置
# 是否在日志中显示执行的SQL
showSql = true
# 是否格式化显示的SQL
formatSql = false
# 是否显示SQL参数
showParams = true
# 打印SQL的日志等级,默认debug,可以是info、warn、error
sqlLevel = debug
在resources目录下创建log4j.properties日志配置文件
测试阶段所有信息均打印在控制台
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%t] %-5p %30.30c %x - %m%n
编写控制器类
@Controller
public class UserController {
private final static Log log= LogFactory.get();
// 跳转到登录页面
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String login(){
return "login";
}
// 处理登录请求
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(String username, String password, HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=UTF-8");
// 获取主体对象
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
// 登录验证
subject.login(token);
} catch (UnknownAccountException e) {
// 给页面返回提示信息
response.getWriter().write("用户名不存在");
log.info("用户名不存在");
return null;
}catch (IncorrectCredentialsException e) {
// 给页面返回提示信息
response.getWriter().write("密码错误");
log.info("密码错误");
return null;
}
// 登录成功则重定向到首页
return "redirect:/";
}
// 进入首页
@RequestMapping("/")
@RequiresRoles("admin") // 只有管理员才能进入首页,其他身份会报错
public String index(Model model){
log.info("进入首页");
// 测试jstl标签
ArrayList<String> list = ListUtil.toList("张三", "李四", "王五", "赵六");
model.addAttribute("list", list);
return "index";
}
}
整合spring mvc中可使用注解来控制权限,常用注解:
@RequiresRoles
:只有对应角色才能访问,其他角色访问就把报错@RequiresPermissions
:只有拥有该权限的用户才能访问,否则报错
自定义Realm类
public class CustomerRealm extends AuthorizingRealm {
// 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取用户名
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
// 根据用户名获取角色信息(t_user、t_role、t_user_role三表联合查询)
List<Entity> roles=null;
try {
roles = Db.use().query("select t_role.* from t_user " +
"join t_user_role on t_user.id=t_user_role.userid " +
"join t_role on t_user_role.roleid=t_role.id " +
"where t_user.username=?", primaryPrincipal);
} catch (SQLException e) {
e.printStackTrace();
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Entity role : roles) {
simpleAuthorizationInfo.addRole(role.getStr("name")); // 把角色信息加入
// 根据角色id 获取对应的权限信息
List<Entity> perms = null;
try {
perms = Db.use().query("select t_perms.* from t_role join t_role_perms on t_role.id=t_role_perms.roleid join t_perms on t_role_perms.permsid=t_perms.id where t_role.id=?", role.getInt("id"));
} catch (SQLException e) {
e.printStackTrace();
}
for (Entity perm : perms) {
simpleAuthorizationInfo.addStringPermission(perm.getStr("name")); // 把该角色对应的角色信息加入
}
}
// 返回
return simpleAuthorizationInfo;
}
// 登录认证时会执行该方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户名
String principal = (String) authenticationToken.getPrincipal();
// 根据用户名查询用户信息
Entity entity = null;
try {
// Entity相当于一个map对象,该处使用了hutool工具类操作数据库
entity = Db.use().get(Entity.create("t_user").set("username", principal));
} catch (SQLException e) {
e.printStackTrace();
}
// 如果从数据库中找到了
if (entity!=null) {
return new SimpleAuthenticationInfo(
entity.getStr("username"), // 用户名
entity.getStr("password"), // 正确的密码
ByteSource.Util.bytes(entity.getStr("salt")), // 用户注册时生成的随机盐
this.getName()); // 当前类名
}
return null;
}
}
编写index.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h1>登录成功</h1>
你好<shiro:principal/>,身份:
<shiro:hasRole name="admin">管理员 </shiro:hasRole>
<shiro:hasRole name="teacher">教师 </shiro:hasRole>
<shiro:hasRole name="student">学生 </shiro:hasRole>
<br>
<a href="logout">退出</a><br>
</body>
</html>
关于shiro的jsp新标签(常用):
<shiro:guest>
:用户未登录时标签中的内容就会显示<shiro:principal/>
:输出当前用户的用户名<shiro:hasRole name="admin">
:如果该用户的角色是admin,则标签中的内容会显示<shiro:hasPermission name="user:create">
:如果该用户具有该权限就显示标签中的内容
更多标签信息点击这里
编写login.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form action="login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
性能优化——配置Eh缓存管理器
登录成功进入首页后,每次刷新页面都会从数据库获取角色(权限)信息,会对数据库造成很大压力,而且角色信息一般也很少变动,故应该使用缓存。这里也可以使用redis做缓存相关信息可以百度
在之前基础上导入新jar包
<!--Eh缓存管理器-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
修改ApplicationContext.xml文件
<!--自定义realm-->
<bean id="myRealm" class="zone.lxy.realm.CustomerRealm">
<!--注入密码匹配器-->
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<!--设置缓存管理器-->
<property name="cacheManager" ref="ehCacheManager"/>
<!--开启全局缓存-->
<property name="cachingEnabled" value="true"/>
<!--开启认证缓存-->
<property name="authenticationCachingEnabled" value="true"/>
<!--开启授权缓存-->
<property name="authorizationCachingEnabled" value="true"/>
</bean>
<!--把Eh缓存管理器加入spirng容器-->
<bean id="ehCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"/>