使用shiro結合spring框架進行用戶認證

Apache Shiro 是 Java 的一個安全框架。目前,使用 Apache Shiro 的人越來越多,因為它相當簡單,對比 Spring Security,可能沒有 Spring Security 做的功能強大,但是在實際工作時可能并不需要那么復雜的東西,所以使用小而簡單的 Shiro 就足夠了。

HP-shiro-spring是一個簡單的基于Spring實現(xiàn)shiro的例子,進行用戶的身份認證,實現(xiàn)基于role的授權。該項目是由MyEclipse進行構建的動態(tài)web項目。

項目具體實現(xiàn)層次結構如下:


HP-shiro-spring-test.png
  1. 首先定義shiro.ini,用來指定用戶身份和憑據(jù)。
[users]
root = secret, root
guest = guest, guest
gandhi = 12345, role1, role2
bose = 67890, role2

[roles]
root = *
role1 = filesystem:*,system:*
role2 = "calculator:add,subtract"

上面的shiro.ini文件定義了四個用戶,格式為“用戶名=密碼,角色”;每個角色擁有一些權限。
root擁有所有的權限,role1擁有filesystem以及system的所有權限,role2擁有calculator的add和substract權限。這些權限在當前用戶對系統(tǒng)資源進行訪問的時候要用到。

2.定義配置文件
web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
    version="2.5">
    <!-- 作用:在啟動Web容器時,自動裝配Spring applicationContext.xml的配置信息 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    
    
    <listener>
        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>    

    <!-- 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>
        <dispatcher>REQUEST</dispatcher> 
        <dispatcher>FORWARD</dispatcher> 
        <dispatcher>INCLUDE</dispatcher> 
        <dispatcher>ERROR</dispatcher>        
    </filter-mapping>
    
    
    <servlet>
        <servlet-name>springMvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    
    <!-- 
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.hp.shiro.simplerbac.controller.LoginServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>logout</servlet-name>
        <servlet-class>com.hp.shiro.simplerbac.controller.LogoutServlet</servlet-class>
    </servlet>
    
    <servlet>
        <servlet-name>home</servlet-name>
        <servlet-class>com.hp.shiro.simplerbac.controller.HomeServlet</servlet-class>
    </servlet>
     -->
    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
