JavaWeb日记——当Shiro遇上Spring
来源:互联网 发布:php 优惠券使用代码 编辑:程序博客网 时间:2024/05/16 10:27
在网络项目开发过程中经常要用到用户登录,还有权限管理,Shiro可以说是Spring的一把利器。
看懂这一篇博客需要两个要求
1. 懂得SpirngMVC的基本配置和使用
2. 懂得Shiro的基本配置和使用
先看一下项目结构
这个项目可以作为pull下来在作为一般项目的脚手架
POM
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jk.shiroLearning</groupId> <artifactId>chapter5</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>chapter5 Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <springfox-version>2.3.0</springfox-version> <spring-version>4.2.4.RELEASE</spring-version> <servlet-api-version>3.1.0</servlet-api-version> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--Spring dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet-api-version}</version> <scope>provided</scope> </dependency> <!--shiro start--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>0.2.23</version> </dependency> <!--shiro end--> </dependencies> <build> <finalName>chapter5</finalName> </build></project>
首先要配置Web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 1. 配置 Shiro 的 shiroFilter. 2. DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和 <filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id. --> <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>
然后配置spring-servlet.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:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.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.0.xsd"> <context:component-scan base-package="com.jk"></context:component-scan> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"></property> <property name="suffix" value=".jsp"></property> </bean> <mvc:annotation-driven></mvc:annotation-driven> <mvc:default-servlet-handler/></beans>
然后配置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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 1. 配置 SecurityManager! --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="authenticator" ref="authenticator"></property> <property name="realms"> <list> <ref bean="jdbcRealm"/> </list> </property> <property name="rememberMeManager.cookie.maxAge" value="10"></property> </bean> <!-- 2. 配置 CacheManager. 2.1 需要加入 ehcache 的 jar 包及配置文件. --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"/> </property> </bean> <!-- 3. 配置 JdbcRealm --> <bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5"></property> <property name="hashIterations" value="1024"></property> </bean> </property> <property name="dataSource"> <bean class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/shiro"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> </property> <property name="permissionsLookupEnabled" value="true"></property> </bean> <!-- 4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法. --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用. --> <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> <!-- 6. 配置 ShiroFilter. 6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致. 若不一致, 则会抛出: NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean. --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!--登录页面的url--> <property name="loginUrl" value="/login.jsp"/> <!--登录成功跳转到该url--> <property name="successUrl" value="/list.jsp"/> <!--没有角色或者身份跳转到该url--> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <!--配置自定义filterChainDefinitionMap,可用filterChainDefinitions代替--> <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property></property> <!-- 配置哪些页面需要受保护. 以及访问这些页面需要的权限. 1). anon 可以被匿名访问 2). authc 必须认证(即登录)后才可能访问的页面. 3). logout 登出. 4). roles 角色过滤器 --> <!--<property name="filterChainDefinitions">--> <!--<value>--> <!--/login.jsp = anon--> <!--/shiro/login = anon--> <!--/shiro/logout = logout--> <!--/role1.jsp = roles[role1]--> <!--/admin.jsp = roles[admin]--> <!--# everything else requires authentication:--> <!--/** = authc--> <!--</value>--> <!--</property>--> </bean> <!-- 配置一个 路由权限的 bean 替代上面的filterChainDefinitions, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 --> <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean> <bean id="filterChainDefinitionMapBuilder" class="com.jk.factory.FilterChainDefinitionMapBuilder"></bean> <bean id="shiroService" class="com.jk.services.ShiroService"></bean></beans>
配置中重点讲解一下filterChainDefinitions,value对应的是url=权限或角色,具体如下
我们可以用一个filterChainDefinitionMap代替filterChainDefinitions
filterChainDefinitionMap
public class FilterChainDefinitionMapBuilder { public LinkedHashMap<String, String> buildFilterChainDefinitionMap(){ LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(); map.put("/login.jsp", "anon"); map.put("/shiro/login", "anon"); map.put("/shiro/logout", "logout"); map.put("/role1.jsp", "authc,roles[role1]"); map.put("/admin.jsp", "authc,roles[admin]"); map.put("/list.jsp", "user"); map.put("/**", "authc"); return map; }}
还有一点就是url的权限是先定义优先级越高,后定义的不会覆盖先定义的,可用/**匹配任何地址
然后再配置缓存
<ehcache> <diskStore path="java.io.tmpdir"/> <!-- 登录记录缓存 锁定10分钟 --> <cache name="authorizationCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <cache name="authenticationCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <cache name="shiro-activeSessionCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /></ehcache>
初始化数据库
drop database if exists shiro;create database shiro;use shiro;create table users ( id bigint auto_increment, username varchar(100), password varchar(100), password_salt varchar(100), constraint pk_users primary key(id)) charset=utf8 ENGINE=InnoDB;create unique index idx_users_username on users(username);create table user_roles( id bigint auto_increment, username varchar(100), role_name varchar(100), constraint pk_user_roles primary key(id)) charset=utf8 ENGINE=InnoDB;create unique index idx_user_roles on user_roles(username, role_name);create table roles_permissions( id bigint auto_increment, role_name varchar(100), permission varchar(100), constraint pk_roles_permissions primary key(id)) charset=utf8 ENGINE=InnoDB;create unique index idx_roles_permissions on roles_permissions(role_name, permission);insert into users(username, password, password_salt) values('jack', 'fc1709d0a95a6be30bc5926fdb7f22f4', 'jack');insert into user_roles(username, role_name) values('jack', 'role1');insert into user_roles(username, role_name) values('jack', 'role2');insert into roles_permissions(role_name, permission) values('role1', 'user1:*');insert into roles_permissions(role_name, permission) values('role1', 'user2:*');insert into roles_permissions(role_name, permission) values('role2', 'user3:*');
再看Controller
@Controller@RequestMapping("/shiro")public class ShiroController { @Autowired private ShiroService shiroService; @RequestMapping("/testShiroAnnotation") public String testShiroAnnotation(HttpSession session){ session.setAttribute("key", "value12345"); try { shiroService.testPermissionMethod(); shiroService.testRoleMethod(); }catch (UnauthorizedException e){ return "redirect:/unauthorized.jsp"; } return "redirect:/list.jsp"; } @RequestMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password){ Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { // 把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 记住登录 token.setRememberMe(true); try { // 执行登录. currentUser.login(token); } // 所有认证时异常的父类. catch (AuthenticationException ae) { System.out.println("登录失败: " + ae.getMessage()); } } return "redirect:/list.jsp"; }}
可以留意到除了登录以外还有一个测试注解的方法,注解是一种比较优雅的限制执行方法权限的方法,看一下如何来使用注解
注解
public class ShiroService { //只需满足其中一种角色就好 @RequiresRoles({"role1","admin"}) public void testRoleMethod(){ System.out.println("testMethod, time: " + new Date()); Session session = SecurityUtils.getSubject().getSession(); Object val = session.getAttribute("key"); System.out.println("Service SessionVal: " + val); } //只需满足其中一种权限就好 @RequiresPermissions({"user1:*","user4:*"}) public void testPermissionMethod(){ System.out.println("testMethod, time: " + new Date()); Session session = SecurityUtils.getSubject().getSession(); Object val = session.getAttribute("key"); System.out.println("Service SessionVal: " + val); }}
Shiro还为我们提供了SecurityUtils.getSubject().getSession()的方法来获取Session,这样就不用传requset到方法里。
Shiro还有我们提供了标签
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%><%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!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> <h4>List Page</h4> <shiro:guest> 欢迎游客访问 </shiro:guest> <shiro:user> 已登录 </shiro:user> <shiro:authenticated> 已通过认证 </shiro:authenticated> <shiro:notAuthenticated> 未通过身份认证(包括记住我) </shiro:notAuthenticated> <shiro:hasRole name="admin"> 拥有角色admin </shiro:hasRole> <shiro:hasAnyRoles name="admin,role1"> 拥有角色admin或role1 </shiro:hasAnyRoles> <shiro:lacksRole name="admin"> 不拥有角色admin </shiro:lacksRole> Welcome: <shiro:principal></shiro:principal></body></html>
• guest 标签:用户没有身份验证时显示相应信息,即游客访问信息:
•user 标签:用户已经经过认证/记住我登录后显示相应的信息。
• authenticated 标签:用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的
• notAuthenticated 标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。
• pincipal 标签:显示用户身份信息,默认调用Subject.getPrincipal() 获取,即 Primary Principal。
• hasRole 标签:如果当前 Subject 有角色将显示 body 体内容:Shiro 标签
• hasAnyRoles 标签:如果当前Subject有任意一个角色(或的关系)将显示body体内容。
Shiro对一些角色和复杂的项目简直就是福音,对spring的支持也是十分友好,配置起来十分的简单
源码地址:https://github.com/jkgeekJack/shiro-learning/tree/master/chapter5
- JavaWeb日记——当Shiro遇上Spring
- JavaWeb日记——当springmvc遇上swagger2
- JavaWeb日记——Shiro初体验
- JavaWeb日记——Shiro之角色和权限
- JavaWeb日记——Shiro从数据库获取身份信息
- JavaWeb日记——Shiro之密码加密
- 当Hibernate遇上Spring
- 当 Hibernate 遇上 Spring
- 当Hibernate遇上Spring
- 当 Hibernate 遇上 Spring
- 当MongoDB遇上Spring
- 当 Hibernate 遇上 Spring(转)
- 奇怪的Hibernate——当?遇上%
- 当creator遇上protobufjs—pbkiller插件
- Spring 系列:当 Hibernate 遇上 Spring
- 当hibernate遇上spring(OpenSessionInViewFilter)
- 当社交遇上金融—平安天下通的启示
- JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring)
- Centos安装RabbitMQ
- 深入理解java类加载机制
- 《CLR via C#》读书笔记---07 常量和字段
- Prometheus 实战于源码分析之storage
- sublime3中的 Error trying to parser file
- JavaWeb日记——当Shiro遇上Spring
- Java的switch是否支持String作为参数,还支持哪些类型?
- 双目相关
- OKHttpUtils使用介绍
- drawable2
- JSON数据格式
- Codeforces Round #410 (Div. 2)
- php 文字输入输出
- 笔记——ROM、PROM、EPROM、EEPROM、Flash ROM