shiro (java安全框架)
Shiro简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
• Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等
下载:http://shiro.apache.org/
一、基本功能点如下图所示:
二、功能简介
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
三、Shiro 架构(Shiro外部来看)
Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外API 核心就是 Subject。Subject 代表了当前“用户”, 这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;与 Subject 的所有交互都会委托给 SecurityManager;Subject 其实是一个门面,SecurityManager 才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色
Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource
四、Shiro 架构(Shiro内部来看)
Subject:任何可以与应用交互的“用户”;
SecurityManager :相当于SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;
所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证、授权、会话及缓存的管理。
Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的 Realm;
SessionManager:管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web环境,也可以用在如普通的 JavaSE 环境
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。
五、集成 Spring
> 加入 Spring 和 Shiro 的 jar 包
> 配置 Spring 及 SpringMVC
> 参照:1.3.2\shiro-root-1.3.2-source-release\shiro-root-1.3.2\samples\spring 配置
web.xml 文件和 Spring 的配置文件
六、与WEB集成
• Shiro 提供了与 Web 集成的支持,其通过一个ShiroFilter 入口来拦截需要安全控制的URL,然后
进行相应的控制
• ShiroFilter 类似于如 Strut2/SpringMVC 这种web 框架的前端控制器,是安全控制的入口点,其
负责读取配置(如ini 配置文件),然后判断URL是否需要登录/权限等工作。
七、ShiroFilter(web.xml文件)
DelegatingFilterProxy 作用是自动到 Spring 容器查找名字为
shiroFilter(filter-name)的 bean 并把所有 Filter的操作委托给它。
<!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
<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>
<!-- Make sure any request you want accessible to Shiro is filtered. /* catches all -->
<!-- requests. Usually this filter mapping is defined first (before all others) to -->
<!-- ensure that Shiro works in subsequent filters in the filter chain: -->
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
部分细节
URL 匹配模式
URL 匹配顺序
URL 权限采取第一次匹配优先的方式,即从头开始
使用第一个匹配的 url 模式对应的拦截器链。
• 如:
/bb/**=filter1
/bb/aa=filter2
/**=filter3
如果请求的url是“/bb/aa”,因为按照声明顺序进行匹
配,那么将使用 filter1 进行拦截。
八、认证
身份验证
• 身份验证:一般需要提供如身份 ID 等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。
• 在 shiro 中,用户需要提供 ==principals (身份)和 credentials(证明)==给 shiro,从而应用能验证用户身份:
• principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个Primary principals,一般是用户名/邮箱/手机号。
• credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
• 最常见的 principals 和 credentials 组合就是用户名/密码了
身份验证基本流程
• 1、收集用户身份/凭证,即如用户名/密码
• 2、调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功
• 3、创建自定义的 Realm 类,继承org.apache.shiro.realm.AuthorizingRealm 类,实现doGetAuthenticationInfo() 方法
示例
/**
* 如果是没有认证的用户
*/
if (!currentUser.isAuthenticated()) {//判断当前用户是否认证过
UsernamePasswordToken token = new UsernamePasswordToken("root", "secret");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
//账号不存在
log.info("------->账号不存在 " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
//密码不匹配
log.info("------->密码错误 " + token.getPrincipal() + " was incorrect!");
//账号锁定
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
AuthenticationException
• 如果身份验证失败请捕获 AuthenticationException 或其子类
• 最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
认证流程
• 1、首先调用 Subject.login(token) 进行登录,其会自动委托给SecurityManager
• 2、SecurityManager 负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证;
• 3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入
自己的实现;
• 4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm身份验证,默认
ModularRealmAuthenticator 会调用AuthenticationStrategy 进行多 Realm 身份验证;
• 5、Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回/抛出异常表示
身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
九、Realm
• Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),即 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作
• Realm常见实现类如下:
• 一般继承 AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。
• Realm 的继承关系:
十、MD5加密
public static void main(String[] args) {
/** 加密
* new SimpleHash(algorithmName, source, salt, hashIterations)
*algorithmName 加密算法的名称
*source 需要加密的内容
*salt 盐值,初学一般为用户名,只要用户名唯一,产生出来的密码就不一样
*hashIterations 加密的次数
*/
// System.out.println(new SimpleHash("Md5", "123456", "admin", 1024));
System.out.println(new SimpleHash("MD5", "123456", "admin", 1024).toString());
}
十一、授权
授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许。
Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)
角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
授权方式
• Shiro 支持三种方式的授权:
– 编程式:通过写if/else 授权代码块完成
– 注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相应的异常
– JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
默认拦截器
• Shiro 内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:
授权相关
其他
添加权限的实现
1、继承AuthorizingRealm
2、doGetAuthorizationInfo(PrincipalCollection principals) principals获取当前用户信息
3、实现
Set<String> roles=new HashSet<String>();
roles.add("user");
if(principals.getPrimaryPrincipal().equals("admin")){
roles.add("admin");
}
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(roles);
添加细节权限(细节到恶心)
// 获取身份信息
String username = (String) principals.getPrimaryPrincipal();
// 根据身份信息从数据库中查询权限数据 /
/....这里使用静态数据模拟
List<String> permissions = new ArrayList<String>();
permissions.add("user:create");
permissions.add("user.delete");
//将权限信息封闭为
AuthorizationInfo SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for(String permission:permissions){
simpleAuthorizationInfo.addStringPermission(permission);
}
十二、Shiro标签(欺骗一般人士的标签)
• Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如根据登录用户显示相应的页面按钮。
• guest 标签:用户没有身份验证时显示相应信息,即游客访问信息:
• user 标签:用户已经经过认证/记住我登录后显示相应的信息。
• authenticated 标签:用户已经身份验证通过,即Subject.login登录成功
• notAuthenticated 标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。
• pincipal 标签:显示用户身份信息,默认调用Subject.getPrincipal() 获取,即 Primary Principal。
• hasRole 标签:如果当前 Subject 有角色将显示 body 体内容
• hasAnyRoles 标签:如果当前Subject有任意一个角色(或的关系)将显示body体内容。
• lacksRole:如果当前 Subject 没有该角色将显示 body 体内容
<shiro:user>
<shiro:principal/>
</shiro:user>
<a href="loginout">注销</a>
<!-- 多种角色显示的模块 -->
<shiro:hasAnyRoles name="admin,test">
测试模块
</shiro:hasAnyRoles>
<!-- shiro:lacksRole name="admin" 非哪种角色显示的模块 -->
<shiro:lacksRole name="admin">
非管理员模块
</shiro:lacksRole>
<!-- 哪种角色显示的模块 -->
<shiro:hasRole name="admin">
<a href="javascript:void(0)" onclick="admin()">管理员</a>
</shiro:hasRole>
网站首页
<shiro:guest>
亲,请<a hrdf="login">登录</a> <a hrdf="register">注册</a>
</shiro:guest>
<shiro:user>
欢迎你!<shiro:principal/>
</shiro:user>
十三、权限注解
• @RequiresAuthentication:表示当前Subject已经通过login进行了身份验证;即 Subject. isAuthenticated() 返回 true
• @RequiresUser:表示当前 Subject 已经身份验证或者通过记住我登录的。
• @RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
• @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前 Subject 需要角色 admin 和user
• @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject 需要权限 user:a 或 user:b。
loginuser表
role表
user_role表
pom.xml(引入相关的jar包,包括SSM三大框架)
<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.hqyj</groupId>
<artifactId>1809_shiro-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!-- spring基本依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
<scope>runtime</scope>
</dependency>
<!-- springmvc依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<!-- JSON核心包 -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.7</version>
</dependency>
<!-- JSON数据绑定包 -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.7</version>
</dependency>
<!-- 引入mybatis的依赖 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- 引入数据库支持 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<!-- 依赖aspectj.weaver.jar -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<!-- 依赖aopalliance.jar -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- c3p0连接池工具 -->
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- 引入mybatis对spring框架整合的jar包 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- spring-jdbc事务依赖 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<!-- 对shiro的支持 -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</project>
web.xml文件(配置springmvc核心,监听器,shiro安全框架)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<!--监听器-->
<!-- needed for ContextLoaderListener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationcontext-dao.xml,classpath:applicationcontext-shiro.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--springmvc核心-->
<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationcontext-springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--定义过滤器和映射-->
<!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
<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>
<!-- Make sure any request you want accessible to Shiro is filtered. /* catches all -->
<!-- requests. Usually this filter mapping is defined first (before all others) to -->
<!-- ensure that Shiro works in subsequent filters in the filter chain: -->
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
applicationcontext-springmvc(对springmvc的全局配置)
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="com.hqyj.action"></context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<mvc:annotation-driven></mvc:annotation-driven>
<mvc:default-servlet-handler/>
</beans>
applicationcontext-dao(对mybatis的全局配置)
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:component-scan base-package="com.hqyj.service"></context:component-scan>
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据库连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driver}"></property>
<property name="jdbcUrl" value="${url}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
<property name="initialPoolSize" value="10"></property>
<property name="maxIdleTime" value="30"></property>
<property name="maxPoolSize" value="100"></property>
<property name="minPoolSize" value="10"></property>
<property name="maxStatements" value="200"></property>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath*:mappers/**/*.xml"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hqyj.dao"></property>
</bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
applicationcontext-shiro(对shiro的全局配置)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置shiroFilter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- override these for application-specific URLs if you like:
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="/home.jsp"/>
<property name="unauthorizedUrl" value="/unauthorized.jsp"/> -->
<!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean -->
<!-- defined will be automatically acquired and available via its beanName in chain -->
<!-- definitions, but you can perform instance overrides or name aliases here if you like: -->
<!-- <property name="filters">
<util:map>
<entry key="anAlias" value-ref="someFilter"/>
</util:map>
</property> -->
<!-- 未认证之前默认的跳转地址 -->
<property name="loginUrl" value="/login"/>
<!-- 未授权的url地址 -->
<!-- <property name="unauthorizedUrl" value="/url?url=unauthorized"/> -->
<property name="unauthorizedUrl" value="/unauthorized"/>
<!-- 拦截规则 -->
<property name="filterChainDefinitions">
<value>
<!-- 优先匹配,只要匹配上一个,后面的不在遵循 -->
# some example chain definitions:
# anon: 代表匿名访问
# authc: 代表认证访问
# logout: 代表注销
# roles[admin]:需要哪些权限
/login=anon
/testlogin=anon
/jquery/**=anon
/register/**=anon
/registeraction/**=anon
/registersuccess/**=anon
/loginout=logout
/url=anon
/jump/** = anon
/admin/** = authc
<!-- /admin/** = authc, roles[admin]
/docs/** = authc, perms[document:read] -->
/** = authc
# more URL-to-FilterChain definitions here
</value>
</property>
</bean>
<!-- Define any javax.servlet.Filter beans you want anywhere in this application context. -->
<!-- They will automatically be acquired by the 'shiroFilter' bean above and made available -->
<!-- to the 'filterChainDefinitions' property. Or you can manually/explicitly add them -->
<!-- to the shiroFilter's 'filters' Map if desired. See its JavaDoc for more details. -->
<!-- <bean id="someFilter" class=""/>
<bean id="anotherFilter" class=""> </bean> -->
<!-- securityManager:管理所有的会话 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- Single realm app. If you have multiple realms, use the 'realms' property instead. -->
<property name="realm" ref="myRealm"/>
<!-- By default the servlet container sessions will be used. Uncomment this line
to use shiro's native sessions (see the JavaDoc for more): -->
<!-- <property name="sessionMode" value="native"/> -->
</bean>
<!-- lifecycleBeanPostProcessor: 生命周期的管理 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Define the Shiro Realm implementation you want to use to connect to your back-end -->
<!-- security datasource: -->
<!-- 加载资源包 -->
<bean id="myRealm" class="com.hqyj.shiro.MyRealm">
</bean>
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after -->
<!-- the lifecycleBeanProcessor has run: -->
<!-- 对shiro注解的支持 -->
<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>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--系统设置 -->
<settings>
<!-- 设置二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!--默认设置懒加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<!-- <typeAliases>
取别名
<typeAlias type="" alias=""/>
</typeAliases> -->
</configuration>
loginuser.xml(mybatis映射文件)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace:名称空间 :面向接口编程的时候,必须为接口所在位置 -->
<mapper namespace="com.hqyj.dao.LoginUserDao">
<select id="findBynameandPwd" resultType="com.hqyj.bean.LoginUser">
select * from loginuser where username = #{username} and password = #{password}
</select>
<select id="findByname" resultType="com.hqyj.bean.LoginUser">
select * from loginuser where username = #{username}
</select>
<insert id="addUser">
insert into loginuser values(#{id},#{username},#{password});
</insert>
<select id="findRole" resultMap="rolemap">
SELECT rolename FROM user_role where username = #{username}
</select>
<resultMap type="com.hqyj.bean.UserRole" id="rolemap">
<result property="rolename" column="rolename"/>
</resultMap>
</mapper>
db.propercties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/1809_shiro_test?characterEncoding=utf-8&useSSL=false
user=root
password=963521
LoginUser.java
package com.hqyj.bean;
public class LoginUser {
private int id;
private String username;
private String password;
private int r_id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
public int getR_id() {
return r_id;
}
public void setR_id(int r_id) {
this.r_id = r_id;
}
@Override
public String toString() {
return "LoginUser [id=" + id + ", username=" + username + ", password=" + password + ", r_id=" + r_id + "]";
}
public LoginUser() {
super();
// TODO Auto-generated constructor stub
}
}
UserRole.java
package com.hqyj.bean;
public class UserRole {
private int id;
private String username;
private String rolename;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRolename() {
return rolename;
}
public void setRolename(String rolename) {
this.rolename = rolename;
}
public UserRole() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "UserRole [id=" + id + ", username=" + username + ", rolename=" + rolename + "]";
}
}
LoginUserDao.java
package com.hqyj.dao;
import java.util.List;
import com.hqyj.bean.LoginUser;
import com.hqyj.bean.UserRole;
public interface LoginUserDao {
public LoginUser findBynameandPwd(LoginUser user);
public LoginUser findByname(LoginUser user);
public void addUser(LoginUser user);
public List<UserRole> findRole(LoginUser user);
}
LoginUserService.java
package com.hqyj.service;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hqyj.bean.LoginUser;
import com.hqyj.bean.UserRole;
import com.hqyj.dao.LoginUserDao;
@Service
public class LoginUserService {
@Autowired
private LoginUserDao dao;
public LoginUser findBynameandPwd(LoginUser user) {
return dao.findBynameandPwd(user);
}
public List<UserRole> findRole(LoginUser user){
return dao.findRole(user);
}
//权限管理,没有admin权限无法进入,会报500错误
@RequiresRoles(value= {"admin"})
public void test() {
System.out.println("我被调用了");
}
}
LoginAction.java
package com.hqyj.action;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.hqyj.bean.LoginUser;
import com.hqyj.dao.LoginUserDao;
@Controller
public class LoginAction {
@Autowired
private LoginUserDao dao;
@RequestMapping("/login")
public String login() {
return "index";
}
@RequestMapping("/testlogin")
@ResponseBody
public String test(@RequestParam("username")String username,@RequestParam("password")String password) {
Subject currentuser = SecurityUtils.getSubject();
if(!currentuser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
token.setRememberMe(true);
try {
currentuser.login(token);
}catch (AuthenticationException e) {
return "fail";
}
}
return "success";
}
// 注册页面
@RequestMapping("/register")
public String register() {
return "register";
}
// 注册检测
@RequestMapping("/registeraction")
@ResponseBody
public String registeraction(LoginUser user) {
if (user.getUsername() == null || user.getUsername().equals("")) {
return "null";
} else {
LoginUser u = dao.findByname(user);
if (u != null) {
return "fail";
} else {
return "ok";
}
}
}
// 注册
@RequestMapping("/registersuccess")
public String registersuccess(LoginUser user) {
System.out.println(user);
String username = user.getUsername();
String password = user.getPassword();
user.setPassword(new SimpleHash("MD5", password, username, 1024).toString());
dao.addUser(user);
return "login";
}
// 登录成功跳转页面
@RequestMapping("/success")
public String dologin() {
return "success";
}
}
JumpUtil.java(跳转工具类)
package com.hqyj.action;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class JumpUtil {
@RequestMapping("/jump")
public String jump(@RequestParam("url") String url) {
return url;
}
}
Unauthorized.java(没有权限返回ajax请求的类)
package com.hqyj.action;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class Unauthorized {
@RequestMapping("unauthorized")
@ResponseBody
public String unauthorized() {
return "sorry";
}
}
GlobeException.java(全局异常类)
package com.hqyj.action;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobeException {
@ExceptionHandler(value= {RuntimeException.class})
public String exception() {
return "unauthorized";
}
}
AdminAction.java(有权限跳转成功返回ajax请求的类)
package com.hqyj.action;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.hqyj.service.LoginUserService;
@Controller
@RequestMapping("/admin")
public class AdminAction {
@Autowired
private LoginUserService service;
@RequestMapping("/test")
@ResponseBody
public String admin() {
service.test();
return "ok";
}
}
MyRealm.java
package com.hqyj.shiro;
import java.util.HashSet;
import java.util.List;
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.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import com.hqyj.bean.LoginUser;
import com.hqyj.bean.UserRole;
import com.hqyj.service.LoginUserService;
public class MyRealm extends AuthorizingRealm{
@Autowired
private LoginUserService service;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
LoginUser loginUser = new LoginUser();
loginUser.setUsername((String) arg0.getPrimaryPrincipal());
List<UserRole> role_list = service.findRole(loginUser);
Set<String> set = new HashSet<>();
for (UserRole userRole : role_list) {
set.add(userRole.getRolename());
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(set);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) arg0;
LoginUser user = new LoginUser();
user.setUsername(token.getUsername());
user.setPassword(new SimpleHash("MD5", token.getPassword(), token.getUsername(), 1024).toString());
LoginUser u = service.findBynameandPwd(user);
if(u==null) {
throw new AuthenticationException();
}
AuthenticationInfo info = new SimpleAuthenticationInfo(token.getUsername(),token.getPassword(),getName());
return info;
}
}
index.jsp(默认进入页面)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!--
参考servlet中的接口:
request.getScheme();
返回的协议名称,默认是http
request.getServerName()
返回的是服务器地址,如果是本机,则是localhost
getServerPort()
获取服务器端口号
request.getContextPath()得到项目的名字,
-->
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html>
<html>
<head>
<base href="<%=basePath%>">
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
网站首页
<shiro:guest>
亲,请<a href="jump?url=login">登录</a> <a href="register">注册</a>
</shiro:guest>
<shiro:user>
欢迎你!<shiro:principal/>
</shiro:user>
</body>
</html>
login.jsp(登录页面)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html>
<html>
<head>
<base href="<%=basePath%>">
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="jquery/jquery-2.1.3.js"></script>
</head>
<body>
<h1>登录</h1>
<form>
用户名:<input type="text" name="username" id="username">
密码:<input type="password" name="password" id="password">
<input type="button" value="提交" onclick="check()">
<a href="register">注册</a>
</form>
<script type="text/javascript">
//检查该用户名是否存在
function check(){
var username = $("#username").val();
var password = $("#password").val();
$.ajax({
type: "GET",
url: "testlogin",
data: "username="+username+"&password="+password,
success: function(msg){
if(msg=="success"){
window.location.href = "success";
}else{
alert("用户或密码错误");
}
},
error:function(error){
alert("请求失败");
}
});
}
</script>
</body>
</html>
register.jsp(注册页面)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!--
参考servlet中的接口:
request.getScheme();
返回的协议名称,默认是http
request.getServerName()
返回的是服务器地址,如果是本机,则是localhost
getServerPort()
获取服务器端口号
request.getContextPath()得到项目的名字,
-->
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html>
<html>
<head>
<base href="<%=basePath%>">
<meta charset="UTF-8">
<title>注册页面</title>
<script type="text/javascript" src="jquery/jquery-2.1.3.min.js"></script>
</head>
<body>
<h1>注册</h1>
<form action="registersuccess" onsubmit="return doregister()">
用户名:<input type="text" name="username" onblur="checkusername()" id="username">
<span id="msg"></span>
<br>
密码:<input type="password" name="password" id="password"><br>
<input type="submit" value="注册">
</form>
<script type="text/javascript">
//检查该用户名是否存在
var flag = false;
function checkusername(){
var username = $("#username").val();
$.ajax({
type: "GET",
url: "registeraction",
data: "username="+username,
success: function(msg){
if(msg=="ok"){
$("#msg").html("该账号可以使用");
flag = true;
}else if(msg=="null"){
$("#msg").html("请输入账号");
flag = false;
}else{
$("#msg").html("该账号已被占用");
flag = false;
}
console.log(flag);
},
error:function(error){
alert("请求失败");
flag = false;
}
});
}
function doregister(){
if(!flag){
alert("请输入正确的信息");
}else{
alert("注册成功,请登录");
}
return flag;
}
</script>
</body>
</html>
success.jsp(登录成功页面)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!--
参考servlet中的接口:
request.getScheme();
返回的协议名称,默认是http
request.getServerName()
返回的是服务器地址,如果是本机,则是localhost
getServerPort()
获取服务器端口号
request.getContextPath()得到项目的名字,
-->
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html>
<html>
<head>
<base href="<%=basePath%>">
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="jquery/jquery-2.1.3.min.js"></script>
</head>
<body>
<h1>欢迎您!
<shiro:principal/>
</h1>
<a href="loginout">注销</a>
<shiro:hasAnyRoles name="admin,test">
<a href="login">主页</a>
</shiro:hasAnyRoles>
<shiro:lacksRole name="admin">
不是管理员看的模板
</shiro:lacksRole>
<shiro:hasRole name="admin">
<a href="javascript:void(0)" onclick="admin()">管理员</a>
</shiro:hasRole>
<script type="text/javascript">
function admin(){
$.ajax({
type:"GET",
url:"admin/test",
success:function(msg){
if(msg=="sorry"){
alert("对不起,您没有权限");
}else{
window.location.href="jump?url=admin";
}
},
error:function(msg){
alert("请求失败");
}
});
}
</script>
</body>
</html>
unauthorized.jsp(没有权限跳转的页面)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!--
参考servlet中的接口:
request.getScheme();
返回的协议名称,默认是http
request.getServerName()
返回的是服务器地址,如果是本机,则是localhost
getServerPort()
获取服务器端口号
request.getContextPath()得到项目的名字,
-->
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html>
<html>
<head>
<base href="<%=basePath%>">
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
对不起,你不具备该权限
</body>
</html>
admin.jsp(有权限跳转的页面)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!--
参考servlet中的接口:
request.getScheme();
返回的协议名称,默认是http
request.getServerName()
返回的是服务器地址,如果是本机,则是localhost
getServerPort()
获取服务器端口号
request.getContextPath()得到项目的名字,
-->
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html>
<html>
<head>
<base href="<%=basePath%>">
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
管理员页面
</body>
</html>