一.shiro的简介
Apache Shiro(发音为“shee-roh”,日语“堡垒(Castle)”的意思)是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用、移动应用到大型网络及企业应用。
Shiro为解决下列问题(我喜欢称它们为应用安全的四要素)提供了保护应用的API:
- 认证 - 用户身份识别,常被称为用户“登录”;
- 授权 - 访问控制;
- 密码加密 - 保护或隐藏数据防止被偷窥;
- 会话管理 - 每用户相关的时间敏感的状态。
从2003年至今,框架选择方面的情况已经改变了不少,但今天仍有令人信服的理由让你选择Shiro。其实理由相当多,Apache Shiro:
- 易于使用 - 易用性是这个项目的最终目标。应用安全有可能会非常让人糊涂,令人沮丧,并被认为是“必要之恶”【译注:比喻应用安全方面的编程。】。若是能让它简化到新手都能很快上手,那它将不再是一种痛苦了。
- 广泛性 - 没有其他安全框架可以达到Apache Shiro宣称的广度,它可以为你的安全需求提供“一站式”服务。
- 灵活性 - Apache Shiro可以工作在任何应用环境中。虽然它工作在Web、EJB和IoC环境中,但它并不依赖这些环境。Shiro既不强加任何规范,也无需过多依赖。
- Web能力 - Apache Shiro对Web应用的支持很神奇,允许你基于应用URL和Web协议(如REST)创建灵活的安全策略,同时还提供了一套控制页面输出的JSP标签库。
- 可插拔 - Shiro干净的API和设计模式使它可以方便地与许多的其他框架和应用进行集成。你将看到Shiro可以与诸如Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin这类第三方框架无缝集成。
核心概念:Subject,SecurityManager和Realms
Subject
Subject一词是一个安全术语,其基本意思是“当前的操作用户”。称之为“用户”并不准确,因为“用户”一词通常跟人相关。在安全领域,术语“Subject”可以是人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。在代码的任何地方,你都能轻易的获得Shiro Subject,
SecurityManager
Subject的“幕后”推手是SecurityManager。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。它是Shiro框架的核心,充当“保护伞”,引用了多个内部嵌套安全组件,它们形成了对象图。但是,一旦SecurityManager及其内部对象图配置好,它就会退居幕后,应用开发人员几乎把他们的所有时间都花在Subject API调用上。
那么,如何设置SecurityManager呢?嗯,这要看应用的环境。例如,Web应用通常会在Web.xml中指定一个Shiro Servlet Filter,这会创建SecurityManager实例,如果你运行的是一个独立应用,你需要用其他配置方式,但有很多配置选项。
一个应用几乎总是只有一个SecurityManager实例。它实际是应用的Singleton(尽管不必是一个静态Singleton)。跟Shiro里的几乎所有组件一样,SecurityManager的缺省实现是POJO,而且可用POJO兼容的任何配置机制进行配置 - 普通的Java代码、Spring XML、YAML、.properties和.ini文件等。基本来讲,能够实例化类和调用JavaBean兼容方法的任何配置形式都可使用。
为此,Shiro借助基于文本的INI配置提供了一个缺省的“公共”解决方案。INI易于阅读、使用简单并且需要极少依赖。你还能看到,只要简单地理解对象导航,INI可被有效地用于配置像SecurityManager那样简单的对象图。注意,Shiro还支持Spring XML配置及其他方式,但这里只我们只讨论INI。
Realms
Shiro的第三个也是最后一个概念是Realm。Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当切实与像用户帐户这类安全相关数据进行交互,执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找很多内容。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现
shiro架构
除前面所讲Subject、SecurityManager 、Realm三个核心组件外,Shiro主要组件还包括:
- Authenticator :认证就是核实用户身份的过程。这个过程的常见例子是大家都熟悉的“用户/密码”组合。多数用户在登录软件系统时,通常提供自己的用户名(当事人)和支持他们的密码(证书)。如果存储在系统中的密码(或密码表示)与用户提供的匹配,他们就被认为通过认证。
- Authorizer :授权实质上就是访问控制 - 控制用户能够访问应用中的哪些内容,比如资源、Web页面等等。
- SessionManager :在安全框架领域,Apache Shiro提供了一些独特的东西:可在任何应用或架构层一致地使用Session API。即,Shiro为任何应用提供了一个会话编程范式 - 从小型后台独立应用到大型集群Web应用。这意味着,那些希望使用会话的应用开发者,不必被迫使用Servlet或EJB容器了。或者,如果正在使用这些容器,开发者现在也可以选择使用在任何层统一一致的会话API,取代Servlet或EJB机制。
- CacheManager :对Shiro的其他组件提供缓存支持。
- Cryptography 提供安全的支持
ini配置权限信息(参考http://shiro.apache.org/configuration.html)
# =======================
# Shiro INI configuration
# =======================
[main]
authc.loginUrl=/login.html
#认证(登录失败)不通过 跳转到loginUrl
roles.loginUrl=/login.html
#授权(没有某个权限) 不通过 跳转到unauthorizedUrl
roles.unauthorizedUrl=/un.html
perms.loginUrl=/login.html
#授权(没有某个权限) 不通过 跳转到unauthorizedUrl
perms.unauthorizedUrl=/un.html
[users]
# 设置用户信息
# 语法是 username = password, roleName1, roleName2, …, roleNameN
jiaozi = 123456,role1
[roles]
# 角色信息和角色拥有的权限
#语法是 rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN
#权限的语法 * 表示所有权限 一般语法是 权限类型.权限动作.权限的资源id 比如 user:delete:1 表示拥有删
#除1号用户的权限 user:delete:*表示删除所有用户权限
admin = *
role1 = user:query:*, user:delete:1
[urls]
# web中的url过滤 访问这个页面时 要求你登录的账号 必须拥有某些权限
/login.html = anon
/suc.jsp = perms[user:delete:2]
创建maven项目 导入架包
<!-- 导入shiro的架包 -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
测试验证权限过程
package cn.et;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class TestShiro {
public static void main(String[] args) {
//从ini中读取权限信息构建 SecurityUtils对象
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:my.ini");
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//获取当前的用户
Subject currentUser = SecurityUtils.getSubject();
//当前用户的会话
Session session = currentUser.getSession();
//判断是否登录 未登录 需要登录
/**
* 用户包括两部分
* principals and credentials
* principals(本人)表示用户的标识信息 比如用户名 用户地址等
* credentials(凭证)表示用户用于登录的凭证 比如密码 证书等
*/
if ( !currentUser.isAuthenticated() ) {
//用户输入的用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("jiaozi", "123456");
try {
currentUser.login( token );
System.out.println("登录成功");
//是否认证通过
System.out.println(currentUser.isAuthenticated());
//判断登录后用户是否有某个角色
if(currentUser.hasRole("role1")){
System.out.println("拥有role1角色");
}
//是否拥有权限
if(currentUser.isPermitted("user:delete:1")){
System.out.println("拥有与删除1的权限");
}
} catch ( UnknownAccountException uae ) {
System.out.println("账号错误");
} catch ( IncorrectCredentialsException ice ) {
System.out.println("密码不匹配");
} catch ( LockedAccountException lae ) {
System.out.println("账号被锁定");
} catch ( AuthenticationException ae ) {
System.out.println("未知错误");
}
}
}
}
二. shiro web
集成web集成 参考 http://shiro.apache.org/web.html#programmatic-support
在之前例子基础上进行拓展 添加war项目 添加shiro-web依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
web.xml添加shiro支持的过滤器和ini文件路径配置参数
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>/WEB-INF/my.ini</param-value>
</context-param>
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
添加几个html和jsp用于测试
login.html添加用于登录的表单,引入的jquery
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>哔哩哔哩弹幕视频网 - ( ゜- ゜)つロ 乾杯~ - bilibili</title>
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="this is my page">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="themes/icon.css">
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="jquery.easyui.min.js"></script>
<script type="text/javascript">
</script>
</head>
<body>
<div class="easyui-layout" style="width:100%;height:100%;">
<!-- 上北只能设置高度 一般不会设置宽度 -->
<div data-options="region:'north'" style="height:15%"></div>
<div data-options="region:'south',split:true" style="height:15%"></div>
<div data-options="region:'center'" >
<form id="ss" action="loginShiro" method="post" enctype="multipart/form-data">
<table cellpadding="5" align="center">
<tr>
<td><input class="easyui-textbox" type="text" name="userName" data-options="required:true,missingMessage:'请告诉我你的昵称吧'"></input></td>
</tr>
<tr>
<td><input class="easyui-textbox" type="text" name="password" data-options="required:true,missingMessage:'密码不能小于6个字符',validType:'length[6,16]'"></input></td>
</tr>
<tr>
<td><input class="text" type="submit" value="登录 "/></td>
</tr>
</table>
</form>
</div>
</div>
</body>
</html>
成功页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
成功登录
</body>
</html>
失败页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
你没有权限
</body>
</html>
servlet
package cn.et.less01.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
/**
* Servlet implementation class LoginController
*/
public class LoginController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public LoginController() {
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取页面传过来的用户名和密码
String username=request.getParameter("userName");
String password=request.getParameter("password");
//获取当前的用户
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
currentUser.login( token );
//是否认证通过
System.out.println(currentUser.isAuthenticated());
request.getRequestDispatcher("/suc.jsp").forward(request, response);
} catch ( UnknownAccountException uae ) {
System.out.println("账号错误");
request.getRequestDispatcher("/login.html").forward(request, response);
} catch ( IncorrectCredentialsException ice ) {
System.out.println("密码不匹配");
request.getRequestDispatcher("/login.html").forward(request, response);
} catch ( LockedAccountException lae ) {
System.out.println("账号被锁定");
} catch ( AuthenticationException ae ) {
System.out.println("未知错误");
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
三. shiro集成spring
添加maven的架包支持和shiro-spring
<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>cn.et</groupId>
<artifactId>Shiroless</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!-- 导入shiro的架包 -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<!--事务的基础 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<!-- 调用jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.12</version>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!-- 添加jacson的json解析库 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.5</version>
</dependency>
<!-- mybatis集成spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>javaee-api</artifactId>
<version>5.0-1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>1.2_04</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>1.2_04</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
在web.xml中配置过滤器
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- 解决乱码的配置 -->
<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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
======================================
spring的配置
======================================
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
=========================================
springmvc的配置
=========================================
-->
<!-- 配置springmvc支持 restful风格url
put+delete
form+hidden(_method) ?_method=put
-->
<filter>
<filter-name>myFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置springmvc的核心控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mymvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 启用druid的监控功能 -->
<servlet>
<servlet-name>statViewServlet</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<init-param>
<param-name>loginUsername</param-name>
<param-value>admin</param-value>
</init-param>
<init-param>
<param-name>loginPassword</param-name>
<param-value>zmw</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>statViewServlet</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
<!-- 配置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>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
在web-inf下创建mymvc.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
">
<!-- springmvc控制层处理 + 视图层 -->
<context:component-scan base-package="cn">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
<mvc:default-servlet-handler/>
<!-- 上传文件 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
<!-- 引用返回对象 响应json的消息转换器 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/x-www-form-urlencoded</value>
</list>
</property>
</bean>
<!-- 配置返回对应解析成json的消息转换器 -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/x-www-form-urlencoded</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</beans>
在resources中配置连接数据库的四要素jdbc.properties文件
url=jdbc:mysql://localhost:3306/auth
driverClassName=com.mysql.jdbc.Driver
userName1=root
password=123456
配置log4j.properties文件
### set log levels ###
log4j.rootLogger = debug , stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
在resources配置spring.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
">
<!-- spring是bean的容器(service+repository)-->
<context:component-scan base-package="cn">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 所有数据库操作的源头 实现自接口DataSouce
DriverManagerDataSource (请求产生一个连接 用完关闭 连接重用 连接池)
c3p0 dbcp druid(阿里 监控功能)
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${url}"></property>
<property name="driverClassName" value="${driverClassName}"></property>
<property name="username" value="${userName1}"></property>
<property name="password" value="${password}"></property>
<!-- 默认初始化的连接个数 -->
<property name="initialSize" value="1"></property>
<!-- 最大允许的连接个数 -->
<property name="maxActive" value="200"></property>
<!-- 最大的等待人数 -->
<property name="maxIdle" value="100"></property>
<!-- 开启sql统计功能 -->
<property name="filters" value="stat"></property>
</bean>
<!-- 集成mybatis -->
<!-- 配置session工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="sessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
</bean>
<!-- 扫描mybatis的接口映射 -->
<bean id="scannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.*.*.dao"></property>
</bean>
<!-- 事务管理器 -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvise" transaction-manager="tm">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* cn.*..*.service.*.*(..))" id="myPointCut"/>
<!-- 关联切点和事务管理器 -->
<aop:advisor advice-ref="txAdvise" pointcut-ref="myPointCut"/>
</aop:config>
<bean id="myFilter" class="cn.et.less01.conf.MyFilter">
</bean>
<!-- spring配置ini loginUrl没有登录 unauthorizedUrl没有权限-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.html"/>
<property name="unauthorizedUrl" value="/un.html"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="myFilter"></entry>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
<!-- key的值key="authc" -->
/** = authc
</value>
</property>
</bean>
<!-- 读取数据库的授权数据 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myDbRealm"/>
</bean>
<!-- 后置处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
</beans>
controller层
package cn.et.less01.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import cn.et.less01.dao.UserMapper;
@Controller
public class SpringLoginController {
@Autowired
UserMapper um;
@RequestMapping("/loginShiro")
public String Login(String userName,String password,Model model){
//获取当前的用户
Subject currentUser = SecurityUtils.getSubject();
//获取页面传过来的用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try {
currentUser.login( token );
model.addAttribute("menuList", um.queryMenuByMenu(userName));
//是否认证通过
return "/save.jsp";
} catch ( UnknownAccountException uae ) {
System.out.println("账号错误");
} catch ( IncorrectCredentialsException ice ) {
System.out.println("密码不匹配");
} catch ( LockedAccountException lae ) {
System.out.println("账号被锁定");
} catch ( AuthenticationException ae ) {
System.out.println("未知错误");
}
return "/un.html";
}
}
设计数据库
用户表
角色表
权限表
菜单表
用户与角色
角色与权限
权限与菜单
实体类
package cn.et.less01.entity;
public class UserInfo {
private Integer userId;
private String userName;
private String password;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
实体菜单类
package cn.et.less01.entity;
public class Menu {
private Integer menuid;
private String menuname;
private String menuurl;
private String menufitter;
private Integer ismenu;
public Integer getMenuid() {
return menuid;
}
public void setMenuid(Integer menuid) {
this.menuid = menuid;
}
public String getMenuname() {
return menuname;
}
public void setMenuname(String menuname) {
this.menuname = menuname;
}
public String getMenuurl() {
return menuurl;
}
public void setMenuurl(String menuurl) {
this.menuurl = menuurl;
}
public String getMenufitter() {
return menufitter;
}
public void setMenufitter(String menufitter) {
this.menufitter = menufitter;
}
public Integer getIsmenu() {
return ismenu;
}
public void setIsmenu(Integer ismenu) {
this.ismenu = ismenu;
}
@Override
public String toString() {
return "Menu [menuid=" + menuid + ", menuname=" + menuname + ", menuurl=" + menuurl + ", menufitter="
+ menufitter + ", ismenu=" + ismenu + "]";
}
}
业务逻辑层
package cn.et.less01.dao;
import java.util.List;
import java.util.Set;
import org.apache.ibatis.annotations.Select;
import cn.et.less01.entity.Menu;
import cn.et.less01.entity.UserInfo;
public interface UserMapper {
//查看用户
@Select("select user_name as userName,pass_word as password from user_info where user_name=#{0}")
public UserInfo queryUser(String userName);
//查看角色
@Select("SELECT r.role_name FROM user_info u INNER JOIN user_role_relation urr ON u.user_id =urr.user_id "
+ " INNER JOIN role r ON r.role_id= urr.role_id "
+ " WHERE user_name=#{0}")
public Set<String> queryRoleByName(String userName);
//查看权限
@Select("SELECT p.pems_tag FROM user_info u INNER JOIN user_role_relation urr ON u.user_id =urr.user_id "
+ " INNER JOIN role r ON r.role_id= urr.role_id "
+ " INNER JOIN role_pems_relation rpr ON rpr.role_id=r.role_id "
+ " INNER JOIN pems p ON rpr.pems_id=p.pems_id "
+ " WHERE user_name=#{0}")
public Set<String> queryPemsByName(String userName);
//查看所有的菜单
@Select("select menu_id as menuid,menu_name as menuname,menu_url as menuurl,menu_fitter as menufitter,is_menu as ismenu from menu")
public List<Menu> queryMenu();
//通过路径查看是否有权限
@Select("select menu_id as menuid,menu_name as menuname,menu_url as menuurl,menu_fitter as menufitter,is_menu as ismenu from menu where menu_url=#{0} ")
public List<Menu> queryMenuByurl(String url);
//通过用户看用户所属的菜单
@Select("SELECT menu_name as menuname,menu_url as menuurl,menu_fitter as menufitter,is_menu as ismenu FROM menu m INNER JOIN user_menu_relation umr ON"
+ " umr.menu_id=m.menu_id "
+ "INNER JOIN user_info u ON u.user_id = umr.user_id "
+ "WHERE u.user_name =#{0} ")
public List<Menu> queryMenuByMenu(String userName);
}
自定义过滤规则
package cn.et.less01.conf;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import cn.et.less01.dao.UserMapper;
import cn.et.less01.entity.Menu;
@Component
public class MyFilter extends AuthorizationFilter {
@Autowired
private ShiroFilterFactoryBean sffb;
/**
* 匹配指定过滤器规则的url
* @param regex
* @param url
* @return
*/
public static boolean matchUrl(String regex,String url){
regex=regex.replaceAll("/+", "/");
if(regex.equals(url)){
return true;
}
regex=regex.replaceAll("\\.", "\\\\.");
// /login.html /l*.html
regex=regex.replaceAll("\\*", ".*");
// /**/login.html /a/b/login.html
if(regex.indexOf("/.*.*/")>=0){
regex=regex.replaceAll("/\\.\\*\\.\\*/", "((/.*/)+|/)");
}
System.out.println(regex+"----"+url);
return Pattern.matches(regex, url);
}
@Autowired
UserMapper um;
/**
* 测试
* @param args
*/
public static void main(String[] args) {
System.out.println(matchUrl("/**/s*.html","/t/g/login.html"));
}
/**
* 在map中模拟 这个也可以将来定义在数据库中
*/
/**
* isAccessAllowed用于判断当前url的请求是否能验证通过 如果验证失败 调用父类的onAccessDenied决定跳转到登录失败页还是授权失败页面
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
HttpServletRequest req=(HttpServletRequest)request;
String contextPath=req.getContextPath();
//获取用户访问的资源的路径
String url=req.getRequestURI();
url=url.split(contextPath)[1];
//获取所有的菜单
List<Menu> qm= um.queryMenu();
//数据库没有配置当前URL的授权
if(qm.size()==0){
return false;
}
String urlAuth =null;
//获取那些URL需要哪些认证
// List<Menu> q=um.queryMenuByurl(url);
for(Menu mu:qm){
if(matchUrl(mu.getMenuurl(),url)){
urlAuth=mu.getMenufitter();
}
}
//如果有授权就获取权限有哪些
if(urlAuth==null){
return false;
}
//配置的过滤器是anon 直接放过
if(urlAuth.startsWith("anon")){
return true;
}
//配置的是authc 判断当前用户是否认证通过
Subject subject = getSubject(request, response);
if(urlAuth.startsWith("authc")){
return subject.isAuthenticated();
}
//授权认证 也需要判断是否登录 没有登录返回 登录继续下面的验证
boolean ifAuthc=subject.isAuthenticated();
if(!ifAuthc)
return ifAuthc;
//如果是定义的roles过滤器 获取所有的roles 一般是roles[a,b]
if(urlAuth.startsWith("roles")){
String[] rolesArray=urlAuth.split("roles\\[")[1].split("\\]")[0].split(",");
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
Set<String> roles = CollectionUtils.asSet(rolesArray);
return subject.hasAllRoles(roles);
}
if(urlAuth.startsWith("perms")){
String[] perms=urlAuth.split("perms\\[")[1].split("\\]")[0].split(",");
boolean isPermitted = true;
if (perms != null && perms.length > 0) {
if (perms.length == 1) {
if (!subject.isPermitted(perms[0])) {
isPermitted = false;
}
} else {
if (!subject.isPermittedAll(perms)) {
isPermitted = false;
}
}
}
return isPermitted;
}
return false;
}
}
自定义realm
package cn.et.less01.conf;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAccount;
import org.apache.shiro.authc.UsernamePasswordToken;
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.stereotype.Component;
import cn.et.less01.dao.UserMapper;
import cn.et.less01.entity.UserInfo;
@Component
public class MyDbRealm extends AuthorizingRealm{
@Autowired
UserMapper um;
/**
* 获取当前用户的授权数据
* 将当前用户在数据库的权限加载到AuthorizationInfo
* 默认 在进行授权认证时调用
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取用户名
String userName=principals.getPrimaryPrincipal().toString();
System.out.println(userName);
//查看角色
Set<String> roleList=um.queryRoleByName(userName);
//查看权限
Set<String> pemsList=um.queryPemsByName(userName);
//角色和权限集合对象
SimpleAuthorizationInfo at=new SimpleAuthorizationInfo();
//设置用户的角色
at.setRoles(roleList);
//设置用户的权限
at.setStringPermissions(pemsList);
return at;
}
/**
* 认证
* 将登陆输入的用户名和密码和数据库中的用户名个密码对比是否相等
* 返回null认证失败 非null成功
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取页面传进来的用户和密码
UsernamePasswordToken upt=(UsernamePasswordToken)token;
//查询数据库是否有这个账号
System.out.println(upt.getPassword());
System.out.println(token.getPrincipal());
//查询数据的用户和密码
UserInfo queryUs = um.queryUser(token.getPrincipal().toString());
//判断账号密码是否一致
if(queryUs!=null && queryUs.getPassword().equals(new String(upt.getPassword()))){
//登录成功
SimpleAccount sa = new SimpleAccount(upt.getUsername(),upt.getPassword(),"MyDbRealm");
return sa;
}
return null;
}
}