JavaWeb日记——当Shiro遇上Spring

在网络项目开发过程中经常要用到用户登录,还有权限管理,Shiro可以说是Spring的一把利器。

看懂这一篇博客需要两个要求
1. 懂得SpirngMVC的基本配置和使用
2. 懂得Shiro的基本配置和使用

先看一下项目结构
JavaWeb日记——当Shiro遇上Spring_第1张图片

这个项目可以作为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.0modelVersion>
    <groupId>com.jk.shiroLearninggroupId>
    <artifactId>chapter5artifactId>
    <packaging>warpackaging>
    <version>1.0-SNAPSHOTversion>
    <name>chapter5 Maven Webappname>
    <url>http://maven.apache.orgurl>

    <properties>
        <springfox-version>2.3.0springfox-version>
        <spring-version>4.2.4.RELEASEspring-version>
        <servlet-api-version>3.1.0servlet-api-version>
    properties>

    <dependencies>
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>jstlartifactId>
            <version>1.2version>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webmvcartifactId>
            <version>${spring-version}version>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-context-supportartifactId>
            <version>${spring-version}version>
        dependency>
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>${servlet-api-version}version>
            <scope>providedscope>
        dependency>
        
        <dependency>
            <groupId>commons-logginggroupId>
            <artifactId>commons-loggingartifactId>
            <version>1.1.3version>
        dependency>
        <dependency>
            <groupId>commons-collectionsgroupId>
            <artifactId>commons-collectionsartifactId>
            <version>3.2.1version>
        dependency>
        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-coreartifactId>
            <version>1.2.2version>
        dependency>
        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-webartifactId>
            <version>1.2.2version>
        dependency>
        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-ehcacheartifactId>
            <version>1.2.2version>
        dependency>
        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-springartifactId>
            <version>1.2.2version>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.25version>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>0.2.23version>
        dependency>
        
    dependencies>
    <build>
        <finalName>chapter5finalName>
    build>
project>

首先要配置Web.xml


<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>contextConfigLocationparam-name>
        <param-value>classpath:applicationContext.xmlparam-value>
    context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    listener>

    <servlet>
        <servlet-name>springservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        <load-on-startup>1load-on-startup>
    servlet>

    <servlet-mapping>
        <servlet-name>springservlet-name>
        <url-pattern>/url-pattern>
    servlet-mapping>

    
    <filter>
        <filter-name>shiroFilterfilter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
        <init-param>
            <param-name>targetFilterLifecycleparam-name>
            <param-value>trueparam-value>
        init-param>
    filter>

    <filter-mapping>
        <filter-name>shiroFilterfilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>

web-app>

然后配置spring-servlet.xml


<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


<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">



    
    <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>

    
    <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>

    
    <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>


    
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.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>

    
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        
        <property name="loginUrl" value="/login.jsp"/>
        
        <property name="successUrl" value="/list.jsp"/>
        
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        
        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap">property>property>
        
        
        
        
        
        

        
        

        
        
        
        
    bean>

    
    <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=权限或角色,具体如下
JavaWeb日记——当Shiro遇上Spring_第2张图片
JavaWeb日记——当Shiro遇上Spring_第3张图片

我们可以用一个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"/>

    
    <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" %>    


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title heretitle>
head>
<body>
    <h4>List Pageh4>
    <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)