使用shiro結(jié)合spring框架進(jìn)行用戶認(rèn)證

Apache Shiro 是 Java 的一個(gè)安全框架德撬。目前银酬,使用 Apache Shiro 的人越來(lái)越多末贾,因?yàn)樗喈?dāng)簡(jiǎn)單揩晴,對(duì)比 Spring Security勋陪,可能沒(méi)有 Spring Security 做的功能強(qiáng)大,但是在實(shí)際工作時(shí)可能并不需要那么復(fù)雜的東西硫兰,所以使用小而簡(jiǎn)單的 Shiro 就足夠了诅愚。

HP-shiro-spring是一個(gè)簡(jiǎn)單的基于Spring實(shí)現(xiàn)shiro的例子,進(jìn)行用戶的身份認(rèn)證劫映,實(shí)現(xiàn)基于role的授權(quán)违孝。該項(xiàng)目是由MyEclipse進(jìn)行構(gòu)建的動(dòng)態(tài)web項(xiàng)目。

項(xiàng)目具體實(shí)現(xiàn)層次結(jié)構(gòu)如下:


HP-shiro-spring-test.png
  1. 首先定義shiro.ini泳赋,用來(lái)指定用戶身份和憑據(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文件定義了四個(gè)用戶,格式為“用戶名=密碼,角色”祖今;每個(gè)角色擁有一些權(quán)限校坑。
root擁有所有的權(quán)限,role1擁有filesystem以及system的所有權(quán)限千诬,role2擁有calculator的add和substract權(quán)限耍目。這些權(quán)限在當(dāng)前用戶對(duì)系統(tǒng)資源進(jìn)行訪問(wèn)的時(shí)候要用到。

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">
    <!-- 作用:在啟動(dòng)Web容器時(shí)徐绑,自動(dòng)裝配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  
     當(dāng)我們需要controller返回一個(gè)map的json對(duì)象時(shí)制妄,可以設(shè)定<mvc:annotation-driven />
     會(huì)自動(dòng)注冊(cè)DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter
      兩個(gè)bean,是spring MVC為@Controllers分發(fā)請(qǐng)求所必須的。并提供了:數(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 />
    
    <!-- 設(shè)置使用注解的類所在的包名 -->
    <context:component-scan base-package="com.hp.shiro.simplerbac.controller" />
    
    <!--完成請(qǐng)求和注解pojo的映射振愿。 
    當(dāng)我們需要controller返回一個(gè)map的json對(duì)象時(shí)捷犹,可以設(shè)定<mvc:annotation-driven />弛饭,
    同時(shí)設(shè)定<mvc:message-converters> 標(biāo)簽,設(shè)定字符集和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" />
         -->
         
    <!-- 視圖解析器萍歉,對(duì)轉(zhuǎn)向頁(yè)面的路徑解析侣颂。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來(lái)實(shí)現(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()注解來(lái)表示每一個(gè)方法的需要的permission憔晒,沒(méi)有該注解的getGreetingMessage(String name)方法不要求任何權(quán)限。

4.定義兩個(gè)Controller蔑舞,分別是登陸/登出頁(yè)面的controller和成功登陸以后完成訪問(wèn)系統(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樣式文件,具體代碼參見(jiàn)工程源碼HP-shiro-spring

總結(jié):
該項(xiàng)目的主要目的是對(duì)spring的shiro的配置文件進(jìn)行一個(gè)梳理攻询,了解它倆結(jié)合的具體配置方式从撼。
該項(xiàng)目將用戶名和密碼簡(jiǎn)單的存放在文本文件中,而且是明文存儲(chǔ)钧栖,以后需要遷移到數(shù)據(jù)庫(kù)加密存儲(chǔ)的形式低零。
參考開(kāi)濤的博客進(jìn)一步對(duì)shiro的功能進(jìn)行探索。例如加密解密模塊和session管理模塊桐经。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末气堕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子畔咧,更是在濱河造成了極大的恐慌茎芭,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件誓沸,死亡現(xiàn)場(chǎng)離奇詭異梅桩,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拜隧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門宿百,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人洪添,你說(shuō)我怎么就攤上這事垦页。” “怎么了干奢?”我有些...
    開(kāi)封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵痊焊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)薄啥,這世上最難降的妖魔是什么辕羽? 我笑而不...
    開(kāi)封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮垄惧,結(jié)果婚禮上刁愿,老公的妹妹穿的比我還像新娘。我一直安慰自己赘艳,他們只是感情好酌毡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蕾管,像睡著了一般枷踏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掰曾,一...
    開(kāi)封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天旭蠕,我揣著相機(jī)與錄音,去河邊找鬼旷坦。 笑死掏熬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秒梅。 我是一名探鬼主播旗芬,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼捆蜀!你這毒婦竟也來(lái)了疮丛?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤辆它,失蹤者是張志新(化名)和其女友劉穎誊薄,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體锰茉,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呢蔫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了飒筑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片片吊。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖协屡,靈堂內(nèi)的尸體忽然破棺而出俏脊,到底是詐尸還是另有隱情,我是刑警寧澤著瓶,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響材原,放射性物質(zhì)發(fā)生泄漏沸久。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一余蟹、第九天 我趴在偏房一處隱蔽的房頂上張望卷胯。 院中可真熱鬧,春花似錦威酒、人聲如沸窑睁。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)担钮。三九已至,卻和暖如春尤仍,著一層夾襖步出監(jiān)牢的瞬間箫津,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工宰啦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苏遥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓赡模,卻偏偏與公主長(zhǎng)得像田炭,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子漓柑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理教硫,服務(wù)發(fā)現(xiàn),斷路器欺缘,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,810評(píng)論 6 342
  • 一. 擴(kuò)展運(yùn)算符 擴(kuò)展運(yùn)算符(spread)是三個(gè)點(diǎn)(...)栋豫。它好比 rest 參數(shù)的逆運(yùn)算,將一個(gè)數(shù)組轉(zhuǎn)為用逗...
    markpapa閱讀 189評(píng)論 0 0
  • 翻開(kāi)前些年寫的本子,上面密密麻麻的寫著自己一輩子要做的事情嫩絮,可如今夢(mèng)想猶在丛肢,只是被生活捆綁住了,5年的時(shí)光說(shuō)過(guò)去就...
    宛若清風(fēng)R閱讀 572評(píng)論 1 0
  • 休言鐵骨此身堅(jiān)剿干, 滿腹空空亦赧顏蜂怎。 墨里妝來(lái)香萬(wàn)千。 遣心弦置尔, 著得芬芳竟芷蘭杠步。
    高十一妹閱讀 272評(píng)論 2 32