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

0 0
原创粉丝点击