为了账号安全,请及时绑定邮箱和手机立即绑定

使用shiro结合spring框架进行用户认证

标签:
Java

Apache  Shiro 是 Java 的一个安全框架。目前,使用 Apache  Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。

HP-shiro-spring是一个简单的基于Spring实现shiro的例子,进行用户的身份认证,实现基于role的授权。该项目是由MyEclipse进行构建的动态web项目。

项目具体实现层次结构如下:


HP-shiro-spring-test.png

  1. 首先定义shiro.ini,用来指定用户身份和凭据。

[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权限。这些权限在当前用户对系统资源进行访问的时候要用到。

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分发请求所必须的。并提供了:数据绑定支持,
    @NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,读写XML
    的支持(JAXB),读写JSON的支持(Jackson)-->
    <mvc:annotation-driven />
    
    <!-- 指定静态资源的位置,例如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来实现功能。

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和成功登陆以后完成访问系统资源功能的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;@Controllerpublic 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的配置文件进行一个梳理,了解它俩结合的具体配置方式。
该项目将用户名和密码简单的存放在文本文件中,而且是明文存储,以后需要迁移到数据库加密存储的形式。
参考开涛的博客进一步对shiro的功能进行探索。例如加密解密模块和session管理模块。

2016/06/22日更新:使用shiro+springmvc+mybatis实现的小例子,页面没有变化,添加了数据库的支持。
项目地址:https://github.com/lunabird/shiro-demo.git



作者:lunabird
链接:https://www.jianshu.com/p/2b2e107ac9b0


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消