<!-- 
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>logout</servlet-name>
        <url-pattern>/logout</url-pattern>
    </servlet-mapping>
    
    <servlet-mapping>
        <servlet-name>home</servlet-name>
        <url-pattern>/home/*</url-pattern>
    </servlet-mapping>
 -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

springMvc-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: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-3.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
    <!-- is actually rather pointless. It declares explicit support 
    for annotation-driven MVC controllers (i.e.@RequestMapping, 
    @Controller, etc), even though support for those is the default
     behaviour  
     當我們需要controller返回一個map的json對象時,可以設定<mvc:annotation-driven />
     會自動注冊DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter
      兩個bean,是spring MVC為@Controllers分發(fā)請求所必須的。并提供了:數(shù)據(jù)綁定支持,
    @NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,讀寫XML
    的支持(JAXB),讀寫JSON的支持(Jackson)-->
    <mvc:annotation-driven />
    
    <!-- 指定靜態(tài)資源的位置,例如js,css和圖片等文件,放到webroot文件夾下 -->
    <mvc:resources mapping="/css/**" location="/css/" />
    <mvc:default-servlet-handler />
    
    <!-- 啟用spring mvc注解  例如 @Required, @Autowired, @PostConstruct-->
    <context:annotation-config />
    
    <!-- 設置使用注解的類所在的包名 -->
    <context:component-scan base-package="com.hp.shiro.simplerbac.controller" />
    
    <!--完成請求和注解pojo的映射。 
    當我們需要controller返回一個map的json對象時,可以設定<mvc:annotation-driven />,
    同時設定<mvc:message-converters> 標簽,設定字符集和json處理類 -->
    <bean
        class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean
                    class="org.springframework.http.converter.StringHttpMessageConverter">
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/plain;charset=UTF-8</value>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
    
    
    <!-- <bean
        class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" />
         -->
         
    <!-- 視圖解析器,對轉向頁面的路徑解析。prefix:前綴,suffix:后綴 -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
    
    <!-- <bean id="multipartResolver"  
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/> -->

</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" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
    <bean id="iniRealm" class="org.apache.shiro.realm.text.IniRealm">
        <property name="resourcePath" value="classpath:/shiro.ini" />
    </bean>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="iniRealm" />
    </bean>
    <!--Shiro 生命周期處理器--> 
    <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" />
        <property name="successUrl" value="/home/" />

        <property name="filterChainDefinitions">
            <value>
                /home/** = authc
            </value>
        </property>
    </bean>


</beans>

3.然后定義ProtectedService.java來實現(xiàn)功能。

package com.hp.shiro.simplerbac.bean;

import java.io.File;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.shiro.authz.annotation.RequiresPermissions;

public class ProtectedService {
    private static final List<String> USERS = Arrays.asList("root","guest","gandhi","bose");
    
    private static final List<String> ROLES = Arrays.asList("root","guest","role1","role2");
    
    @RequiresPermissions("user-roles:read")
    public List<String> getUsers() {
        return USERS;
    }
    
    @RequiresPermissions("user-roles:read")
    public List<String> getRoles() {
        return ROLES;
    }
    
    @RequiresPermissions("system:read:time")
    public Date getSystemTime() {
        return Calendar.getInstance().getTime();
    }
    
    @RequiresPermissions("calculator:add")
    public int sum(int a, int b) {
        return a+b;
    }
    
    @RequiresPermissions("calculator:subtract")
    public int diff(int a, int b) {
        return a-b;
    }
    
    @RequiresPermissions("filesystem:read:home")
    public List<String> getHomeFiles() {
        File homeDir = new File(System.getProperty("user.home"));
        return Arrays.asList(homeDir.list());
    }

    public String getGreetingMessage(String name) {
        return String.format("Hello %s",name);
    }
}

使用了@RequiresPermissions()注解來表示每一個方法的需要的permission,沒有該注解的getGreetingMessage(String name)方法不要求任何權限。

4.定義兩個Controller,分別是登陸/登出頁面的controller和成功登陸以后完成訪問系統(tǒng)資源功能的controller。

package com.hp.shiro.simplerbac.controller;

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.UsernamePasswordToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;


@Controller
public class LoginController {
    @RequestMapping(value="login", method=RequestMethod.GET)
    public String login(HttpServletRequest req){
        if (SecurityUtils.getSubject().isAuthenticated()) {
            return "redirect:/home";
        } else {
            return "login";
        }
    }

    @RequestMapping(value="login", method=RequestMethod.POST)
    public String login(HttpServletRequest req,RedirectAttributes redirectAttributes,Model model) {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("access to login");
        System.out.println(username+","+password);
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        String errorMessage = null;
        try {
            SecurityUtils.getSubject().login(token);
        } catch (AuthenticationException e) {
            errorMessage = "user name doesn't exist or wrong password";
        }
        if(null == errorMessage) {
            redirectAttributes.addAttribute("username", username);
            return "redirect:/home";
        } else {
            System.out.println(errorMessage);
            req.setAttribute("errorMessage",errorMessage);
            return "login";
        }
    }
    
    @RequestMapping(value="logout")
    public String logout(HttpServletRequest req){
        SecurityUtils.getSubject().logout();
        return "redirect:/login";
    }
    
    
}

package com.hp.shiro.simplerbac.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.hp.shiro.simplerbac.bean.ProtectedService;

@Controller
public class HomeController {
    @RequestMapping(value="home")
    public String home(HttpServletRequest req, HttpServletResponse response, Model model){
//      System.out.println("access to home controller.");
        String username = (String)SecurityUtils.getSubject().getPrincipal();
//      System.out.println("username:"+username);
        model.addAttribute("username", username);
        String method = req.getParameter("method");
//      System.out.println("method:"+method);
        /*
         * method可能的值value包括:
         *  <input type="hidden" name="method" value="getUsers"/>
         *  <input type="hidden" name="method" value="getRoles"/>
         *  <input type="hidden" name="method" value="getSystemTime"/>
         *  <input type="hidden" name="method" value="sum"/>
         *  <input type="hidden" name="method" value="diff"/>
         *  <input type="hidden" name="method" value="getHomeFiles"/>
         *  <input type="hidden" name="method" value="getGreetingMessage"/>
         */

        ProtectedService protectedService = new ProtectedService();
        
        try {
            if ("getUsers".equals(method)) {
                model.addAttribute("users", protectedService.getUsers());
            } else if ("getRoles".equals(method)) {
                model.addAttribute("roles", protectedService.getRoles());
            } else if ("getSystemTime".equals(method)) {
                model.addAttribute("systemTime", protectedService.getSystemTime());
            } else if ("sum".equals(method)) {
                int a = Integer.parseInt(req.getParameter("a"));
                int b = Integer.parseInt(req.getParameter("b"));
                model.addAttribute("sum",protectedService.sum(a, b));
            } else if ("diff".equals(method)) {
                int a = Integer.parseInt(req.getParameter("a"));
                int b = Integer.parseInt(req.getParameter("b"));
                model.addAttribute("diff",protectedService.diff(a, b));
            } else if ("getHomeFiles".equals(method)) {
                model.addAttribute("homeFiles",protectedService.getHomeFiles());
            } else if ("getGreetingMessage".equals(method)) {
                String name = req.getParameter("name");
                model.addAttribute("greetingMessage",protectedService.getGreetingMessage(name));
            }
        } catch(Exception e) {
            model.addAttribute("errorMessage", e.getMessage());
        }
        
        return "home";
    }
}

5.定義jsp文件和css樣式文件,具體代碼參見工程源碼HP-shiro-spring

總結:
該項目的主要目的是對spring的shiro的配置文件進行一個梳理,了解它倆結合的具體配置方式。
該項目將用戶名和密碼簡單的存放在文本文件中,而且是明文存儲,以后需要遷移到數(shù)據(jù)庫加密存儲的形式。
參考開濤的博客進一步對shiro的功能進行探索。例如加密解密模塊和session管理模塊。

2016/06/22日更新:使用shiro+springmvc+mybatis實現(xiàn)的小例子,頁面沒有變化,添加了數(shù)據(jù)庫的支持。
項目地址:https://github.com/lunabird/shiro-demo.git

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,273評論 6 342
  • 一. 擴展運算符 擴展運算符(spread)是三個點(...)。它好比 rest 參數(shù)的逆運算,將一個數(shù)組轉為用逗...
    markpapa閱讀 203評論 0 0
  • 翻開前些年寫的本子,上面密密麻麻的寫著自己一輩子要做的事情,可如今夢想猶在,只是被生活捆綁住了,5年的時光說過去就...
    宛若清風R閱讀 693評論 1 0
  • 休言鐵骨此身堅, 滿腹空空亦赧顏。 墨里妝來香萬千。 遣心弦, 著得芬芳竟芷蘭。
    高十一妹閱讀 327評論 2 32

友情鏈接更多精彩內容